Repository: python/mypy

Issue:
Forward reference to namespaced type with same name breaks with TypedDict
* Are you reporting a bug, or opening a feature request?

I am reporting a bug

* Please insert below the code you are checking with mypy,
  or a mock-up repro if the source is private. We would appreciate
  if you try to simplify your case to a minimal repro.

```
from __future__ import annotations
from typing import Union, Any
from typing_extensions import TypedDict


class T(TypedDict):
    b: b.T


class b:
    T = Union[Any]
```

* What is the actual behavior/output?

```
failure2.py:7: error: Name 'b.T' is not defined
Found 1 error in 1 file (checked 1 source file)
```
* What is the behavior/output you expect? 

No type error

* What are the versions of mypy and Python you are using?
  Do you see the same issue after installing mypy from Git master?

Python 3.7.3, mypy 0.750. I get the same results after installing mypy from Git master.

* What are the mypy flags you are using? (For example --strict-optional)

No particular flag.

* If mypy crashed with a traceback, please paste
  the full traceback below.

No crash

Additional notes:
The following examples *do* typecheck properly:
* Name the type differently:
```
from __future__ import annotations
from typing import Union, Any
from typing_extensions import TypedDict


class T(TypedDict):
    b: b.U


class b:
    U = Union[Any]
```
* Without TypedDict:
```
from __future__ import annotations
from typing import Union, Any


class T:
    b: b.T


class b:
    T = Union[Any]
```
* Without forward reference:
```
from __future__ import annotations
from typing import Union, Any
from typing_extensions import TypedDict


class b:
    T = Union[Any]


class T(TypedDict):
    b: b.T
```



Discussion:
Thanks for the detailed bug report! Something is clearly wrong here.
Well, I encounter a similar situation with class T inherited from `NamedTuple`. It seems like this kind of inheritance cause TypeInfo of class `b` to have no node of attribute `T`, but I'm still trying to figure out the deeper reason.

Resolving patch:
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -186,7 +186,7 @@ class SemanticAnalyzer(NodeVisitor[None],
     #
     # Note that a star import adds a special name '*' to the set, this blocks
     # adding _any_ names in the current file.
-    missing_names = None  # type: Set[str]
+    missing_names = None  # type: List[Set[str]]
     # Callbacks that will be called after semantic analysis to tweak things.
     patches = None  # type: List[Tuple[int, Callable[[], None]]]
     loop_depth = 0         # Depth of breakable loops
@@ -236,6 +236,7 @@ def __init__(self,
         self.modules = modules
         self.msg = MessageBuilder(errors, modules)
         self.missing_modules = missing_modules
+        self.missing_names = [set()]
         # These namespaces are still in process of being populated. If we encounter a
         # missing name in these namespaces, we need to defer the current analysis target,
         # since it's possible that the name will be there once the namespace is complete.
@@ -343,7 +344,7 @@ def refresh_partial(self,
         self.deferred = False
         self.incomplete = False
         self._final_iteration = final_iteration
-        self.missing_names = set()
+        self.missing_names[-1] = set()
 
         with self.file_context(file_node, options, active_type):
             if isinstance(node, MypyFile):
@@ -1157,6 +1158,7 @@ def enter_class(self, info: TypeInfo) -> None:
         self.is_comprehension_stack.append(False)
         self.block_depth.append(-1)  # The class body increments this to 0
         self.type = info
+        self.missing_names.append(set())
 
     def leave_class(self) -> None:
         """ Restore analyzer state. """
@@ -1164,6 +1166,7 @@ def leave_class(self) -> None:
         self.locals.pop()
         self.is_comprehension_stack.pop()
         self.type = self.type_stack.pop()
+        self.missing_names.pop()
 
     def analyze_class_decorator(self, defn: ClassDef, decorator: Expression) -> None:
         decorator.accept(self)
@@ -4274,7 +4277,7 @@ def add_symbol_table_node(self,
                 if not (isinstance(new, (FuncDef, Decorator))
                         and self.set_original_def(old, new)):
                     self.name_already_defined(name, context, existing)
-        elif name not in self.missing_names and '*' not in self.missing_names:
+        elif (name not in self.missing_names[-1] and '*' not in self.missing_names[-1]):
             names[name] = symbol
             self.progress = True
             return True
@@ -4451,7 +4454,7 @@ def mark_incomplete(self, name: str, node: Node,
             self.add_symbol(name, placeholder,
                             module_public=module_public, module_hidden=module_hidden,
                             context=dummy_context())
-        self.missing_names.add(name)
+        self.missing_names[-1].add(name)
 
     def is_incomplete_namespace(self, fullname: str) -> bool:
         """Is a module or class namespace potentially missing some definitions?
@@ -4496,6 +4499,7 @@ def enter(self, function: Union[FuncItem, GeneratorExpr, DictionaryComprehension
         self.nonlocal_decls.append(set())
         # -1 since entering block will increment this to 0.
         self.block_depth.append(-1)
+        self.missing_names.append(set())
 
     def leave(self) -> None:
         self.locals.pop()
@@ -4503,6 +4507,7 @@ def leave(self) -> None:
         self.global_decls.pop()
         self.nonlocal_decls.pop()
         self.block_depth.pop()
+        self.missing_names.pop()
 
     def is_func_scope(self) -> bool:
         return self.locals[-1] is not None


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
@@ -3200,3 +3200,25 @@ class User:
     def __init__(self, name: str) -> None:
         self.name = name  # E: Cannot assign to a method \
                           # E: Incompatible types in assignment (expression has type "str", variable has type "Callable[..., Any]")
+
+[case testNewAnalyzerMemberNameMatchesTypedDict]
+from typing import Union, Any
+from typing_extensions import TypedDict
+
+class T(TypedDict):
+    b: b.T
+
+class b:
+    T: Union[Any]
+[builtins fixtures/tuple.pyi]
+
+[case testNewAnalyzerMemberNameMatchesNamedTuple]
+from typing import Union, Any, NamedTuple
+
+class T(NamedTuple):
+    b: b.T
+
+class b:
+    T = Union[Any]
+
+[builtins fixtures/tuple.pyi]