Skip to content

Commit 00b3a0a

Browse files
authored
New semantic analyzer: track line numbers of placeholders (#6983)
Line numbers are used sometimes to decide when a name should be looked up from an outer scope, so they should be set for placeholders. This actually breaks some forward references to assignment-based named tuples. The reason is that we don't set `becomes_typeinfo=True` for the placeholders. I'm going to open a separate issue about this, as it's an existing issue and this change only exposes it. Fixes #6949.
1 parent 6a4f99a commit 00b3a0a

File tree

4 files changed

+36
-14
lines changed

4 files changed

+36
-14
lines changed

mypy/newsemanal/semanal.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,7 @@ def add_implicit_module_attrs(self, file_node: MypyFile) -> None:
410410
self.add_symbol(name, var, dummy_context())
411411
else:
412412
self.add_symbol(name,
413-
PlaceholderNode(self.qualified_name(name), file_node),
413+
PlaceholderNode(self.qualified_name(name), file_node, -1),
414414
dummy_context())
415415

416416
def add_builtin_aliases(self, tree: MypyFile) -> None:
@@ -994,8 +994,8 @@ def analyze_class(self, defn: ClassDef) -> None:
994994
# resolved. We don't want this to cause a deferral, since if there
995995
# are no incomplete references, we'll replace this with a TypeInfo
996996
# before returning.
997-
self.add_symbol(defn.name, PlaceholderNode(fullname, defn, True), defn,
998-
can_defer=False)
997+
placeholder = PlaceholderNode(fullname, defn, defn.line, becomes_typeinfo=True)
998+
self.add_symbol(defn.name, placeholder, defn, can_defer=False)
999999

10001000
tag = self.track_incomplete_refs()
10011001

@@ -1607,6 +1607,7 @@ def analyze_metaclass(self, defn: ClassDef) -> None:
16071607
#
16081608

16091609
def visit_import(self, i: Import) -> None:
1610+
self.statement = i
16101611
for id, as_id in i.ids:
16111612
if as_id is not None:
16121613
self.add_module_symbol(id, as_id, module_public=True, context=i)
@@ -1668,6 +1669,7 @@ def allow_patching(self, parent_mod: MypyFile, child: str) -> bool:
16681669
return False
16691670

16701671
def visit_import_from(self, imp: ImportFrom) -> None:
1672+
self.statement = imp
16711673
import_id = self.correct_relative_import(imp)
16721674
self.add_submodules_to_parent_modules(import_id, True)
16731675
module = self.modules.get(import_id)
@@ -2370,8 +2372,11 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool:
23702372
if self.found_incomplete_ref(tag) or isinstance(res, PlaceholderType):
23712373
# Since we have got here, we know this must be a type alias (incomplete refs
23722374
# may appear in nested positions), therefore use becomes_typeinfo=True.
2373-
self.add_symbol(lvalue.name, PlaceholderNode(self.qualified_name(lvalue.name),
2374-
rvalue, becomes_typeinfo=True), s)
2375+
placeholder = PlaceholderNode(self.qualified_name(lvalue.name),
2376+
rvalue,
2377+
s.line,
2378+
becomes_typeinfo=True)
2379+
self.add_symbol(lvalue.name, placeholder, s)
23752380
return True
23762381
self.add_type_alias_deps(depends_on)
23772382
# In addition to the aliases used, we add deps on unbound
@@ -3820,7 +3825,8 @@ class C:
38203825
return (node is None
38213826
or node.line < self.statement.line
38223827
or not self.is_defined_in_current_module(node.fullname())
3823-
or isinstance(node, TypeInfo))
3828+
or isinstance(node, TypeInfo)
3829+
or (isinstance(node, PlaceholderNode) and node.becomes_typeinfo))
38243830

38253831
def is_defined_in_current_module(self, fullname: Optional[str]) -> bool:
38263832
if fullname is None:
@@ -4265,9 +4271,9 @@ def mark_incomplete(self, name: str, node: Node,
42654271
self.incomplete = True
42664272
elif name not in self.current_symbol_table() and not self.is_global_or_nonlocal(name):
42674273
fullname = self.qualified_name(name)
4268-
self.add_symbol(name,
4269-
PlaceholderNode(fullname, node, becomes_typeinfo),
4270-
context=dummy_context())
4274+
placeholder = PlaceholderNode(fullname, node, self.statement.line,
4275+
becomes_typeinfo=becomes_typeinfo)
4276+
self.add_symbol(name, placeholder, context=dummy_context())
42714277
self.missing_names.add(name)
42724278

42734279
def is_incomplete_namespace(self, fullname: str) -> bool:

mypy/newsemanal/semanal_newtype.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ def process_newtype_declaration(self, s: AssignmentStmt) -> bool:
5050
if (not call.analyzed or
5151
isinstance(call.analyzed, NewTypeExpr) and not call.analyzed.info):
5252
# Start from labeling this as a future class, as we do for normal ClassDefs.
53-
self.api.add_symbol(name, PlaceholderNode(fullname, s, becomes_typeinfo=True), s,
54-
can_defer=False)
53+
placeholder = PlaceholderNode(fullname, s, s.line, becomes_typeinfo=True)
54+
self.api.add_symbol(name, placeholder, s, can_defer=False)
5555

5656
old_type, should_defer = self.check_newtype_args(name, call, s)
5757
if not call.analyzed:

mypy/nodes.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2792,11 +2792,12 @@ class C(Sequence[C]): ...
27922792
something that can support general recursive types.
27932793
"""
27942794

2795-
def __init__(self, fullname: str, node: Node, becomes_typeinfo: bool = False) -> None:
2795+
def __init__(self, fullname: str, node: Node, line: int, *,
2796+
becomes_typeinfo: bool = False) -> None:
27962797
self._fullname = fullname
27972798
self.node = node
27982799
self.becomes_typeinfo = becomes_typeinfo
2799-
self.line = -1
2800+
self.line = line
28002801

28012802
def name(self) -> str:
28022803
return self._fullname.split('.')[-1]

test-data/unit/check-newsemanal.test

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -830,8 +830,8 @@ reveal_type(o.x.t) # E: Revealed type is '__main__.C.Other'
830830
reveal_type(i.t) # E: Revealed type is '__main__.C.Other'
831831

832832
class C:
833-
Out = NamedTuple('Out', [('x', In), ('y', Other)])
834833
In = NamedTuple('In', [('s', str), ('t', Other)])
834+
Out = NamedTuple('Out', [('x', In), ('y', Other)])
835835
class Other: pass
836836

837837

@@ -2471,3 +2471,18 @@ class Something:
24712471

24722472
IDS = [87]
24732473
[builtins fixtures/list.pyi]
2474+
2475+
[case testNewAnalyzerPlaceholderFromOuterScope]
2476+
import b
2477+
[file a.py]
2478+
import b
2479+
class A(B): ...
2480+
class B: ...
2481+
2482+
[file b.py]
2483+
from a import A
2484+
2485+
class C:
2486+
A = A # Initially rvalue will be a placeholder
2487+
2488+
reveal_type(C.A) # E: Revealed type is 'def () -> a.A'

0 commit comments

Comments
 (0)