Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix tuple subtyping #16073

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 @@ -435,8 +435,10 @@ def visit_instance(self, left: Instance) -> bool:
# dynamic base classes correctly, see #5456.
return not isinstance(self.right, NoneType)
right = self.right
if isinstance(right, TupleType) and right.partial_fallback.type.is_enum:
return self._is_subtype(left, mypy.typeops.tuple_fallback(right))
if isinstance(right, TupleType):
return self._is_subtype(left, right.partial_fallback) and self._is_subtype(
left, mypy.typeops.tuple_fallback(right)
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, but this is wrong. IIUC this will make tuple[object, ...] a subtype of tuple[int, str]. While even tuple[int, ...] shoul not be a subtype of tuple[int, int]. Really only tuple[Any, ...] should be a subtype of say tuple[int, int]. This is the same as we consider *args: Any in callables very differently from *args: object or *args: int.

Note btw this question will appear even broader in context of my upcoming PR for variadic types, e.g. should we also special case Foo[int, *tuple[Any, ...], str] similarly in spirit to extending the special-casing of trivial arguments to trivial suffix in #15913. (Btw I would say yes, but it should be a separate PR after we gain more experience).

Also I bet most of the unused type ignores in mypy primer are from situations like:

v: tuple[int, ...]
if len(v) == 2:
    x: tuple[int, int] = v

(as e.g. for ignore in mypy itself). See also #1178. The proper way to solve such issues is by finally completing #10367. Unfortunately author is not responding anymore, so one of us may need to complete the PR, since it is an important one.

if isinstance(right, Instance):
if type_state.is_cached_subtype_check(self._subtype_kind, left, right):
return True
Expand Down
18 changes: 17 additions & 1 deletion test-data/unit/check-tuples.test
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ 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]")
t1 = t2
if int():
t2 = t1

Expand Down Expand Up @@ -1522,3 +1522,19 @@ 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):
reveal_type(zip(*t)) # N: Revealed type is "typing.Iterator[builtins.tuple[Any, ...]]"
reveal_type(zip(t)) # N: Revealed type is "typing.Iterator[Tuple[Any]]"
[builtins fixtures/tuple.pyi]
4 changes: 1 addition & 3 deletions test-data/unit/check-typevar-tuple.test
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,7 @@ reveal_type(f(f_args3)) # N: Revealed type is "Tuple[builtins.str, builtins.str
f(empty) # E: Argument 1 to "f" has incompatible type "Tuple[()]"; expected "Tuple[int]"
f(bad_args) # E: Argument 1 to "f" has incompatible type "Tuple[str, str]"; expected "Tuple[int, str]"

# The reason for error in subtle: actual can be empty, formal cannot.
reveal_type(f(var_len_tuple)) # N: Revealed type is "Tuple[builtins.str, Unpack[builtins.tuple[builtins.int, ...]]]" \
# E: Argument 1 to "f" has incompatible type "Tuple[int, ...]"; expected "Tuple[int, Unpack[Tuple[int, ...]]]"
reveal_type(f(var_len_tuple)) # N: Revealed type is "Tuple[builtins.str, Unpack[builtins.tuple[builtins.int, ...]]]"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error is correct, because () is an element of tuple[int, ...] but it is not an element of tuple[int, *tuple[int, ...]] (this must have at least one integer in the tuple). So tuple[int, ...] is not a subtype of tuple[int, *tuple[int, ...]]


g_args: Tuple[str, int]
reveal_type(g(g_args)) # N: Revealed type is "Tuple[builtins.str, builtins.str]"
Expand Down