Repository: python/mypy

Issue:
Update the behaviour of the `type` function for metaclasses
Calling `type` on a class `A` yields `Type[A]`, even if `A` has a metaclass other than `type`. This leads to incorrectly typechecking interactions with `type(A)` when when attempting to use metaclass attributes.


Discussion:
Somewhat related to #2655
Can you give a concrete example?
The interaction with other features might be interesting. Maybe it should work something like this
```Python
class M(Generic(T), Type[T]): pass  # maybe M(type) is a shorthand for this

T1 = TypeVar('T1', bound='A')

class A(metaclass=M):
    @classmethod
    def foo(cls: M[T1]) -> T1:
        return cls()

reveal_type(A)  # E: Revealed type is M[A]

class B(A): pass

reveal_type(B)  # E: Revealed type is M[B]
reveal_type(B.foo()) # E: Revealed type is B
```
All of this should be how things work right now, except that `M[B]` is written `Type[B]`. Where does this fail?
I agree it should be possible to write `M[T]` for "a type whose metaclass is M".
```python
class Meta(type):
    x = 1

class A(metaclass=Meta): pass

print(type(A).x) # test2826.py:6: error: Type[] has no attribute "x"
```

`type(A)` is `Meta` which definitely has an attribute `x`. Do I need to make `Meta` subclass `Type[T]` for it to work?
Thanks.

The culprit in my example seems to be that the type variable T is quantified over type constructors (`TypeInfo`) and not over types. Perhaps there is a workaround.
(Just thinking out loud).

Resolving patch:
diff --git a/mypy/typeanal.py b/mypy/typeanal.py
--- a/mypy/typeanal.py
+++ b/mypy/typeanal.py
@@ -129,7 +129,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type:
                 return TypeVarType(sym.tvar_def, t.line)
             elif fullname == 'builtins.None':
                 return NoneTyp()
-            elif fullname == 'typing.Any':
+            elif fullname == 'typing.Any' or fullname == 'builtins.Any':
                 return AnyType()
             elif fullname == 'typing.Tuple':
                 if len(t.args) == 0 and not t.empty_tuple_index:
diff --git a/mypy/types.py b/mypy/types.py
--- a/mypy/types.py
+++ b/mypy/types.py
@@ -384,6 +384,7 @@ class Instance(Type):
 
     def __init__(self, typ: Optional[mypy.nodes.TypeInfo], args: List[Type],
                  line: int = -1, column: int = -1, erased: bool = False) -> None:
+        assert(typ is None or typ.fullname() not in ["builtins.Any", "typing.Any"])
         self.type = typ
         self.args = args
         self.erased = erased
@@ -1146,7 +1147,10 @@ class TypeType(Type):
 
     def __init__(self, item: Type, *, line: int = -1, column: int = -1) -> None:
         super().__init__(line, column)
-        self.item = item
+        if isinstance(item, CallableType) and item.is_type_obj():
+            self.item = item.fallback
+        else:
+            self.item = item
 
     def accept(self, visitor: 'TypeVisitor[T]') -> T:
         return visitor.visit_type_type(self)


Test patch:
diff --git a/mypy/test/testtypegen.py b/mypy/test/testtypegen.py
--- a/mypy/test/testtypegen.py
+++ b/mypy/test/testtypegen.py
@@ -88,7 +88,7 @@ def __init__(self) -> None:
         self.is_typing = False
 
     def visit_mypy_file(self, f: MypyFile) -> None:
-        self.is_typing = f.fullname() == 'typing'
+        self.is_typing = f.fullname() == 'typing' or f.fullname() == 'builtins'
         super().visit_mypy_file(f)
 
     def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
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
@@ -3111,3 +3111,11 @@ reveal_type(A.x) # E: Revealed type is 'Any'
 A.f(1) # E: Too many arguments for "f" of "A"
 A().g(1) # E: Too many arguments for "g" of "A"
 [builtins fixtures/classmethod.pyi]
+
+[case testMetaclassTypeCallable]
+class M(type):
+    x = 5
+
+class A(metaclass=M): pass
+reveal_type(type(A).x)  # E: Revealed type is 'builtins.int'
+
diff --git a/test-data/unit/semanal-types.test b/test-data/unit/semanal-types.test
--- a/test-data/unit/semanal-types.test
+++ b/test-data/unit/semanal-types.test
@@ -736,7 +736,7 @@ MypyFile:1(
       f
       Args(
         Var(a))
-      def (a: builtins.Any) -> builtins.Any
+      def (a: Any) -> Any
       Block:7(
         ReturnStmt:7(
           NameExpr(a [l]))))