Skip to content

Commit 49ccd26

Browse files
authored
Fix crashes on variadic unpacking in synthetic types (#21555)
Fixes #21237 (and similar crashes). The problem is that we cannot do some of the type argument normalization early in `typeanal.py` (since that will cause crashes on some pathological recursive aliases), so we do it in `semanal_typeargs.py` later. However, the mixed traverser visitor is somewhat incomplete (most notably w.r.t. generated methods). I add (most of) the missing parts.
1 parent 94838b0 commit 49ccd26

3 files changed

Lines changed: 56 additions & 12 deletions

File tree

mypy/mixedtraverser.py

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
TypeApplication,
1717
TypedDictExpr,
1818
TypeFormExpr,
19+
TypeInfo,
1920
TypeVarExpr,
2021
Var,
2122
WithStmt,
@@ -41,15 +42,29 @@ def visit_func(self, o: FuncItem, /) -> None:
4142
self.visit_optional_type(o.type)
4243

4344
def visit_class_def(self, o: ClassDef, /) -> None:
44-
# TODO: Should we visit generated methods/variables as well, either here or in
45-
# TraverserVisitor?
4645
super().visit_class_def(o)
47-
info = o.info
48-
if info:
49-
for base in info.bases:
50-
base.accept(self)
51-
if info.special_alias:
52-
info.special_alias.accept(self)
46+
if o.info:
47+
self.process_type_info(o.info)
48+
49+
def process_type_info(self, info: TypeInfo) -> None:
50+
# TODO: Should we visit generated methods/variables as well?
51+
# We should for methods generated by us (see below). But it is less clear for
52+
# 3rd party plugin generated methods (since we don't want to emit errors there).
53+
for base in info.bases:
54+
base.accept(self)
55+
if info.special_alias:
56+
# We need to accept all types that are conceptually identical like special
57+
# alias target and corresponding tuple_type or typeddict_type, since those
58+
# may be copies, and not the same object.
59+
info.special_alias.accept(self)
60+
if info.tuple_type:
61+
info.tuple_type.accept(self)
62+
if info.typeddict_type:
63+
info.typeddict_type.accept(self)
64+
if info.is_named_tuple or info.is_newtype:
65+
for sym in info.names.values():
66+
if sym.plugin_generated and sym.node:
67+
sym.node.accept(self)
5368

5469
def visit_type_alias_expr(self, o: TypeAliasExpr, /) -> None:
5570
super().visit_type_alias_expr(o)
@@ -64,19 +79,20 @@ def visit_type_var_expr(self, o: TypeVarExpr, /) -> None:
6479

6580
def visit_typeddict_expr(self, o: TypedDictExpr, /) -> None:
6681
super().visit_typeddict_expr(o)
67-
self.visit_optional_type(o.info.typeddict_type)
82+
self.process_type_info(o.info)
6883

6984
def visit_namedtuple_expr(self, o: NamedTupleExpr, /) -> None:
7085
super().visit_namedtuple_expr(o)
71-
assert o.info.tuple_type
72-
o.info.tuple_type.accept(self)
86+
self.process_type_info(o.info)
7387

7488
def visit__promote_expr(self, o: PromoteExpr, /) -> None:
7589
super().visit__promote_expr(o)
7690
o.type.accept(self)
7791

7892
def visit_newtype_expr(self, o: NewTypeExpr, /) -> None:
7993
super().visit_newtype_expr(o)
94+
if o.info:
95+
self.process_type_info(o.info)
8096
self.visit_optional_type(o.old_type)
8197

8298
# Statements

mypy/typetraverser.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,11 @@ def visit_instance(self, t: Instance, /) -> None:
8282
self.traverse_type_tuple(t.args)
8383

8484
def visit_callable_type(self, t: CallableType, /) -> None:
85-
# FIX generics
85+
for tv in t.variables:
86+
tv.upper_bound.accept(self)
87+
if isinstance(tv, TypeVarType):
88+
for v in tv.values:
89+
v.accept(self)
8690
self.traverse_type_list(t.arg_types)
8791
t.ret_type.accept(self)
8892
t.fallback.accept(self)

test-data/unit/check-python312.test

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2293,3 +2293,27 @@ reveal_type(x) # N: Revealed type is "__main__.C[()]"
22932293
y: T[bool]
22942294
reveal_type(y) # N: Revealed type is "__main__.C[()]"
22952295
[builtins fixtures/tuple.pyi]
2296+
2297+
[case testTupleBaseTupleTypeUnpack]
2298+
class X(tuple[*tuple[int], *tuple[int]]):
2299+
pass
2300+
x = X((1, 2))
2301+
reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int, fallback=__main__.X]"
2302+
[builtins fixtures/tuple.pyi]
2303+
2304+
[case testNewTypeTupleTypeUnpack]
2305+
from typing import NewType
2306+
2307+
T = NewType("T", tuple[*tuple[int], *tuple[int]])
2308+
t = T((1, 2))
2309+
reveal_type(t) # N: Revealed type is "tuple[builtins.int, builtins.int, fallback=__main__.T]"
2310+
[builtins fixtures/tuple.pyi]
2311+
2312+
[case testNamedTupleTupleTypeUnpack]
2313+
from typing import NamedTuple
2314+
2315+
class N(NamedTuple):
2316+
x: tuple[*tuple[int], *tuple[int]]
2317+
n = N((1, 2))
2318+
reveal_type(n) # N: Revealed type is "tuple[tuple[builtins.int, builtins.int], fallback=__main__.N]"
2319+
[builtins fixtures/tuple.pyi]

0 commit comments

Comments
 (0)