Skip to content

Commit

Permalink
Fix tuple[Any, ...] subtyping (#16108)
Browse files Browse the repository at this point in the history
Follow up to #16073 and #16076
Fix needed for
https://github.com/python/mypy/pull/16053/files#r1316481395

I add test cases that would have caught my previous incorrect PR. I add
an explicit case for the new desirable behaviour we see with zip.
  • Loading branch information
hauntsaninja authored Sep 14, 2023
1 parent f41e24c commit 2c2d126
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 12 deletions.
2 changes: 1 addition & 1 deletion mypy/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1359,7 +1359,7 @@ def set_strict_flags() -> None:
parser.error("Can only find occurrences of class members.")
if len(_find_occurrences) != 2:
parser.error("Can only find occurrences of non-nested class members.")
state.find_occurrences = _find_occurrences # type: ignore[assignment]
state.find_occurrences = _find_occurrences

# Set reports.
for flag, val in vars(special_opts).items():
Expand Down
6 changes: 4 additions & 2 deletions mypy/subtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -463,8 +463,10 @@ def visit_instance(self, left: Instance) -> bool:
assert unpacked.type.fullname == "builtins.tuple"
if isinstance(get_proper_type(unpacked.args[0]), AnyType):
return not self.proper_subtype
# TODO: we need a special case similar to above to consider (something that maps to)
# tuple[Any, ...] a subtype of Tuple[<whatever>].
if mapped.type.fullname == "builtins.tuple" and isinstance(
get_proper_type(mapped.args[0]), AnyType
):
return not self.proper_subtype
return False
if isinstance(right, TypeVarTupleType):
# tuple[Any, ...] is like Any in the world of tuples (see special case above).
Expand Down
164 changes: 155 additions & 9 deletions test-data/unit/check-tuples.test
Original file line number Diff line number Diff line change
Expand Up @@ -107,19 +107,147 @@ class A: pass
class B(A): pass
[builtins fixtures/tuple.pyi]

[case testSubtypingWithNamedTupleType]
from typing import Tuple
t1: Tuple[A, A]
t2: tuple

if int():
t1 = t2 # E: Incompatible types in assignment (expression has type "Tuple[Any, ...]", variable has type "Tuple[A, A]")
if int():
t2 = t1
[case testSubtypingWithTupleType]
from __future__ import annotations
from typing import Any, Tuple

tuple_aa: tuple[A, A]
Tuple_aa: Tuple[A, A]

tuple_obj: tuple[object, ...]
Tuple_obj: Tuple[object, ...]

tuple_obj_one: tuple[object]
Tuple_obj_one: Tuple[object]

tuple_obj_two: tuple[object, object]
Tuple_obj_two: Tuple[object, object]

tuple_any_implicit: tuple
Tuple_any_implicit: Tuple

tuple_any: tuple[Any, ...]
Tuple_any: Tuple[Any, ...]

tuple_any_one: tuple[Any]
Tuple_any_one: Tuple[Any]

tuple_any_two: tuple[Any, Any]
Tuple_any_two: Tuple[Any, Any]

def takes_tuple_aa(t: tuple[A, A]): ...

takes_tuple_aa(tuple_aa)
takes_tuple_aa(Tuple_aa)
takes_tuple_aa(tuple_obj) # E: Argument 1 to "takes_tuple_aa" has incompatible type "Tuple[object, ...]"; expected "Tuple[A, A]"
takes_tuple_aa(Tuple_obj) # E: Argument 1 to "takes_tuple_aa" has incompatible type "Tuple[object, ...]"; expected "Tuple[A, A]"
takes_tuple_aa(tuple_obj_one) # E: Argument 1 to "takes_tuple_aa" has incompatible type "Tuple[object]"; expected "Tuple[A, A]"
takes_tuple_aa(Tuple_obj_one) # E: Argument 1 to "takes_tuple_aa" has incompatible type "Tuple[object]"; expected "Tuple[A, A]"
takes_tuple_aa(tuple_obj_two) # E: Argument 1 to "takes_tuple_aa" has incompatible type "Tuple[object, object]"; expected "Tuple[A, A]"
takes_tuple_aa(Tuple_obj_two) # E: Argument 1 to "takes_tuple_aa" has incompatible type "Tuple[object, object]"; expected "Tuple[A, A]"
takes_tuple_aa(tuple_any_implicit)
takes_tuple_aa(Tuple_any_implicit)
takes_tuple_aa(tuple_any)
takes_tuple_aa(Tuple_any)
takes_tuple_aa(tuple_any_one) # E: Argument 1 to "takes_tuple_aa" has incompatible type "Tuple[Any]"; expected "Tuple[A, A]"
takes_tuple_aa(Tuple_any_one) # E: Argument 1 to "takes_tuple_aa" has incompatible type "Tuple[Any]"; expected "Tuple[A, A]"
takes_tuple_aa(tuple_any_two)
takes_tuple_aa(Tuple_any_two)

def takes_tuple_any_implicit(t: tuple): ...

takes_tuple_any_implicit(tuple_aa)
takes_tuple_any_implicit(Tuple_aa)
takes_tuple_any_implicit(tuple_obj)
takes_tuple_any_implicit(Tuple_obj)
takes_tuple_any_implicit(tuple_obj_one)
takes_tuple_any_implicit(Tuple_obj_one)
takes_tuple_any_implicit(tuple_obj_two)
takes_tuple_any_implicit(Tuple_obj_two)
takes_tuple_any_implicit(tuple_any_implicit)
takes_tuple_any_implicit(Tuple_any_implicit)
takes_tuple_any_implicit(tuple_any)
takes_tuple_any_implicit(Tuple_any)
takes_tuple_any_implicit(tuple_any_one)
takes_tuple_any_implicit(Tuple_any_one)
takes_tuple_any_implicit(tuple_any_two)
takes_tuple_any_implicit(Tuple_any_two)

def takes_tuple_any_one(t: tuple[Any]): ...

takes_tuple_any_one(tuple_aa) # E: Argument 1 to "takes_tuple_any_one" has incompatible type "Tuple[A, A]"; expected "Tuple[Any]"
takes_tuple_any_one(Tuple_aa) # E: Argument 1 to "takes_tuple_any_one" has incompatible type "Tuple[A, A]"; expected "Tuple[Any]"
takes_tuple_any_one(tuple_obj) # E: Argument 1 to "takes_tuple_any_one" has incompatible type "Tuple[object, ...]"; expected "Tuple[Any]"
takes_tuple_any_one(Tuple_obj) # E: Argument 1 to "takes_tuple_any_one" has incompatible type "Tuple[object, ...]"; expected "Tuple[Any]"
takes_tuple_any_one(tuple_obj_one)
takes_tuple_any_one(Tuple_obj_one)
takes_tuple_any_one(tuple_obj_two) # E: Argument 1 to "takes_tuple_any_one" has incompatible type "Tuple[object, object]"; expected "Tuple[Any]"
takes_tuple_any_one(Tuple_obj_two) # E: Argument 1 to "takes_tuple_any_one" has incompatible type "Tuple[object, object]"; expected "Tuple[Any]"
takes_tuple_any_one(tuple_any_implicit)
takes_tuple_any_one(Tuple_any_implicit)
takes_tuple_any_one(tuple_any)
takes_tuple_any_one(Tuple_any)
takes_tuple_any_one(tuple_any_one)
takes_tuple_any_one(Tuple_any_one)
takes_tuple_any_one(tuple_any_two) # E: Argument 1 to "takes_tuple_any_one" has incompatible type "Tuple[Any, Any]"; expected "Tuple[Any]"
takes_tuple_any_one(Tuple_any_two) # E: Argument 1 to "takes_tuple_any_one" has incompatible type "Tuple[Any, Any]"; expected "Tuple[Any]"

class A: pass
[builtins fixtures/tuple.pyi]

[case testSubtypingWithTupleTypeSubclass]
from __future__ import annotations
from typing import Any, Tuple

class A: ...

inst_tuple_aa: Tuple[A, A]

class tuple_aa_subclass(Tuple[A, A]): ...
inst_tuple_aa_subclass: tuple_aa_subclass

class tuple_any_subclass(Tuple[Any, ...]): ...
inst_tuple_any_subclass: tuple_any_subclass

class tuple_any_one_subclass(Tuple[Any]): ...
inst_tuple_any_one_subclass: tuple_any_one_subclass

class tuple_any_two_subclass(Tuple[Any, Any]): ...
inst_tuple_any_two_subclass: tuple_any_two_subclass

class tuple_obj_subclass(Tuple[object, ...]): ...
inst_tuple_obj_subclass: tuple_obj_subclass

class tuple_obj_one_subclass(Tuple[object]): ...
inst_tuple_obj_one_subclass: tuple_obj_one_subclass

class tuple_obj_two_subclass(Tuple[object, object]): ...
inst_tuple_obj_two_subclass: tuple_obj_two_subclass

def takes_tuple_aa(t: Tuple[A, A]): ...

takes_tuple_aa(inst_tuple_aa)
takes_tuple_aa(inst_tuple_aa_subclass)
takes_tuple_aa(inst_tuple_any_subclass)
takes_tuple_aa(inst_tuple_any_one_subclass) # E: Argument 1 to "takes_tuple_aa" has incompatible type "tuple_any_one_subclass"; expected "Tuple[A, A]"
takes_tuple_aa(inst_tuple_any_two_subclass)
takes_tuple_aa(inst_tuple_obj_subclass) # E: Argument 1 to "takes_tuple_aa" has incompatible type "tuple_obj_subclass"; expected "Tuple[A, A]"
takes_tuple_aa(inst_tuple_obj_one_subclass) # E: Argument 1 to "takes_tuple_aa" has incompatible type "tuple_obj_one_subclass"; expected "Tuple[A, A]"
takes_tuple_aa(inst_tuple_obj_two_subclass) # E: Argument 1 to "takes_tuple_aa" has incompatible type "tuple_obj_two_subclass"; expected "Tuple[A, A]"

def takes_tuple_aa_subclass(t: tuple_aa_subclass): ...

takes_tuple_aa_subclass(inst_tuple_aa) # E: Argument 1 to "takes_tuple_aa_subclass" has incompatible type "Tuple[A, A]"; expected "tuple_aa_subclass"
takes_tuple_aa_subclass(inst_tuple_aa_subclass)
takes_tuple_aa_subclass(inst_tuple_any_subclass) # E: Argument 1 to "takes_tuple_aa_subclass" has incompatible type "tuple_any_subclass"; expected "tuple_aa_subclass"
takes_tuple_aa_subclass(inst_tuple_any_one_subclass) # E: Argument 1 to "takes_tuple_aa_subclass" has incompatible type "tuple_any_one_subclass"; expected "tuple_aa_subclass"
takes_tuple_aa_subclass(inst_tuple_any_two_subclass) # E: Argument 1 to "takes_tuple_aa_subclass" has incompatible type "tuple_any_two_subclass"; expected "tuple_aa_subclass"
takes_tuple_aa_subclass(inst_tuple_obj_subclass) # E: Argument 1 to "takes_tuple_aa_subclass" has incompatible type "tuple_obj_subclass"; expected "tuple_aa_subclass"
takes_tuple_aa_subclass(inst_tuple_obj_one_subclass) # E: Argument 1 to "takes_tuple_aa_subclass" has incompatible type "tuple_obj_one_subclass"; expected "tuple_aa_subclass"
takes_tuple_aa_subclass(inst_tuple_obj_two_subclass) # E: Argument 1 to "takes_tuple_aa_subclass" has incompatible type "tuple_obj_two_subclass"; expected "tuple_aa_subclass"

[builtins fixtures/tuple.pyi]

[case testTupleInitializationWithNone]
# flags: --no-strict-optional
from typing import Tuple
Expand Down Expand Up @@ -1522,3 +1650,21 @@ class Bar(aaaaaaaaaa): # E: Name "aaaaaaaaaa" is not defined
class FooBarTuple(Tuple[Foo, Bar]):
...
[builtins fixtures/tuple.pyi]


[case testTupleOverloadZipAny]
from typing import Any, Iterable, Iterator, Tuple, TypeVar, overload

T = TypeVar("T")

@overload
def zip(__i: Iterable[T]) -> Iterator[Tuple[T]]: ...
@overload
def zip(*i: Iterable[Any]) -> Iterator[Tuple[Any, ...]]: ...
def zip(i): ...

def g(t: Tuple):
# Ideally, we'd infer that these are iterators of tuples
reveal_type(zip(*t)) # N: Revealed type is "typing.Iterator[Any]"
reveal_type(zip(t)) # N: Revealed type is "typing.Iterator[Any]"
[builtins fixtures/tuple.pyi]

0 comments on commit 2c2d126

Please sign in to comment.