Repository: python/mypy

Issue:
Mypy treats type aliases of nested classes as invalid types
Consider the following test case:

``` python
class Outer:
    class Inner:
        def make_int(self) -> int: return 1

Bar = Outer.Inner
x = Bar()  # type: Bar 
def produce() -> Bar:
    return Bar()
```

The expected output is for mypy to typecheck this without error, but instead it reports the following:

```
test.py:6: error: Invalid type "test.Bar"
test.py: note: In function "produce":
test.py:7: error: Invalid type "test.Bar"
```

As a side-note, I did some brief testing and it seems this error only happens when you use `Bar` where a type is expected. Mypy is perfectly happy with the following program, for example:

``` python
class Outer:
    class Inner:
        def make_int(self) -> int: return 1

Bar = Outer.Inner
x = Bar()  # type: Outer.Inner 
def produce() -> Outer.Inner:
    return Bar()
```



Discussion:
If you want to play around with aliases, they are recognized in semanal.py,
function visit_assignment_stmt(), in the big else clause around line 1100.


Resolving patch:
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -72,6 +72,7 @@
 from mypy.visitor import NodeVisitor
 from mypy.traverser import TraverserVisitor
 from mypy.errors import Errors, report_internal_error
+from mypy.messages import CANNOT_ASSIGN_TO_TYPE
 from mypy.types import (
     NoneTyp, CallableType, Overloaded, Instance, Type, TypeVarType, AnyType,
     FunctionLike, UnboundType, TypeList, TypeVarDef, TypeType,
@@ -1591,8 +1592,10 @@ def is_self_member_ref(self, memberexpr: MemberExpr) -> bool:
         return isinstance(node, Var) and node.is_self
 
     def check_lvalue_validity(self, node: Union[Expression, SymbolNode], ctx: Context) -> None:
-        if isinstance(node, (TypeInfo, TypeVarExpr)):
+        if isinstance(node, TypeVarExpr):
             self.fail('Invalid assignment target', ctx)
+        elif isinstance(node, TypeInfo):
+            self.fail(CANNOT_ASSIGN_TO_TYPE, ctx)
 
     def store_declared_types(self, lvalue: Lvalue, typ: Type) -> None:
         if isinstance(typ, StarType) and not isinstance(lvalue, StarExpr):
@@ -2652,7 +2655,7 @@ def visit_member_expr(self, expr: MemberExpr) -> None:
                 expr.node = n.node
             else:
                 # We only catch some errors here; the rest will be
-                # catched during type checking.
+                # caught during type checking.
                 #
                 # This way we can report a larger number of errors in
                 # one type checker run. If we reported errors here,
@@ -2664,14 +2667,14 @@ def visit_member_expr(self, expr: MemberExpr) -> None:
                     self.fail("Module%s has no attribute %r (it's now called %r)" % (
                         mod_name, expr.name, obsolete_name_mapping[full_name]), expr)
         elif isinstance(base, RefExpr) and isinstance(base.node, TypeInfo):
-            # This branch handles the case C.bar where C is a class
-            # and bar is a module resulting from `import bar` inside
-            # class C.  Here base.node is a TypeInfo, and again we
-            # look up the name in its namespace.  This is done only
-            # when bar is a module; other things (e.g. methods)
-            # are handled by other code in checkmember.
             n = base.node.names.get(expr.name)
-            if n is not None and n.kind == MODULE_REF:
+            if n is not None and (n.kind == MODULE_REF or isinstance(n.node, TypeInfo)):
+                # This branch handles the case C.bar where C is a class and
+                # bar is a type definition or a module resulting from
+                # `import bar` inside class C. Here base.node is a TypeInfo,
+                # and again we look up the name in its namespace.
+                # This is done only when bar is a module or a type; other
+                # things (e.g. methods) are handled by other code in checkmember.
                 n = self.normalize_type_alias(n, expr)
                 if not n:
                     return


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
@@ -662,6 +662,21 @@ b = A.B()
 b = A() # E: Incompatible types in assignment (expression has type "A", variable has type "B")
 b = A.B(b) # E: Too many arguments for "B"
 
+[case testAliasNestedClass]
+class Outer:
+    class Inner:
+        def make_int(self) -> int: return 1
+    reveal_type(Inner().make_int)  # E: Revealed type is 'def () -> builtins.int'
+    some_int = Inner().make_int()
+
+reveal_type(Outer.Inner.make_int)  # E: Revealed type is 'def (self: __main__.Outer.Inner) -> builtins.int'
+reveal_type(Outer().some_int) # E: Revealed type is 'builtins.int'
+Bar = Outer.Inner
+reveal_type(Bar.make_int)  # E: Revealed type is 'def (self: __main__.Outer.Inner) -> builtins.int'
+x = Bar()  # type: Bar
+def produce() -> Bar:
+    reveal_type(Bar().make_int)  # E: Revealed type is 'def () -> builtins.int'
+    return Bar()
 
 -- Declaring attribute type in method
 -- ----------------------------------
diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test
--- a/test-data/unit/check-generics.test
+++ b/test-data/unit/check-generics.test
@@ -1296,6 +1296,18 @@ a = A().g(1)
 a = 1
 a = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int")
 
+[case testGenericInnerClass]
+from typing import TypeVar, Generic
+T = TypeVar('T')
+class A:
+    class B(Generic[T]):
+        def meth(self) -> T:  ...
+    B[int]()
+    reveal_type(B[int]().meth) # E: Revealed type is 'def () -> builtins.int*'
+
+A.B[int]()
+reveal_type(A.B[int]().meth) # E: Revealed type is 'def () -> builtins.int*'
+
 [case testGenericClassInnerFunctionTypeVariable]
 from typing import TypeVar, Generic
 T = TypeVar('T')
diff --git a/test-data/unit/check-newtype.test b/test-data/unit/check-newtype.test
--- a/test-data/unit/check-newtype.test
+++ b/test-data/unit/check-newtype.test
@@ -300,7 +300,7 @@ b = NewType('b', float)  # this line throws two errors
 c = NewType('c', str)   # type: str
 [out]
 main:4: error: Cannot redefine 'a' as a NewType
-main:7: error: Invalid assignment target
+main:7: error: Cannot assign to a type
 main:7: error: Cannot redefine 'b' as a NewType
 main:9: error: Cannot declare the type of a NewType declaration
 
diff --git a/test-data/unit/semanal-errors.test b/test-data/unit/semanal-errors.test
--- a/test-data/unit/semanal-errors.test
+++ b/test-data/unit/semanal-errors.test
@@ -760,7 +760,7 @@ class A(A): pass # E: Cycle in inheritance hierarchy
 [case testAssignToTypeDef]
 import typing
 class A: pass
-A = None # E: Invalid assignment target
+A = None # E: Cannot assign to a type
 [out]
 
 [case testInvalidCastTargetSyntax]