Repository: python/mypy

Issue:
Regression since v0.520 due to PR #3700: Name 'blah' not defined
Example:
```py
class A(object):
    def f(self):
        self.attr = 1
    attr = 0  # error: Name 'attr' is not defined

class B(object):
    attr = 0  # no error
    def f(self):
        self.attr = 1
```
This has no errors in v0.520 but has an error at the indicated line at f127e2cac5277c1d008da50611532167891cae48, i.e. it is caused by #3700.


Discussion:

Resolving patch:
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -3384,6 +3384,7 @@ def tvar_scope_frame(self, frame: TypeVarScope) -> Iterator[None]:
 
     def lookup(self, name: str, ctx: Context) -> SymbolTableNode:
         """Look up an unqualified name in all active namespaces."""
+        implicit_name = False
         # 1a. Name declared using 'global x' takes precedence
         if name in self.global_decls[-1]:
             if name in self.globals:
@@ -3404,6 +3405,8 @@ def lookup(self, name: str, ctx: Context) -> SymbolTableNode:
             node = self.type.names[name]
             if not node.implicit:
                 return node
+            implicit_name = True
+            implicit_node = node
         # 3. Local (function) scopes
         for table in reversed(self.locals):
             if table is not None and name in table:
@@ -3423,8 +3426,11 @@ def lookup(self, name: str, ctx: Context) -> SymbolTableNode:
                 node = table[name]
                 return node
         # Give up.
-        self.name_not_defined(name, ctx)
-        self.check_for_obsolete_short_name(name, ctx)
+        if not implicit_name:
+            self.name_not_defined(name, ctx)
+            self.check_for_obsolete_short_name(name, ctx)
+        else:
+            return implicit_node
         return None
 
     def check_for_obsolete_short_name(self, name: str, ctx: Context) -> None:


Test patch:
diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test
--- a/test-data/unit/check-classes.test
+++ b/test-data/unit/check-classes.test
@@ -198,6 +198,43 @@ class A:
 main:6: error: Incompatible types in assignment (expression has type "str", variable has type "int")
 main:8: error: Incompatible types in assignment (expression has type "str", variable has type "int")
 
+[case testClassNamesDefinedOnSelUsedInClassBody]
+class A(object):
+    def f(self):
+        self.attr = 1
+    attr = 0
+
+class B(object):
+    attr = 0
+    def f(self):
+        self.attr = 1
+
+class C(object):
+    attr = 0
+    def f(self):
+        self.attr = 1
+    attr = 0
+
+class D(object):
+    def g(self):
+        self.attr = 1
+    attr = 0
+    def f(self):
+        self.attr = 1
+[out]
+
+[case testClassNamesDefinedOnSelUsedInClassBodyReveal]
+class A(object):
+    def f(self) -> None:
+        self.attr = 1
+    reveal_type(attr)  # E: Revealed type is 'builtins.int'
+
+class B(object):
+    attr = 0
+    def f(self) -> None:
+        reveal_type(self.attr)  # E: Revealed type is 'builtins.int'
+[out]
+
 
 -- Method overriding
 -- -----------------
@@ -3386,9 +3423,7 @@ NT([])
 [builtins fixtures/dict.pyi]
 [out]
 
--- The two tests below will not crash after
--- https://github.com/python/mypy/issues/3319 is fixed
-[case testCrashForwardSyntheticClassSyntax-skip]
+[case testCrashForwardSyntheticClassSyntax]
 from typing import NamedTuple
 from mypy_extensions import TypedDict
 class A1(NamedTuple):
@@ -3406,7 +3441,7 @@ reveal_type(y['b']) # E: Revealed type is '__main__.B'
 [builtins fixtures/dict.pyi]
 [out]
 
-[case testCrashForwardSyntheticFunctionSyntax-skip]
+[case testCrashForwardSyntheticFunctionSyntax]
 from typing import NamedTuple
 from mypy_extensions import TypedDict
 A1 = NamedTuple('A1', [('b', 'B'), ('x', int)])