Skip to content

Commit cc3b54b

Browse files
authored
New analyzer: fix issues with order-dependent results (#7158)
When we add a placeholder, we should do it through `mark_incomplete`, since the name needs to added to the set of missing names. Otherwise a later definition can take precedence over an earlier definition, if the first definition initially creates a placeholder. Fixes #7157 and probably another issue related to type aliases, which was exposed by the fix.
1 parent e1f113a commit cc3b54b

File tree

3 files changed

+114
-24
lines changed

3 files changed

+114
-24
lines changed

mypy/newsemanal/semanal.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,10 @@ class NewSemanticAnalyzer(NodeVisitor[None],
203203
# Note that missing names are per module, _not_ per namespace. This means that e.g.
204204
# a missing name at global scope will block adding same name at a class scope.
205205
# This should not affect correctness and is purely a performance issue,
206-
# since it can cause unnecessary deferrals.
206+
# since it can cause unnecessary deferrals. These are represented as
207+
# PlaceholderNodes in the symbol table. We use this to ensure that the first
208+
# definition takes precedence even if it's incomplete.
209+
#
207210
# Note that a star import adds a special name '*' to the set, this blocks
208211
# adding _any_ names in the current file.
209212
missing_names = None # type: Set[str]
@@ -1702,7 +1705,9 @@ def process_imported_symbol(self,
17021705
if self.final_iteration:
17031706
self.report_missing_module_attribute(module_id, id, imported_id, context)
17041707
return
1705-
self.record_incomplete_ref()
1708+
else:
1709+
# This might become a type.
1710+
self.mark_incomplete(imported_id, node.node, becomes_typeinfo=True)
17061711
existing_symbol = self.globals.get(imported_id)
17071712
if (existing_symbol and not isinstance(existing_symbol.node, PlaceholderNode) and
17081713
not isinstance(node.node, PlaceholderNode)):
@@ -2408,11 +2413,7 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
24082413
if self.found_incomplete_ref(tag) or has_placeholder(res):
24092414
# Since we have got here, we know this must be a type alias (incomplete refs
24102415
# may appear in nested positions), therefore use becomes_typeinfo=True.
2411-
placeholder = PlaceholderNode(self.qualified_name(lvalue.name),
2412-
rvalue,
2413-
s.line,
2414-
becomes_typeinfo=True)
2415-
self.add_symbol(lvalue.name, placeholder, s)
2416+
self.mark_incomplete(lvalue.name, rvalue, becomes_typeinfo=True)
24162417
return True
24172418
self.add_type_alias_deps(depends_on)
24182419
# In addition to the aliases used, we add deps on unbound
@@ -4139,6 +4140,9 @@ def add_symbol_table_node(self,
41394140
# need to do anything.
41404141
old = existing.node
41414142
new = symbol.node
4143+
if isinstance(new, PlaceholderNode):
4144+
# We don't know whether this is okay. Let's wait until the next iteration.
4145+
return False
41424146
if not is_same_symbol(old, new):
41434147
if isinstance(new, (FuncDef, Decorator, OverloadedFuncDef, TypeInfo)):
41444148
self.add_redefinition(names, name, symbol)
@@ -4306,7 +4310,7 @@ def mark_incomplete(self, name: str, node: Node,
43064310
self.defer(node)
43074311
if name == '*':
43084312
self.incomplete = True
4309-
elif name not in self.current_symbol_table() and not self.is_global_or_nonlocal(name):
4313+
elif not self.is_global_or_nonlocal(name):
43104314
fullname = self.qualified_name(name)
43114315
assert self.statement
43124316
placeholder = PlaceholderNode(fullname, node, self.statement.line,
@@ -4809,10 +4813,12 @@ def is_valid_replacement(old: SymbolTableNode, new: SymbolTableNode) -> bool:
48094813
2. Placeholder that isn't known to become type replaced with a
48104814
placeholder that can become a type
48114815
"""
4812-
return (isinstance(old.node, PlaceholderNode)
4813-
and (not isinstance(new.node, PlaceholderNode)
4814-
or (not old.node.becomes_typeinfo
4815-
and new.node.becomes_typeinfo)))
4816+
if isinstance(old.node, PlaceholderNode):
4817+
if isinstance(new.node, PlaceholderNode):
4818+
return not old.node.becomes_typeinfo and new.node.becomes_typeinfo
4819+
else:
4820+
return True
4821+
return False
48164822

48174823

48184824
def is_same_symbol(a: Optional[SymbolNode], b: Optional[SymbolNode]) -> bool:

test-data/unit/check-incremental.test

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4497,29 +4497,36 @@ tmp/a.py:3: note: Revealed type is 'builtins.list[builtins.list[builtins.list[An
44974497
[case testRecursiveAliasImported2]
44984498
# flags: --new-semantic-analyzer
44994499
import a
4500+
45004501
[file a.py]
45014502
import lib
45024503
x: int
4504+
45034505
[file a.py.2]
45044506
import lib
45054507
x: lib.A
45064508
reveal_type(x)
4509+
45074510
[file lib.pyi]
45084511
from typing import List
4509-
from other import B
4512+
MYPY = False
4513+
if MYPY: # Force processing order
4514+
from other import B
45104515
A = List[B] # type: ignore
4516+
45114517
[file other.pyi]
45124518
from typing import List
45134519
from lib import A
45144520
B = List[A]
4521+
45154522
[builtins fixtures/list.pyi]
45164523
[out]
4524+
tmp/other.pyi:2: error: Module 'lib' has no attribute 'A'
45174525
tmp/other.pyi:3: error: Cannot resolve name "B" (possible cyclic definition)
4518-
tmp/lib.pyi:2: error: Module 'other' has no attribute 'B'
45194526
[out2]
4527+
tmp/other.pyi:2: error: Module 'lib' has no attribute 'A'
45204528
tmp/other.pyi:3: error: Cannot resolve name "B" (possible cyclic definition)
4521-
tmp/lib.pyi:2: error: Module 'other' has no attribute 'B'
4522-
tmp/a.py:3: note: Revealed type is 'builtins.list[Any]'
4529+
tmp/a.py:3: note: Revealed type is 'builtins.list[builtins.list[Any]]'
45234530

45244531
[case testRecursiveNamedTupleTypedDict]
45254532
# https://github.com/python/mypy/issues/7125

test-data/unit/check-newsemanal.test

Lines changed: 85 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,33 @@ reveal_type(x) # N: Revealed type is 'TypedDict('__main__.T2', {'x': builtins.st
272272
y: T4
273273
reveal_type(y) # N: Revealed type is 'TypedDict('__main__.T4', {'x': builtins.str, 'y': __main__.A})'
274274

275-
[case testNewAnalyzerRedefinitionAndDeferral]
275+
[case testNewAnalyzerRedefinitionAndDeferral1a]
276+
import a
277+
278+
[file a.py]
279+
MYPY = False
280+
if MYPY:
281+
from b import x as y
282+
x = 0
283+
284+
def y(): pass # E: Name 'y' already defined on line 4
285+
reveal_type(y) # N: Revealed type is 'builtins.int'
286+
287+
y2 = y
288+
class y2: pass # E: Name 'y2' already defined on line 9
289+
reveal_type(y2) # N: Revealed type is 'builtins.int'
290+
291+
y3, y4 = y, y
292+
if MYPY: # Tweak processing order
293+
from b import f as y3 # E: Incompatible import of "y3" (imported name has type "Callable[[], Any]", local name has type "int")
294+
reveal_type(y3) # N: Revealed type is 'builtins.int'
295+
296+
[file b.py]
297+
from a import x
298+
299+
def f(): pass
300+
301+
[case testNewAnalyzerRedefinitionAndDeferral1b]
276302
import a
277303

278304
[file a.py]
@@ -291,11 +317,26 @@ from b import f as y3 # E: Incompatible import of "y3" (imported name has type "
291317
reveal_type(y3) # N: Revealed type is 'builtins.int'
292318

293319
[file b.py]
294-
from a import x
320+
MYPY = False
321+
if MYPY: # Tweak processing order
322+
from a import x
295323

296324
def f(): pass
297325

298-
[case testNewAnalyzerRedefinitionAndDeferral2]
326+
[case testNewAnalyzerRedefinitionAndDeferral2a]
327+
import a
328+
329+
[file a.py]
330+
MYPY = False
331+
if MYPY: # Tweak processing order
332+
from b import C as C2
333+
class C: pass
334+
class C2: pass # E: Name 'C2' already defined on line 4
335+
336+
[file b.py]
337+
from a import C
338+
339+
[case testNewAnalyzerRedefinitionAndDeferral2b]
299340
import a
300341

301342
[file a.py]
@@ -304,7 +345,9 @@ class C: pass
304345

305346
class C2: pass # E: Name 'C2' already defined on line 2
306347
[file b.py]
307-
from a import C
348+
MYPY = False
349+
if MYPY: # Tweak processing order
350+
from a import C
308351

309352
[case testNewAnalyzerRedefinitionAndDeferral3]
310353
import a
@@ -320,7 +363,7 @@ reveal_type(b) # N: Revealed type is 'Any'
320363
[file b.py]
321364
from a import f
322365

323-
[case testNewAnalyzerImportStarForwardRef]
366+
[case testNewAnalyzerImportStarForwardRef1]
324367
import a
325368

326369
[file a.py]
@@ -331,6 +374,25 @@ from b import *
331374

332375
class A: pass # E: Name 'A' already defined (possibly by an import)
333376

377+
[file b.py]
378+
class A: pass
379+
MYPY = False
380+
if MYPY: # Tweak processing order
381+
from a import x
382+
383+
[case testNewAnalyzerImportStarForwardRef2]
384+
import a
385+
386+
[file a.py]
387+
x: A
388+
reveal_type(x) # N: Revealed type is 'b.A'
389+
390+
MYPY = False
391+
if MYPY: # Tweak processing order
392+
from b import *
393+
394+
class A: pass # E: Name 'A' already defined (possibly by an import)
395+
334396
[file b.py]
335397
class A: pass
336398
from a import x
@@ -1005,19 +1067,34 @@ import a
10051067
class C(B): ...
10061068
class B: ...
10071069

1008-
[case testNewAnalyzerImportOverExistingInCycleStar]
1070+
[case testNewAnalyzerImportOverExistingInCycleStar1]
10091071
import a
10101072
[file a.py]
10111073
C = 1
1012-
from b import * # E: Name 'C' already defined on line 1 \
1013-
# E: Incompatible import of "C" (imported name has type "Type[C]", local name has type "int")
1074+
MYPY = False
1075+
if MYPY: # Tweak processing ordre
1076+
from b import * # E: Incompatible import of "C" (imported name has type "Type[C]", local name has type "int")
10141077

10151078
[file b.py]
10161079
import a
10171080

10181081
class C(B): ...
10191082
class B: ...
10201083

1084+
[case testNewAnalyzerImportOverExistingInCycleStar2]
1085+
import a
1086+
[file a.py]
1087+
C = 1
1088+
from b import * # E: Incompatible import of "C" (imported name has type "Type[C]", local name has type "int")
1089+
1090+
[file b.py]
1091+
MYPY = False
1092+
if MYPY: # Tweak processing order
1093+
import a
1094+
1095+
class C(B): ...
1096+
class B: ...
1097+
10211098
[case testNewAnalyzerIncompleteFixture]
10221099
from typing import Tuple
10231100

0 commit comments

Comments
 (0)