Repository: python/mypy

Issue:
New semantic analyzer: cannot resolve assignment to imported value from outer scope
This test case fails with `Cannot resolve name "A" (possible cyclic definition)`:

```py
[case testNewAnalyzerTrickyX]
import b
[file a.py]
import b
class A(B): ...
class B: ...

[file b.py]
from a import A

class C:
    A = A
```



Discussion:

Resolving patch:
diff --git a/mypy/newsemanal/semanal.py b/mypy/newsemanal/semanal.py
--- 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
--- 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
--- 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]


Test patch:
diff --git a/test-data/unit/check-newsemanal.test b/test-data/unit/check-newsemanal.test
--- 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'