Skip to content

Commit a665335

Browse files
ilevkivskyiIvan Levkivskyi
andcommitted
Fix crash on join of recursive types (#11433)
The initial implementation was not very careful, so I only keep the covariant part, while other cases fall back to old simple logic (which is actually correct for invariant type parameters). Co-authored-by: Ivan Levkivskyi <[email protected]>
1 parent e4c52ee commit a665335

File tree

3 files changed

+36
-13
lines changed

3 files changed

+36
-13
lines changed

mypy/join.py

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
from mypy.nodes import INVARIANT, COVARIANT, CONTRAVARIANT
1818
import mypy.typeops
1919
from mypy import state
20-
from mypy import meet
2120

2221

2322
class InstanceJoiner:
@@ -55,18 +54,15 @@ def join_instances(self, t: Instance, s: Instance) -> ProperType:
5554
if not is_subtype(new_type, type_var.upper_bound):
5655
self.seen_instances.pop()
5756
return object_from_instance(t)
58-
elif type_var.variance == CONTRAVARIANT:
59-
new_type = meet.meet_types(ta, sa)
60-
if len(type_var.values) != 0 and new_type not in type_var.values:
61-
self.seen_instances.pop()
62-
return object_from_instance(t)
63-
# No need to check subtype, as ta and sa already have to be subtypes of
64-
# upper_bound
65-
elif type_var.variance == INVARIANT:
66-
new_type = join_types(ta, sa)
57+
# TODO: contravariant case should use meet but pass seen instances as
58+
# an argument to keep track of recursive checks.
59+
elif type_var.variance in (INVARIANT, CONTRAVARIANT):
6760
if not is_equivalent(ta, sa):
6861
self.seen_instances.pop()
6962
return object_from_instance(t)
63+
# If the types are different but equivalent, then an Any is involved
64+
# so using a join in the contravariant case is also OK.
65+
new_type = join_types(ta, sa, self)
7066
assert new_type is not None
7167
args.append(new_type)
7268
result: ProperType = Instance(t.type, args)

mypy/test/testtypes.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -653,8 +653,8 @@ def test_generics_covariant(self) -> None:
653653

654654
def test_generics_contravariant(self) -> None:
655655
self.assert_join(self.fx_contra.ga, self.fx_contra.ga, self.fx_contra.ga)
656-
self.assert_join(self.fx_contra.ga, self.fx_contra.gb, self.fx_contra.gb)
657-
self.assert_join(self.fx_contra.ga, self.fx_contra.gd, self.fx_contra.gn)
656+
# TODO: this can be more precise than "object", see a comment in mypy/join.py
657+
self.assert_join(self.fx_contra.ga, self.fx_contra.gb, self.fx_contra.o)
658658
self.assert_join(self.fx_contra.ga, self.fx_contra.g2a, self.fx_contra.o)
659659

660660
def test_generics_with_multiple_args(self) -> None:

test-data/unit/check-generics.test

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2463,7 +2463,8 @@ class B(A): ...
24632463
a_c: Container[A]
24642464
b_c: Container[B]
24652465

2466-
reveal_type([a_c, b_c]) # N: Revealed type is "builtins.list[__main__.Container*[__main__.B]]"
2466+
# TODO: this can be more precise than "object", see a comment in mypy/join.py
2467+
reveal_type([a_c, b_c]) # N: Revealed type is "builtins.list[builtins.object*]"
24672468
[builtins fixtures/list.pyi]
24682469

24692470
[case testGenericJoinRecursiveTypes]
@@ -2477,3 +2478,29 @@ b: B
24772478

24782479
reveal_type([a, b]) # N: Revealed type is "builtins.list[typing.Sequence*[builtins.object]]"
24792480
[builtins fixtures/list.pyi]
2481+
2482+
[case testGenericJoinRecursiveInvariant]
2483+
from typing import Generic, TypeVar
2484+
2485+
T = TypeVar("T")
2486+
class I(Generic[T]): ...
2487+
2488+
class A(I[A]): ...
2489+
class B(I[B]): ...
2490+
2491+
a: A
2492+
b: B
2493+
reveal_type([a, b]) # N: Revealed type is "builtins.list[builtins.object*]"
2494+
[builtins fixtures/list.pyi]
2495+
2496+
[case testGenericJoinNestedInvariantAny]
2497+
from typing import Any, Generic, TypeVar
2498+
2499+
T = TypeVar("T")
2500+
class I(Generic[T]): ...
2501+
2502+
a: I[I[int]]
2503+
b: I[I[Any]]
2504+
reveal_type([a, b]) # N: Revealed type is "builtins.list[__main__.I*[__main__.I[Any]]]"
2505+
reveal_type([b, a]) # N: Revealed type is "builtins.list[__main__.I*[__main__.I[Any]]]"
2506+
[builtins fixtures/list.pyi]

0 commit comments

Comments
 (0)