Skip to content

Commit 6f54a6f

Browse files
authored
New semantic analyzer: fix recursive type aliases (#7091)
This fixes maximum iteration count errors. Recursive type aliases still don't work and the behavior is a regression from the old semantic analyzer. Previously they were truncated, but now they aren't supported at all. We could perhaps hack something similar to what we used to have, but I don't think that it's urgent. This also breaks some use cases where "semi-recursive" type aliases were supported previously. The support mechanism caused infinite expansion for actual recursive type aliases. I couldn't come up with a simple way to support both, so I decided to prioritize fixing infinite expansion, as they have a worse impact on user experience. Work towards #6445.
1 parent 68e37c5 commit 6f54a6f

File tree

4 files changed

+54
-37
lines changed

4 files changed

+54
-37
lines changed

mypy/newsemanal/semanal.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2347,7 +2347,9 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
23472347
self.analyze_alias(rvalue, allow_placeholder=True)
23482348
if not res:
23492349
return False
2350-
if self.found_incomplete_ref(tag) or isinstance(res, PlaceholderType):
2350+
# TODO: Maybe we only need to reject top-level placeholders, similar
2351+
# to base classes.
2352+
if self.found_incomplete_ref(tag) or has_placeholder(res):
23512353
# Since we have got here, we know this must be a type alias (incomplete refs
23522354
# may appear in nested positions), therefore use becomes_typeinfo=True.
23532355
placeholder = PlaceholderNode(self.qualified_name(lvalue.name),
@@ -2381,9 +2383,6 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
23812383
alias_tvars=alias_tvars, no_args=no_args)
23822384
if existing:
23832385
# An alias gets updated.
2384-
if self.final_iteration:
2385-
self.cannot_resolve_name(lvalue.name, 'name', s)
2386-
return True
23872386
updated = False
23882387
if isinstance(existing.node, TypeAlias):
23892388
if existing.node.target != res:
@@ -2397,7 +2396,10 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
23972396
# Otherwise just replace existing placeholder with type alias.
23982397
existing.node = alias_node
23992398
updated = True
2400-
if updated:
2399+
if self.final_iteration:
2400+
self.cannot_resolve_name(lvalue.name, 'name', s)
2401+
return True
2402+
elif updated:
24012403
self.progress = True
24022404
# We need to defer so that this change can get propagated to base classes.
24032405
self.defer()

test-data/unit/check-incremental.test

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2469,10 +2469,10 @@ x: int = C()[0][0]
24692469
from typing import List, Generic, TypeVar, NamedTuple
24702470
T = TypeVar('T')
24712471

2472-
class C(A, B): #type: ignore
2472+
class C(A, B): # type: ignore
24732473
pass
24742474
class G(Generic[T]): pass
2475-
A = G[C]
2475+
A = G[C] # type: ignore
24762476
class B(NamedTuple):
24772477
x: int
24782478

@@ -2482,7 +2482,7 @@ C(1)[0]
24822482
[out]
24832483

24842484
[case testSerializeRecursiveAliases1]
2485-
# flags: --no-new-semantic-analyzer
2485+
# flags: --new-semantic-analyzer
24862486
from typing import Type, Callable, Union
24872487

24882488
A = Union[A, int] # type: ignore
@@ -2491,7 +2491,7 @@ C = Type[C] # type: ignore
24912491
[out]
24922492

24932493
[case testSerializeRecursiveAliases2]
2494-
# flags: --no-new-semantic-analyzer
2494+
# flags: --new-semantic-analyzer
24952495
from typing import Type, Callable, Union
24962496

24972497
A = Union[B, int] # type: ignore
@@ -2500,7 +2500,7 @@ C = Type[A] # type: ignore
25002500
[out]
25012501

25022502
[case testSerializeRecursiveAliases3]
2503-
# flags: --no-new-semantic-analyzer
2503+
# flags: --new-semantic-analyzer
25042504
from typing import Type, Callable, Union, NamedTuple
25052505

25062506
A = Union[B, int] # type: ignore
@@ -4514,15 +4514,12 @@ from lib import A
45144514
B = List[A]
45154515
[builtins fixtures/list.pyi]
45164516
[out]
4517-
tmp/other.pyi:2: error: Module 'lib' has no attribute 'A'
45184517
tmp/other.pyi:3: error: Cannot resolve name "B" (possible cyclic definition)
45194518
tmp/lib.pyi:2: error: Module 'other' has no attribute 'B'
45204519
[out2]
4521-
tmp/other.pyi:2: error: Module 'lib' has no attribute 'A'
45224520
tmp/other.pyi:3: error: Cannot resolve name "B" (possible cyclic definition)
45234521
tmp/lib.pyi:2: error: Module 'other' has no attribute 'B'
4524-
tmp/a.py:2: error: Cannot resolve name "lib.A" (possible cyclic definition)
4525-
tmp/a.py:3: note: Revealed type is 'Any'
4522+
tmp/a.py:3: note: Revealed type is 'builtins.list[Any]'
45264523

45274524
[case testRecursiveNamedTupleTypedDict]
45284525
# flags: --no-new-semantic-analyzer

test-data/unit/check-newsemanal.test

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1383,9 +1383,15 @@ x: B
13831383
B = List[C]
13841384
class C(B): pass
13851385

1386-
reveal_type(x) # N: Revealed type is 'builtins.list[__main__.C]'
1387-
reveal_type(x[0][0]) # N: Revealed type is '__main__.C*'
1386+
reveal_type(x)
1387+
reveal_type(x[0][0])
13881388
[builtins fixtures/list.pyi]
1389+
[out]
1390+
main:3: error: Cannot resolve name "B" (possible cyclic definition)
1391+
main:4: error: Cannot resolve name "B" (possible cyclic definition)
1392+
main:4: error: Cannot resolve name "C" (possible cyclic definition)
1393+
main:7: note: Revealed type is 'Any'
1394+
main:8: note: Revealed type is 'Any'
13891395

13901396
[case testNewAnalyzerAliasToNotReadyTwoDeferralsFunction]
13911397
import a
@@ -1410,14 +1416,18 @@ from typing import List
14101416
from b import D
14111417

14121418
def f(x: B) -> List[B]: ...
1413-
B = List[C]
1419+
B = List[C] # E
14141420
class C(B): pass
14151421

14161422
[file b.py]
14171423
from a import f
14181424
class D: ...
1419-
reveal_type(f) # N: Revealed type is 'def (x: builtins.list[a.C]) -> builtins.list[builtins.list[a.C]]'
1425+
reveal_type(f) # N
14201426
[builtins fixtures/list.pyi]
1427+
[out]
1428+
tmp/b.py:3: note: Revealed type is 'def (x: builtins.list[Any]) -> builtins.list[builtins.list[Any]]'
1429+
tmp/a.py:5: error: Cannot resolve name "B" (possible cyclic definition)
1430+
tmp/a.py:5: error: Cannot resolve name "C" (possible cyclic definition)
14211431

14221432
[case testNewAnalyzerAliasToNotReadyMixed]
14231433
from typing import List, Union
@@ -1947,14 +1957,21 @@ class B(List[C]):
19471957
from typing import NewType, List
19481958

19491959
x: D
1950-
reveal_type(x[0][0]) # N: Revealed type is '__main__.C*'
1960+
reveal_type(x[0][0])
19511961

19521962
D = List[C]
19531963
C = NewType('C', B)
19541964

19551965
class B(D):
19561966
pass
19571967
[builtins fixtures/list.pyi]
1968+
[out]
1969+
main:3: error: Cannot resolve name "D" (possible cyclic definition)
1970+
main:4: note: Revealed type is 'Any'
1971+
main:6: error: Cannot resolve name "D" (possible cyclic definition)
1972+
main:6: error: Cannot resolve name "C" (possible cyclic definition)
1973+
main:7: error: Argument 2 to NewType(...) must be a valid type
1974+
main:7: error: Cannot resolve name "B" (possible cyclic definition)
19581975

19591976
-- Copied from check-classes.test (tricky corner cases).
19601977
[case testNewAnalyzerNoCrashForwardRefToBrokenDoubleNewTypeClass]
@@ -1974,10 +1991,10 @@ class C:
19741991
from typing import List, Generic, TypeVar, NamedTuple
19751992
T = TypeVar('T')
19761993

1977-
class C(A, B):
1994+
class C(A, B): # E: Cannot resolve name "A" (possible cyclic definition)
19781995
pass
19791996
class G(Generic[T]): pass
1980-
A = G[C]
1997+
A = G[C] # E: Cannot resolve name "A" (possible cyclic definition)
19811998
class B(NamedTuple):
19821999
x: int
19832000

test-data/unit/check-type-aliases.test

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -178,30 +178,31 @@ Alias = Tuple[int, T]
178178
[out]
179179

180180
[case testRecursiveAliasesErrors1]
181-
# flags: --no-new-semantic-analyzer
181+
# flags: --new-semantic-analyzer
182182
# Recursive aliases are not supported yet.
183183
from typing import Type, Callable, Union
184184

185-
A = Union[A, int]
186-
B = Callable[[B], int]
187-
C = Type[C]
188-
[out]
189-
main:5: error: Recursive types not fully supported yet, nested types replaced with "Any"
190-
main:6: error: Recursive types not fully supported yet, nested types replaced with "Any"
191-
main:7: error: Recursive types not fully supported yet, nested types replaced with "Any"
185+
A = Union[A, int] # E: Cannot resolve name "A" (possible cyclic definition)
186+
B = Callable[[B], int] # E: Cannot resolve name "B" (possible cyclic definition)
187+
C = Type[C] # E: Cannot resolve name "C" (possible cyclic definition)
192188

193189
[case testRecursiveAliasesErrors2]
194-
# flags: --no-new-semantic-analyzer
190+
# flags: --new-semantic-analyzer
195191
# Recursive aliases are not supported yet.
196192
from typing import Type, Callable, Union
197193

198194
A = Union[B, int]
199195
B = Callable[[C], int]
200196
C = Type[A]
197+
x: A
198+
reveal_type(x)
201199
[out]
202-
main:5: error: Recursive types not fully supported yet, nested types replaced with "Any"
203-
main:6: error: Recursive types not fully supported yet, nested types replaced with "Any"
204-
main:7: error: Recursive types not fully supported yet, nested types replaced with "Any"
200+
main:5: error: Cannot resolve name "A" (possible cyclic definition)
201+
main:5: error: Cannot resolve name "B" (possible cyclic definition)
202+
main:6: error: Cannot resolve name "B" (possible cyclic definition)
203+
main:6: error: Cannot resolve name "C" (possible cyclic definition)
204+
main:7: error: Cannot resolve name "C" (possible cyclic definition)
205+
main:9: note: Revealed type is 'Union[Any, builtins.int]'
205206

206207
[case testDoubleForwardAlias]
207208
from typing import List
@@ -223,12 +224,12 @@ reveal_type(x[0].x) # N: Revealed type is 'builtins.str'
223224
[out]
224225

225226
[case testJSONAliasApproximation]
226-
# flags: --no-new-semantic-analyzer
227+
# flags: --new-semantic-analyzer
227228
# Recursive aliases are not supported yet.
228229
from typing import List, Union, Dict
229-
x: JSON
230-
JSON = Union[int, str, List[JSON], Dict[str, JSON]] # type: ignore
231-
reveal_type(x) # N: Revealed type is 'Union[builtins.int, builtins.str, builtins.list[Any], builtins.dict[builtins.str, Any]]'
230+
x: JSON # E: Cannot resolve name "JSON" (possible cyclic definition)
231+
JSON = Union[int, str, List[JSON], Dict[str, JSON]] # E: Cannot resolve name "JSON" (possible cyclic definition)
232+
reveal_type(x) # N: Revealed type is 'Any'
232233
if isinstance(x, list):
233234
reveal_type(x) # N: Revealed type is 'builtins.list[Any]'
234235
[builtins fixtures/isinstancelist.pyi]

0 commit comments

Comments
 (0)