Skip to content

Commit

Permalink
Fix missing meet case exposed by len narrowing (#16470)
Browse files Browse the repository at this point in the history
Fixes #16468

The fix is straightforward.

Btw when fixing this I noticed that we disregard type arguments when
narrowing, for example:
```python
x: Sequence[int]
if isinstance(x, tuple):
    reveal_type(x)  # tuple[Any, ...], but should be `tuple[int, ...]`
```
I guess fixing this may be tricky, and it is quite old behavior.
  • Loading branch information
ilevkivskyi authored and JukkaL committed Nov 15, 2023
1 parent 88791ca commit 5c354c4
Show file tree
Hide file tree
Showing 3 changed files with 16 additions and 2 deletions.
3 changes: 2 additions & 1 deletion mypy/meet.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from mypy.typeops import is_recursive_pair, make_simplified_union, tuple_fallback
from mypy.types import (
MYPYC_NATIVE_INT_NAMES,
TUPLE_LIKE_INSTANCE_NAMES,
AnyType,
CallableType,
DeletedType,
Expand Down Expand Up @@ -936,7 +937,7 @@ def visit_tuple_type(self, t: TupleType) -> ProperType:
return TupleType(items, tuple_fallback(t))
elif isinstance(self.s, Instance):
# meet(Tuple[t1, t2, <...>], Tuple[s, ...]) == Tuple[meet(t1, s), meet(t2, s), <...>].
if self.s.type.fullname == "builtins.tuple" and self.s.args:
if self.s.type.fullname in TUPLE_LIKE_INSTANCE_NAMES and self.s.args:
return t.copy_modified(items=[meet_types(it, self.s.args[0]) for it in t.items])
elif is_proper_subtype(t, self.s):
# A named tuple that inherits from a normal class
Expand Down
13 changes: 13 additions & 0 deletions test-data/unit/check-narrowing.test
Original file line number Diff line number Diff line change
Expand Up @@ -1910,3 +1910,16 @@ if len(x) == a:
else:
reveal_type(x) # N: Revealed type is "Union[Tuple[builtins.int, builtins.int], Tuple[builtins.int, builtins.int, builtins.int]]"
[builtins fixtures/len.pyi]

[case testNarrowingLenUnionWithUnreachable]
from typing import Union, Sequence

def f(x: Union[int, Sequence[int]]) -> None:
if (
isinstance(x, tuple)
and len(x) == 2
and isinstance(x[0], int)
and isinstance(x[1], int)
):
reveal_type(x) # N: Revealed type is "Tuple[builtins.int, builtins.int]"
[builtins fixtures/len.pyi]
2 changes: 1 addition & 1 deletion test-data/unit/fixtures/len.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class object:
class type:
def __init__(self, x) -> None: pass

class tuple(Generic[T]):
class tuple(Sequence[T]):
def __len__(self) -> int: pass

class list(Sequence[T]): pass
Expand Down

0 comments on commit 5c354c4

Please # to comment.