diff --git a/mypy/newsemanal/semanal.py b/mypy/newsemanal/semanal.py index 8e932c95731e..cfd2005bf70a 100644 --- a/mypy/newsemanal/semanal.py +++ b/mypy/newsemanal/semanal.py @@ -410,7 +410,7 @@ def add_implicit_module_attrs(self, file_node: MypyFile) -> None: self.add_symbol(name, var, dummy_context()) else: self.add_symbol(name, - PlaceholderNode(self.qualified_name(name), file_node), + PlaceholderNode(self.qualified_name(name), file_node, -1), dummy_context()) def add_builtin_aliases(self, tree: MypyFile) -> None: @@ -994,8 +994,8 @@ def analyze_class(self, defn: ClassDef) -> None: # resolved. We don't want this to cause a deferral, since if there # are no incomplete references, we'll replace this with a TypeInfo # before returning. - self.add_symbol(defn.name, PlaceholderNode(fullname, defn, True), defn, - can_defer=False) + placeholder = PlaceholderNode(fullname, defn, defn.line, becomes_typeinfo=True) + self.add_symbol(defn.name, placeholder, defn, can_defer=False) tag = self.track_incomplete_refs() @@ -1607,6 +1607,7 @@ def analyze_metaclass(self, defn: ClassDef) -> None: # def visit_import(self, i: Import) -> None: + self.statement = i for id, as_id in i.ids: if as_id is not None: 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: return False def visit_import_from(self, imp: ImportFrom) -> None: + self.statement = imp import_id = self.correct_relative_import(imp) self.add_submodules_to_parent_modules(import_id, True) module = self.modules.get(import_id) @@ -2370,8 +2372,11 @@ def check_and_set_up_type_alias(self, s: AssignmentStmt) -> bool: if self.found_incomplete_ref(tag) or isinstance(res, PlaceholderType): # Since we have got here, we know this must be a type alias (incomplete refs # may appear in nested positions), therefore use becomes_typeinfo=True. - self.add_symbol(lvalue.name, PlaceholderNode(self.qualified_name(lvalue.name), - rvalue, becomes_typeinfo=True), s) + placeholder = PlaceholderNode(self.qualified_name(lvalue.name), + rvalue, + s.line, + becomes_typeinfo=True) + self.add_symbol(lvalue.name, placeholder, s) return True self.add_type_alias_deps(depends_on) # In addition to the aliases used, we add deps on unbound @@ -3820,7 +3825,8 @@ class C: return (node is None or node.line < self.statement.line or not self.is_defined_in_current_module(node.fullname()) - or isinstance(node, TypeInfo)) + or isinstance(node, TypeInfo) + or (isinstance(node, PlaceholderNode) and node.becomes_typeinfo)) def is_defined_in_current_module(self, fullname: Optional[str]) -> bool: if fullname is None: @@ -4265,9 +4271,9 @@ def mark_incomplete(self, name: str, node: Node, self.incomplete = True elif name not in self.current_symbol_table() and not self.is_global_or_nonlocal(name): fullname = self.qualified_name(name) - self.add_symbol(name, - PlaceholderNode(fullname, node, becomes_typeinfo), - context=dummy_context()) + placeholder = PlaceholderNode(fullname, node, self.statement.line, + becomes_typeinfo=becomes_typeinfo) + self.add_symbol(name, placeholder, context=dummy_context()) self.missing_names.add(name) def is_incomplete_namespace(self, fullname: str) -> bool: diff --git a/mypy/newsemanal/semanal_newtype.py b/mypy/newsemanal/semanal_newtype.py index e8a819330a01..8c4f3b46aeb1 100644 --- a/mypy/newsemanal/semanal_newtype.py +++ b/mypy/newsemanal/semanal_newtype.py @@ -50,8 +50,8 @@ def process_newtype_declaration(self, s: AssignmentStmt) -> bool: if (not call.analyzed or isinstance(call.analyzed, NewTypeExpr) and not call.analyzed.info): # Start from labeling this as a future class, as we do for normal ClassDefs. - self.api.add_symbol(name, PlaceholderNode(fullname, s, becomes_typeinfo=True), s, - can_defer=False) + placeholder = PlaceholderNode(fullname, s, s.line, becomes_typeinfo=True) + self.api.add_symbol(name, placeholder, s, can_defer=False) old_type, should_defer = self.check_newtype_args(name, call, s) if not call.analyzed: diff --git a/mypy/nodes.py b/mypy/nodes.py index 25c38eb6c394..93421c253cb7 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -2792,11 +2792,12 @@ class C(Sequence[C]): ... something that can support general recursive types. """ - def __init__(self, fullname: str, node: Node, becomes_typeinfo: bool = False) -> None: + def __init__(self, fullname: str, node: Node, line: int, *, + becomes_typeinfo: bool = False) -> None: self._fullname = fullname self.node = node self.becomes_typeinfo = becomes_typeinfo - self.line = -1 + self.line = line def name(self) -> str: return self._fullname.split('.')[-1] diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test index 431007b37751..3ce2154d72a6 100644 --- a/test-data/unit/check-newsemanal.test +++ b/test-data/unit/check-newsemanal.test @@ -830,8 +830,8 @@ reveal_type(o.x.t) # E: Revealed type is '__main__.C.Other' reveal_type(i.t) # E: Revealed type is '__main__.C.Other' class C: - Out = NamedTuple('Out', [('x', In), ('y', Other)]) In = NamedTuple('In', [('s', str), ('t', Other)]) + Out = NamedTuple('Out', [('x', In), ('y', Other)]) class Other: pass @@ -2471,3 +2471,18 @@ class Something: IDS = [87] [builtins fixtures/list.pyi] + +[case testNewAnalyzerPlaceholderFromOuterScope] +import b +[file a.py] +import b +class A(B): ... +class B: ... + +[file b.py] +from a import A + +class C: + A = A # Initially rvalue will be a placeholder + +reveal_type(C.A) # E: Revealed type is 'def () -> a.A'