mypy icon indicating copy to clipboard operation
mypy copied to clipboard

Regression with type narrowing of bool

Open cdce8p opened this issue 4 years ago • 2 comments

Regression in 0.920 / 0.930 compared with 0.910.

from typing import Any

def func() -> Any:
    ...

var = func()
reveal_type(var)  # Any
assert isinstance(var, bool)
reveal_type(var)  # bool

if var:
    reveal_type(var)  # Literal[True]
    pass
reveal_type(var)  # Any -> should still be 'bool'!

The type should not be lost if it can't be narrowed to Literal[True] or Literal[False]. It's a side effect of #10389. The issue was already mentioned https://github.com/python/mypy/pull/10389#issuecomment-835805544 but hasn't been addressed yet.

/CC: @ethan-leba

cdce8p avatar Dec 23 '21 17:12 cdce8p

Ok, looks like I was able to debug this problem. It is not really related to https://github.com/python/mypy/pull/10389, but this PR just made this problem visible. But, theoretically you can come up with other examples. Like:

from typing import Any

var: Any
reveal_type(var)  # N: Revealed type is "Any"
assert isinstance(var, bool) or isinstance(var, str)
reveal_type(var)  # N: Revealed type is "Union[builtins.bool, builtins.str]"

if isinstance(var, bool):
    reveal_type(var)  # N: Revealed type is "builtins.bool"

# Should be `Union[builtins.bool, builtins.str]`, but:
reveal_type(var)  # N: Revealed type is "Any"

What is the cause of this problem? https://github.com/python/mypy/blob/f96446ce2f99a86210b21d39c7aec4b13a8bfc66/mypy/binder.py#L232-L237

  1. When we use allow_jump, it ends up with two frame objects in self.options_on_return[index]
  2. Each type in self.options_on_return[index] is only a part of the original type
  3. So, when we run this check: if not all(is_same_type(type, cast(Type, t)) for t in resulting_values[1:]): it evaluates to True and Any is returned

I guess we should write proper type joiner for frame object. Two frames with Var(x): Literal[True] and Var(x): Literal[False] should be joined as Var(x): bool. The same with union types.

sobolevn avatar Dec 25 '21 21:12 sobolevn

I have a related issue. Not sure if the same cause, though. In this code:

https://github.com/life4/deal/blob/48c6b1dbc51781f95ebba19c618f875ca9199ad4/deal/_runtime/_contracts.py#L67-L74

contracts variable should be inferred as Contracts after contracts = cls(func) but now it is Optional[Any].

  • Explicitly annotating cls doesn't help.
  • If I explicitly annotate contracts: Optional[Contracts], it stays that way, no type refinement is done by contracts = cls(func)
  • If I add an explicit assert contracts is not None, it also doesn't help, contracts stays Optional[Contracts].

In mypy 0.910 everything was fine. In pyright everything is also correctly inferred.

orsinium avatar Dec 27 '21 12:12 orsinium