-
-
Notifications
You must be signed in to change notification settings - Fork 3k
tuple[Any, *tuple[Any, ...]]
is not assignable to other tuple types
#19109
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
Comments
tuple[Any, *tuple[Any, ...]]
tuple[Any, *tuple[Any, ...]]
is not assignable to other tuple types
and yet it's the only check we do there that takes And it's...complicated. Tuples may have fixed prefixes and suffices around unpack, the following is a valid type: tuple[int, *tuple[int, ...], str] which means that when RHS has fewer elements, we need to be careful not to count the same RHS item twice. I have a patch, but I hate it... And doubt it's complete, I'm usually bad at getting index math right from the first try. I'll try to polish it a bit later, feel free to join my efforts! diff --git a/mypy/subtypes.py b/mypy/subtypes.py
index 84fda7955..d31b7dd9a 100644
--- a/mypy/subtypes.py
+++ b/mypy/subtypes.py
@@ -1,6 +1,6 @@
from __future__ import annotations
-from collections.abc import Iterable, Iterator
+from collections.abc import Iterable, Iterator, Sequence
from contextlib import contextmanager
from typing import Any, Callable, Final, TypeVar, cast
from typing_extensions import TypeAlias as _TypeAlias
@@ -782,9 +782,29 @@ class SubtypeVisitor(TypeVisitor[bool]):
# doesn't have one, we will fall through to False down the line.
if self.variadic_tuple_subtype(left, right):
return True
- if len(left.items) != len(right.items):
- return False
- if any(not self._is_subtype(l, r) for l, r in zip(left.items, right.items)):
+ if (li := find_unpack_in_list(left.items)) is not None:
+ if len(left.items) - 1 > len(right.items):
+ # Even without the unpack we don't have enough items. Sad.
+ return False
+
+ left_prefix = left.items[:li]
+ left_unp = left.items[li]
+ assert isinstance(left_unp, UnpackType)
+ left_suffix = left.items[li + 1 :]
+
+ right_prefix = right.items[: len(left_prefix)]
+ # No negative indexing - -0 is same as +0.
+ right_suffix = right.items[len(right.items) - len(left_suffix) :]
+ right_middle = right.items[len(left_prefix) : len(right.items) - len(left_suffix)]
+ return (
+ self._is_subtype_sequence(left_prefix, right_prefix)
+ and self._is_subtype_sequence(left_suffix, right_suffix)
+ and self._is_subtype(
+ left_unp.type, TupleType(right_middle, right.partial_fallback)
+ )
+ )
+
+ if not self._is_subtype_sequence(left.items, right.items):
return False
if is_named_instance(right.partial_fallback, "builtins.tuple"):
# No need to verify fallback. This is useful since the calculated fallback
@@ -801,6 +821,11 @@ class SubtypeVisitor(TypeVisitor[bool]):
else:
return False
+ def _is_subtype_sequence(self, lefts: Sequence[Type], rights: Sequence[Type]) -> bool:
+ return len(lefts) == len(rights) and all(
+ self._is_subtype(left, right) for left, right in zip(lefts, rights)
+ )
+
def variadic_tuple_subtype(self, left: TupleType, right: TupleType) -> bool:
"""Check subtyping between two potentially variadic tuples.
diff --git a/test-data/unit/check-typevar-tuple.test b/test-data/unit/check-typevar-tuple.test
index d364439f2..0c06f0be2 100644
--- a/test-data/unit/check-typevar-tuple.test
+++ b/test-data/unit/check-typevar-tuple.test
@@ -2628,3 +2628,87 @@ def fn(f: Callable[[*tuple[T]], int]) -> Callable[[*tuple[T]], int]: ...
def test(*args: Unpack[tuple[T]]) -> int: ...
reveal_type(fn(test)) # N: Revealed type is "def [T] (T`1) -> builtins.int"
[builtins fixtures/tuple.pyi]
+
+[case testAnyTuple1]
+from typing import Any
+
+Ge0 = tuple[Any, ...]
+
+def ge0_to_0(shape: Ge0) -> tuple[()]:
+ return shape
+def ge0_to_1(shape: Ge0) -> tuple[int]:
+ return shape
+def ge0_to_2(shape: Ge0) -> tuple[int, int]:
+ return shape
+
+
+Ge1 = tuple[int, *tuple[Any, ...]]
+
+def ge1_to_0(shape: Ge1) -> tuple[()]:
+ return shape # E: Incompatible return value type (got "Tuple[int, Unpack[Tuple[Any, ...]]]", expected "Tuple[()]")
+def ge1_to_1(shape: Ge1) -> tuple[int]:
+ return shape
+def ge1_to_2(shape: Ge1) -> tuple[int, int]:
+ return shape
+
+Ge2 = tuple[*tuple[Any, ...], int]
+
+def ge2_to_0(shape: Ge2) -> tuple[()]:
+ return shape # E: Incompatible return value type (got "Tuple[Unpack[Tuple[Any, ...]], int]", expected "Tuple[()]")
+def ge2_to_1(shape: Ge2) -> tuple[int]:
+ return shape
+def ge2_to_2(shape: Ge2) -> tuple[int, int]:
+ return shape
+
+Ge3 = tuple[int, *tuple[Any, ...], str]
+
+def ge3_to_0(shape: Ge3) -> tuple[()]:
+ return shape # E: Incompatible return value type (got "Tuple[int, Unpack[Tuple[Any, ...]], str]", expected "Tuple[()]")
+def ge3_to_1(shape: Ge3) -> tuple[int]:
+ return shape # E: Incompatible return value type (got "Tuple[int, Unpack[Tuple[Any, ...]], str]", expected "Tuple[int]")
+def ge3_to_2(shape: Ge3) -> tuple[int, int]:
+ return shape # E: Incompatible return value type (got "Tuple[int, Unpack[Tuple[Any, ...]], str]", expected "Tuple[int, int]")
+def ge3_to_22(shape: Ge3) -> tuple[int, int, str]:
+ return shape
+[builtins fixtures/tuple.pyi]
+
+[case testAnyTuple2]
+Ge0 = tuple[int, ...]
+
+def ge0_to_0(shape: Ge0) -> tuple[()]:
+ return shape # E: Incompatible return value type (got "Tuple[int, ...]", expected "Tuple[()]")
+def ge0_to_1(shape: Ge0) -> tuple[int]:
+ return shape # E: Incompatible return value type (got "Tuple[int, ...]", expected "Tuple[int]")
+def ge0_to_2(shape: Ge0) -> tuple[int, int]:
+ return shape # E: Incompatible return value type (got "Tuple[int, ...]", expected "Tuple[int, int]")
+
+
+Ge1 = tuple[int, *tuple[int, ...]]
+
+def ge1_to_0(shape: Ge1) -> tuple[()]:
+ return shape # E: Incompatible return value type (got "Tuple[int, Unpack[Tuple[int, ...]]]", expected "Tuple[()]")
+def ge1_to_1(shape: Ge1) -> tuple[int]:
+ return shape # E: Incompatible return value type (got "Tuple[int, Unpack[Tuple[int, ...]]]", expected "Tuple[int]")
+def ge1_to_2(shape: Ge1) -> tuple[int, int]:
+ return shape # E: Incompatible return value type (got "Tuple[int, Unpack[Tuple[int, ...]]]", expected "Tuple[int, int]")
+
+Ge2 = tuple[*tuple[int, ...], int]
+
+def ge2_to_0(shape: Ge2) -> tuple[()]:
+ return shape # E: Incompatible return value type (got "Tuple[Unpack[Tuple[int, ...]], int]", expected "Tuple[()]")
+def ge2_to_1(shape: Ge2) -> tuple[int]:
+ return shape # E: Incompatible return value type (got "Tuple[Unpack[Tuple[int, ...]], int]", expected "Tuple[int]")
+def ge2_to_2(shape: Ge2) -> tuple[int, int]:
+ return shape # E: Incompatible return value type (got "Tuple[Unpack[Tuple[int, ...]], int]", expected "Tuple[int, int]")
+
+Ge3 = tuple[int, *tuple[int, ...], str]
+
+def ge3_to_0(shape: Ge3) -> tuple[()]:
+ return shape # E: Incompatible return value type (got "Tuple[int, Unpack[Tuple[int, ...]], str]", expected "Tuple[()]")
+def ge3_to_1(shape: Ge3) -> tuple[int]:
+ return shape # E: Incompatible return value type (got "Tuple[int, Unpack[Tuple[int, ...]], str]", expected "Tuple[int]")
+def ge3_to_2(shape: Ge3) -> tuple[int, int]:
+ return shape # E: Incompatible return value type (got "Tuple[int, Unpack[Tuple[int, ...]], str]", expected "Tuple[int, int]")
+def ge3_to_22(shape: Ge3) -> tuple[int, int, str]:
+ return shape # E: Incompatible return value type (got "Tuple[int, Unpack[Tuple[int, ...]], str]", expected "Tuple[int, int, str]")
+[builtins fixtures/tuple.pyi]
|
Oof yea that's no simple problem... My knowledge about the mypy codebase is very limited, so I'm not sure how much I could be of help here, given the complexity and all 😅. It might help reduce the complexity if you exploit the fact that there can only be one nested
Then the algo could look roughly like this
This doesn't include the special treatment that For what it's worth, I just posted https://discuss.python.org/t/unbounded-tuple-unions/92472, which might be something to take into consideration here as well, as there might be some overlap. And thanks a lot @sterliakov for looking into this, and so quickly too; I certainly appreciate it! |
Uh oh!
There was an error while loading. Please reload this page.
From the typing spec:
And mypy does precisely this:
Ok, so far so good.
The typing spec also says the following:
So
tuple[int, *tuple[Any, ...]]
is assignable to any tuple withint
as first element. But mypy behaves differently:full output:
So it's only the last two errors that are false positives.
mypy-play
Some observations:
master
branch.tuple[Any, *tuple[Any, ...]]
and/or useAny
in the return types.The text was updated successfully, but these errors were encountered: