Skip to content

Fix overlap check for variadic generics #18638

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

Merged
merged 1 commit into from
Feb 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions mypy/meet.py
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,27 @@ def _type_object_overlap(left: Type, right: Type) -> bool:
else:
return False

if len(left.args) == len(right.args):
if right.type.has_type_var_tuple_type:
# Similar to subtyping, we delegate the heavy lifting to the tuple overlap.
assert right.type.type_var_tuple_prefix is not None
assert right.type.type_var_tuple_suffix is not None
prefix = right.type.type_var_tuple_prefix
suffix = right.type.type_var_tuple_suffix
tvt = right.type.defn.type_vars[prefix]
assert isinstance(tvt, TypeVarTupleType)
fallback = tvt.tuple_fallback
left_prefix, left_middle, left_suffix = split_with_prefix_and_suffix(
left.args, prefix, suffix
)
right_prefix, right_middle, right_suffix = split_with_prefix_and_suffix(
right.args, prefix, suffix
)
left_args = left_prefix + (TupleType(list(left_middle), fallback),) + left_suffix
right_args = right_prefix + (TupleType(list(right_middle), fallback),) + right_suffix
else:
left_args = left.args
right_args = right.args
if len(left_args) == len(right_args):
# Note: we don't really care about variance here, since the overlapping check
# is symmetric and since we want to return 'True' even for partial overlaps.
#
Expand All @@ -570,7 +590,7 @@ def _type_object_overlap(left: Type, right: Type) -> bool:
# to contain only instances of B at runtime.
if all(
_is_overlapping_types(left_arg, right_arg)
for left_arg, right_arg in zip(left.args, right.args)
for left_arg, right_arg in zip(left_args, right_args)
):
return True

Expand Down
26 changes: 26 additions & 0 deletions test-data/unit/check-typevar-tuple.test
Original file line number Diff line number Diff line change
Expand Up @@ -2487,3 +2487,29 @@ class C(Generic[P, R]):
c: C[int, str] # E: Can only replace ParamSpec with a parameter types list or another ParamSpec, got "int"
reveal_type(c.fn) # N: Revealed type is "def (*Any, **Any)"
[builtins fixtures/tuple.pyi]

[case testTypeVarTupleInstanceOverlap]
# flags: --strict-equality
from typing import TypeVarTuple, Unpack, Generic

Ts = TypeVarTuple("Ts")

class Foo(Generic[Unpack[Ts]]):
pass

x1: Foo[Unpack[tuple[int, ...]]]
y1: Foo[Unpack[tuple[str, ...]]]
x1 is y1 # E: Non-overlapping identity check (left operand type: "Foo[Unpack[Tuple[int, ...]]]", right operand type: "Foo[Unpack[Tuple[str, ...]]]")

x2: Foo[Unpack[tuple[int, ...]]]
y2: Foo[Unpack[tuple[int, ...]]]
x2 is y2

x3: Foo[Unpack[tuple[int, ...]]]
y3: Foo[Unpack[tuple[int, int]]]
x3 is y3

x4: Foo[Unpack[tuple[str, ...]]]
y4: Foo[Unpack[tuple[int, int]]]
x4 is y4 # E: Non-overlapping identity check (left operand type: "Foo[Unpack[Tuple[str, ...]]]", right operand type: "Foo[int, int]")
[builtins fixtures/tuple.pyi]