Repository: python/mypy

Issue:
Metaclasses should inherit from type.
There is something missing in #2475: this code:
```python
class M:
    ...
class A(metaclass=M):
    ...
```
Passes without errors, but fails at runtime.

---------------------------------------------------
Btw, I have found two open issues that were fixed by #2475, I left comments referencing the #2475 in that issues.


Discussion:
I am for fixing it. The reason I have allowed this behavior is the [documentation](https://docs.python.org/3/reference/datamodel.html#determining-the-appropriate-metaclass).

Quote:

The appropriate metaclass for a class definition is determined as follows:

* if no bases and no explicit metaclass are given, then type() is used
* if an explicit metaclass is given and it is _not_ an instance of type(), then it is used directly as the metaclass
* if an instance of type() is given as the explicit metaclass, or bases are defined, then the most derived metaclass is used

End quote.

What do you think?
@elazarg I think the use case where metaclass is not an instance of ``type`` is quite rare, and (more importantly) this case could be difficult to typecheck. I think mypy could give an error message like: ``Metaclasses not inheriting from "type" are not yet supported.``
I have a branch ready 
, but it requires multiple changes to typeshed before it can be merged.


Resolving patch:
diff --git a/mypy/nodes.py b/mypy/nodes.py
--- a/mypy/nodes.py
+++ b/mypy/nodes.py
@@ -2024,7 +2024,7 @@ def calculate_metaclass_type(self) -> 'Optional[mypy.types.Instance]':
         return None
 
     def is_metaclass(self) -> bool:
-        return self.has_base('builtins.type')
+        return self.has_base('builtins.type') or self.fullname() == 'abc.ABCMeta'
 
     def _calculate_is_enum(self) -> bool:
         """
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -966,6 +966,9 @@ def analyze_metaclass(self, defn: ClassDef) -> None:
             if not isinstance(sym.node, TypeInfo) or sym.node.tuple_type is not None:
                 self.fail("Invalid metaclass '%s'" % defn.metaclass, defn)
                 return
+            if not sym.node.is_metaclass():
+                self.fail("Metaclasses not inheriting from 'type' are not supported", defn)
+                return
             inst = fill_typevars(sym.node)
             assert isinstance(inst, Instance)
             defn.info.declared_metaclass = inst


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
@@ -2770,9 +2770,9 @@ class B(A, metaclass=Y): pass  # E: Inconsistent metaclass structure for 'B'
 class M:
     x = 0  # type: int
 
-class A(metaclass=M): pass
+class A(metaclass=M): pass  # E: Metaclasses not inheriting from 'type' are not supported
 
-reveal_type(A.x)  # E: Revealed type is 'builtins.int'
+A.x  # E: "A" has no attribute "x"
 
 [case testMetaclassTypeReveal]
 from typing import Type
@@ -2785,6 +2785,14 @@ def f(TA: Type[A]):
     reveal_type(TA)  # E: Revealed type is 'Type[__main__.A]'
     reveal_type(TA.x)  # E: Revealed type is 'builtins.int'
 
+[case testSubclassMetaclass]
+class M1(type):
+    x = 0
+class M2(M1): pass
+class C(metaclass=M2):
+    pass
+reveal_type(C.x) # E: Revealed type is 'builtins.int'
+
 [case testMetaclassSubclass]
 from typing import Type
 class M(type):
@@ -2822,4 +2830,4 @@ from typing import Tuple
 class M(Tuple[int]): pass
 class C(metaclass=M): pass  # E: Invalid metaclass 'M'
 
-[builtins fixtures/tuple.pyi]
\ No newline at end of file
+[builtins fixtures/tuple.pyi]