Repository: python/mypy

Issue:
Metaclass with Any type should be allowed
This should work:

```
from typing import Any
M = None # type: Any
class C(metaclass=M): pass
```

Currently `M` is considered an invalid metaclass.

Also, it should be possible to use a class that is imported from a missing module as a metaclass when using `--ignore-missing-imports`.

The simplest fix is to just ignore such metaclasses, though a better approach may be to treat them in a similar way as an `Any` base class (but only for class attributes).


Discussion:

Resolving patch:
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -960,6 +960,7 @@ def is_base_class(self, t: TypeInfo, s: TypeInfo) -> bool:
     def analyze_metaclass(self, defn: ClassDef) -> None:
         error_context = defn  # type: Context
         if defn.metaclass is None and self.options.python_version[0] == 2:
+            # Look for "__metaclass__ = <metaclass>" in Python 2.
             for body_node in defn.defs.body:
                 if isinstance(body_node, ClassDef) and body_node.name == "__metaclass__":
                     self.fail("Metaclasses defined as inner classes are not supported", body_node)
@@ -990,6 +991,13 @@ def analyze_metaclass(self, defn: ClassDef) -> None:
             if sym is None:
                 # Probably a name error - it is already handled elsewhere
                 return
+            if isinstance(sym.node, Var) and isinstance(sym.node.type, AnyType):
+                # 'Any' metaclass -- just ignore it.
+                #
+                # TODO: A better approach would be to record this information
+                #       and assume that the type object supports arbitrary
+                #       attributes, similar to an 'Any' base class.
+                return
             if not isinstance(sym.node, TypeInfo) or sym.node.tuple_type is not None:
                 self.fail("Invalid metaclass '%s'" % defn.metaclass, defn)
                 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
@@ -2923,3 +2923,30 @@ class M1(M):
 
 class A(metaclass=M1): pass
 reveal_type(A.foo())  # E: Revealed type is '__main__.A*'
+
+[case testMetaclassAndSkippedImport]
+# flags: --ignore-missing-imports
+from missing import M
+class A(metaclass=M):
+    y = 0
+reveal_type(A.y) # E: Revealed type is 'builtins.int'
+A.x # E: "A" has no attribute "x"
+
+[case testAnyMetaclass]
+from typing import Any
+M = None  # type: Any
+class A(metaclass=M):
+    y = 0
+reveal_type(A.y) # E: Revealed type is 'builtins.int'
+A.x # E: "A" has no attribute "x"
+
+[case testInvalidVariableAsMetaclass]
+from typing import Any
+M = 0  # type: int
+MM = 0
+class A(metaclass=M): # E: Invalid metaclass 'M'
+    y = 0
+class B(metaclass=MM): # E: Invalid metaclass 'MM'
+    y = 0
+reveal_type(A.y) # E: Revealed type is 'builtins.int'
+A.x # E: "A" has no attribute "x"
diff --git a/test-data/unit/check-python2.test b/test-data/unit/check-python2.test
--- a/test-data/unit/check-python2.test
+++ b/test-data/unit/check-python2.test
@@ -283,3 +283,12 @@ class A(object):
     __metaclass__ = m.M  # E: Name 'm' is not defined
 class B(object):
     __metaclass__ = M  # E: Name 'M' is not defined
+
+[case testMetaclassAndSkippedImportInPython2]
+# flags: --ignore-missing-imports
+from missing import M
+class A(object):
+    __metaclass__ = M
+    y = 0
+reveal_type(A.y) # E: Revealed type is 'builtins.int'
+A.x # E: "A" has no attribute "x"