Skip to content

Commit 568b945

Browse files
authored
Fix crash on recursive tuple with Hashable (#20232)
Fixes #20227 This is huge pain to reproduce, so I added just _some_ repro as a test. Note the change in `typeops.py` is not required for this fix, but it is a technically correct thing to do that I noticed while looking at this.
1 parent 75592ff commit 568b945

File tree

4 files changed

+22
-5
lines changed

4 files changed

+22
-5
lines changed

mypy/typeops.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -666,15 +666,15 @@ def _remove_redundant_union_items(items: list[Type], keep_erased: bool) -> list[
666666
else:
667667
# If not, check if we've seen a supertype of this type
668668
for j, tj in enumerate(new_items):
669-
tj = get_proper_type(tj)
669+
proper_tj = get_proper_type(tj)
670670
# If tj is an Instance with a last_known_value, do not remove proper_ti
671671
# (unless it's an instance with the same last_known_value)
672672
if (
673-
isinstance(tj, Instance)
674-
and tj.last_known_value is not None
673+
isinstance(proper_tj, Instance)
674+
and proper_tj.last_known_value is not None
675675
and not (
676676
isinstance(proper_ti, Instance)
677-
and tj.last_known_value == proper_ti.last_known_value
677+
and proper_tj.last_known_value == proper_ti.last_known_value
678678
)
679679
):
680680
continue

mypy/types.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4159,7 +4159,11 @@ def flatten_nested_unions(
41594159
tp = t
41604160
if isinstance(tp, ProperType) and isinstance(tp, UnionType):
41614161
flat_items.extend(
4162-
flatten_nested_unions(tp.items, handle_type_alias_type=handle_type_alias_type)
4162+
flatten_nested_unions(
4163+
tp.items,
4164+
handle_type_alias_type=handle_type_alias_type,
4165+
handle_recursive=handle_recursive,
4166+
)
41634167
)
41644168
else:
41654169
# Must preserve original aliases when possible.

test-data/unit/check-python312.test

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2192,3 +2192,15 @@ y3: A3[int] # E: Type argument "int" of "A3" must be a subtype of "B3"
21922192
z3: A3[None]
21932193
[builtins fixtures/tuple.pyi]
21942194
[typing fixtures/typing-full.pyi]
2195+
2196+
[case testPEP695TypeAliasRecursiveTupleUnionNoCrash]
2197+
from collections.abc import Hashable
2198+
2199+
type HashableArg = int | tuple[Hashable | HashableArg]
2200+
x: HashableArg
2201+
reveal_type(x) # N: Revealed type is "Union[builtins.int, tuple[Union[typing.Hashable, ...]]]"
2202+
if isinstance(x, tuple):
2203+
y, = x
2204+
reveal_type(y) # N: Revealed type is "Union[typing.Hashable, Union[builtins.int, tuple[Union[typing.Hashable, ...]]]]"
2205+
[builtins fixtures/tuple.pyi]
2206+
[typing fixtures/typing-full.pyi]

test-data/unit/fixtures/tuple.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class type:
1414
def __init__(self, *a: object) -> None: pass
1515
def __call__(self, *a: object) -> object: pass
1616
class tuple(Sequence[_Tco], Generic[_Tco]):
17+
def __hash__(self) -> int: ...
1718
def __new__(cls: Type[_T], iterable: Iterable[_Tco] = ...) -> _T: ...
1819
def __iter__(self) -> Iterator[_Tco]: pass
1920
def __contains__(self, item: object) -> bool: pass

0 commit comments

Comments
 (0)