Skip to content

Commit 29ffa3e

Browse files
authored
Fix overlap check for variadic generics (#18638)
Fixes #18105 When I implemented this initially, I only handled tuples, but forgot instances, thus causing the crash. I don't add many tests, since the instance overlap check simply relays to the tuple one, that is already tested. (Btw I already forgot how verbose everything is in the `TypeVarTuple` world :-))
1 parent 876f636 commit 29ffa3e

File tree

2 files changed

+48
-2
lines changed

2 files changed

+48
-2
lines changed

mypy/meet.py

+22-2
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,27 @@ def _type_object_overlap(left: Type, right: Type) -> bool:
553553
else:
554554
return False
555555

556-
if len(left.args) == len(right.args):
556+
if right.type.has_type_var_tuple_type:
557+
# Similar to subtyping, we delegate the heavy lifting to the tuple overlap.
558+
assert right.type.type_var_tuple_prefix is not None
559+
assert right.type.type_var_tuple_suffix is not None
560+
prefix = right.type.type_var_tuple_prefix
561+
suffix = right.type.type_var_tuple_suffix
562+
tvt = right.type.defn.type_vars[prefix]
563+
assert isinstance(tvt, TypeVarTupleType)
564+
fallback = tvt.tuple_fallback
565+
left_prefix, left_middle, left_suffix = split_with_prefix_and_suffix(
566+
left.args, prefix, suffix
567+
)
568+
right_prefix, right_middle, right_suffix = split_with_prefix_and_suffix(
569+
right.args, prefix, suffix
570+
)
571+
left_args = left_prefix + (TupleType(list(left_middle), fallback),) + left_suffix
572+
right_args = right_prefix + (TupleType(list(right_middle), fallback),) + right_suffix
573+
else:
574+
left_args = left.args
575+
right_args = right.args
576+
if len(left_args) == len(right_args):
557577
# Note: we don't really care about variance here, since the overlapping check
558578
# is symmetric and since we want to return 'True' even for partial overlaps.
559579
#
@@ -570,7 +590,7 @@ def _type_object_overlap(left: Type, right: Type) -> bool:
570590
# to contain only instances of B at runtime.
571591
if all(
572592
_is_overlapping_types(left_arg, right_arg)
573-
for left_arg, right_arg in zip(left.args, right.args)
593+
for left_arg, right_arg in zip(left_args, right_args)
574594
):
575595
return True
576596

test-data/unit/check-typevar-tuple.test

+26
Original file line numberDiff line numberDiff line change
@@ -2487,3 +2487,29 @@ class C(Generic[P, R]):
24872487
c: C[int, str] # E: Can only replace ParamSpec with a parameter types list or another ParamSpec, got "int"
24882488
reveal_type(c.fn) # N: Revealed type is "def (*Any, **Any)"
24892489
[builtins fixtures/tuple.pyi]
2490+
2491+
[case testTypeVarTupleInstanceOverlap]
2492+
# flags: --strict-equality
2493+
from typing import TypeVarTuple, Unpack, Generic
2494+
2495+
Ts = TypeVarTuple("Ts")
2496+
2497+
class Foo(Generic[Unpack[Ts]]):
2498+
pass
2499+
2500+
x1: Foo[Unpack[tuple[int, ...]]]
2501+
y1: Foo[Unpack[tuple[str, ...]]]
2502+
x1 is y1 # E: Non-overlapping identity check (left operand type: "Foo[Unpack[Tuple[int, ...]]]", right operand type: "Foo[Unpack[Tuple[str, ...]]]")
2503+
2504+
x2: Foo[Unpack[tuple[int, ...]]]
2505+
y2: Foo[Unpack[tuple[int, ...]]]
2506+
x2 is y2
2507+
2508+
x3: Foo[Unpack[tuple[int, ...]]]
2509+
y3: Foo[Unpack[tuple[int, int]]]
2510+
x3 is y3
2511+
2512+
x4: Foo[Unpack[tuple[str, ...]]]
2513+
y4: Foo[Unpack[tuple[int, int]]]
2514+
x4 is y4 # E: Non-overlapping identity check (left operand type: "Foo[Unpack[Tuple[str, ...]]]", right operand type: "Foo[int, int]")
2515+
[builtins fixtures/tuple.pyi]

0 commit comments

Comments
 (0)