Repository: python/mypy

Issue:
Type[X] not compatible with metaclass of X
The second-to-last line should probably be accepted by mypy, but it currently generates an error because `Type[A]` isn't compatible with the metaclass of `M`:

```py
from typing import Type

class M(type): pass
class A(metaclass=M): pass

a: Type[A] = A
m: M = a    # error, but should be okay
m2: M = A   # no error
```


Discussion:

Resolving patch:
diff --git a/mypy/subtypes.py b/mypy/subtypes.py
--- a/mypy/subtypes.py
+++ b/mypy/subtypes.py
@@ -69,11 +69,6 @@ def is_subtype(left: Type, right: Type,
         elif is_subtype_of_item:
             return True
         # otherwise, fall through
-    # Treat builtins.type the same as Type[Any]
-    elif is_named_instance(left, 'builtins.type'):
-            return is_subtype(TypeType(AnyType()), right)
-    elif is_named_instance(right, 'builtins.type'):
-            return is_subtype(left, TypeType(AnyType()))
     return left.accept(SubtypeVisitor(right, type_parameter_checker,
                                       ignore_pos_arg_names=ignore_pos_arg_names))
 
@@ -158,16 +153,18 @@ def visit_instance(self, left: Instance) -> bool:
             item = right.item
             if isinstance(item, TupleType):
                 item = item.fallback
-            if isinstance(item, Instance):
-                return is_subtype(left, item.type.metaclass_type)
-            elif isinstance(item, AnyType):
-                # Special case: all metaclasses are subtypes of Type[Any]
-                mro = left.type.mro or []
-                return any(base.fullname() == 'builtins.type' for base in mro)
-            else:
-                return False
-        else:
-            return False
+            if is_named_instance(left, 'builtins.type'):
+                return is_subtype(TypeType(AnyType()), right)
+            if left.type.is_metaclass():
+                if isinstance(item, AnyType):
+                    return True
+                if isinstance(item, Instance):
+                    # Special-case enum since we don't have better way of expressing it
+                    if (is_named_instance(left, 'enum.EnumMeta')
+                            and is_named_instance(item, 'enum.Enum')):
+                        return True
+                    return is_named_instance(item, 'builtins.object')
+        return False
 
     def visit_type_var(self, left: TypeVarType) -> bool:
         right = self.right
@@ -263,8 +260,8 @@ def visit_overloaded(self, left: Overloaded) -> bool:
         elif isinstance(right, TypeType):
             # All the items must have the same type object status, so
             # it's sufficient to query only (any) one of them.
-            # This is unsound, we don't check the __init__ signature.
-            return left.is_type_obj() and is_subtype(left.items()[0].ret_type, right.item)
+            # This is unsound, we don't check all the __init__ signatures.
+            return left.is_type_obj() and is_subtype(left.items()[0], right)
         else:
             return False
 
@@ -284,11 +281,14 @@ def visit_type_type(self, left: TypeType) -> bool:
             # This is unsound, we don't check the __init__ signature.
             return is_subtype(left.item, right.ret_type)
         if isinstance(right, Instance):
-            if right.type.fullname() == 'builtins.object':
-                # treat builtins.object the same as Any.
+            if right.type.fullname() in ['builtins.object', 'builtins.type']:
                 return True
             item = left.item
-            return isinstance(item, Instance) and is_subtype(item, right.type.metaclass_type)
+            if isinstance(item, TypeVarType):
+                item = item.upper_bound
+            if isinstance(item, Instance):
+                metaclass = item.type.metaclass_type
+                return metaclass is not None and is_subtype(metaclass, right)
         return False
 
 


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
@@ -3067,11 +3067,11 @@ class A(metaclass=M): pass
 
 reveal_type(A[M])  # E: Revealed type is 'builtins.int'
 
-[case testMetaclassSelftype]
+[case testMetaclassSelfType]
 from typing import TypeVar, Type
 
 class M(type): pass
-T = TypeVar('T', bound='A')
+T = TypeVar('T')
 
 class M1(M):
     def foo(cls: Type[T]) -> T: ...
@@ -3137,6 +3137,80 @@ class M(type):
 class A(metaclass=M): pass
 reveal_type(type(A).x)  # E: Revealed type is 'builtins.int'
 
+[case testMetaclassStrictSupertypeOfTypeWithClassmethods]
+from typing import Type, TypeVar
+TA = TypeVar('TA', bound='A')
+TTA = TypeVar('TTA', bound='Type[A]')
+TM = TypeVar('TM', bound='M')
+
+class M(type):
+    def g1(cls: 'Type[A]') -> A: pass #  E: The erased type of self 'Type[__main__.A]' is not a supertype of its class '__main__.M'
+    def g2(cls: Type[TA]) -> TA: pass #  E: The erased type of self 'Type[__main__.A]' is not a supertype of its class '__main__.M'
+    def g3(cls: TTA) -> TTA: pass #  E: The erased type of self 'Type[__main__.A]' is not a supertype of its class '__main__.M'
+    def g4(cls: TM) -> TM: pass
+m: M
+
+class A(metaclass=M):
+    def foo(self): pass
+
+reveal_type(A.g1)  # E: Revealed type is 'def () -> __main__.A'
+reveal_type(A.g2)  # E: Revealed type is 'def () -> __main__.A*'
+reveal_type(A.g3)  # E: Revealed type is 'def () -> def () -> __main__.A'
+reveal_type(A.g4)  # E: Revealed type is 'def () -> def () -> __main__.A'
+
+class B(metaclass=M):
+    def foo(self): pass
+
+B.g1  # Should be error: Argument 0 to "g1" of "M" has incompatible type "B"; expected Type[A]
+B.g2  # Should be error: Argument 0 to "g2" of "M" has incompatible type "B"; expected Type[TA]
+B.g3  # Should be error: Argument 0 to "g3" of "M" has incompatible type "B"; expected "TTA"
+reveal_type(B.g4)  # E: Revealed type is 'def () -> def () -> __main__.B'
+
+# 4 examples of unsoundness - instantiation, classmethod, staticmethod and ClassVar:
+
+ta: Type[A] = m  # E: Incompatible types in assignment (expression has type "M", variable has type Type[A])
+a: A = ta()
+reveal_type(ta.g1)  # E: Revealed type is 'def () -> __main__.A'
+reveal_type(ta.g2)  # E: Revealed type is 'def () -> __main__.A*'
+reveal_type(ta.g3)  # E: Revealed type is 'def () -> Type[__main__.A]'
+reveal_type(ta.g4)  # E: Revealed type is 'def () -> Type[__main__.A]'
+
+x: M = ta
+x.g1  # should be error: Argument 0 to "g1" of "M" has incompatible type "M"; expected Type[A]
+x.g2  # should be error: Argument 0 to "g2" of "M" has incompatible type "M"; expected Type[TA]
+x.g3  # should be error: Argument 0 to "g3" of "M" has incompatible type "M"; expected "TTA"
+reveal_type(x.g4)  # E: Revealed type is 'def () -> __main__.M*'
+
+def r(ta: Type[TA], tta: TTA) -> None:
+    x: M = ta
+    y: M = tta
+
+class Class(metaclass=M):
+    @classmethod
+    def f1(cls: Type[Class]) -> None: pass
+    @classmethod
+    def f2(cls: M) -> None: pass
+cl: Type[Class] = m  # E: Incompatible types in assignment (expression has type "M", variable has type Type[Class])
+reveal_type(cl.f1)  # E: Revealed type is 'def ()'
+reveal_type(cl.f2)  # E: Revealed type is 'def ()'
+x1: M = cl
+
+class Static(metaclass=M):
+    @staticmethod
+    def f() -> None: pass
+s: Type[Static] = m  # E: Incompatible types in assignment (expression has type "M", variable has type Type[Static])
+reveal_type(s.f)  # E: Revealed type is 'def ()'
+x2: M = s
+
+from typing import ClassVar
+class Cvar(metaclass=M):
+    x = 1  # type: ClassVar[int]
+cv: Type[Cvar] = m  # E: Incompatible types in assignment (expression has type "M", variable has type Type[Cvar])
+cv.x
+x3: M = cv
+
+[builtins fixtures/classmethod.pyi]
+
 -- Synthetic types crashes
 -- -----------------------