Repository: python/mypy

Issue:
Detect `Any` used as a metaclass
Right now there's an inconsistency in how base classes / metaclasses behave when working with `Any`.

Example:

```python
from typing import Any

MyAny: Any = type('MyAny', (type,), {'a': 1})

class Subclass(MyAny):
    pass

class WithMeta(metaclass=MyAny):
    pass

reveal_type(Subclass.a)  # N: Revealed type is "Any"
reveal_type(WithMeta.a)  # E: "Type[WithMeta]" has no attribute "a" \
                         # N: Revealed type is "Any"
```

Both `Subclass.a` and `WithMeta.a` return `1` in runtime. But, for some reason `metaclass=Any` is not recognised.

It even has an existing `TODO` item: 
https://github.com/python/mypy/blob/dfbaff74f1d05c2597f48105b3c0cf974066c1fa/mypy/semanal.py#L2076-L2082

But, it was never implemented. I would love to fix it.

Related: 
- https://github.com/python/mypy/pull/13565
- https://github.com/python/mypy/pull/13579





Discussion:

Resolving patch:
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -2074,12 +2074,17 @@ def get_declared_metaclass(
                 # Probably a name error - it is already handled elsewhere
                 return None, False
             if isinstance(sym.node, Var) and isinstance(get_proper_type(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 None, False
+                # Create a fake TypeInfo that fallbacks to `Any`, basically allowing
+                # all the attributes. Same thing as we do for `Any` base class.
+                any_info = self.make_empty_type_info(ClassDef(sym.node.name, Block([])))
+                any_info.fallback_to_any = True
+                any_info._fullname = sym.node.fullname
+                if self.options.disallow_subclassing_any:
+                    self.fail(
+                        f'Class cannot use "{any_info.fullname}" as a metaclass (has type "Any")',
+                        metaclass_expr,
+                    )
+                return Instance(any_info, []), False
             if isinstance(sym.node, PlaceholderNode):
                 return None, True  # defer later in the caller
 


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
@@ -4392,6 +4392,35 @@ def f(TB: Type[B]):
     reveal_type(TB)  # N: Revealed type is "Type[__main__.B]"
     reveal_type(TB.x)  # N: Revealed type is "builtins.int"
 
+[case testMetaclassAsAny]
+from typing import Any, ClassVar
+
+MyAny: Any
+class WithMeta(metaclass=MyAny):
+    x: ClassVar[int]
+
+reveal_type(WithMeta.a)   # N: Revealed type is "Any"
+reveal_type(WithMeta.m)   # N: Revealed type is "Any"
+reveal_type(WithMeta.x)   # N: Revealed type is "builtins.int"
+reveal_type(WithMeta().x) # N: Revealed type is "builtins.int"
+WithMeta().m              # E: "WithMeta" has no attribute "m"
+WithMeta().a              # E: "WithMeta" has no attribute "a"
+
+[case testMetaclassAsAnyWithAFlag]
+# flags: --disallow-subclassing-any
+from typing import Any, ClassVar
+
+MyAny: Any
+class WithMeta(metaclass=MyAny):  # E: Class cannot use "__main__.MyAny" as a metaclass (has type "Any")
+    x: ClassVar[int]
+
+reveal_type(WithMeta.a)   # N: Revealed type is "Any"
+reveal_type(WithMeta.m)   # N: Revealed type is "Any"
+reveal_type(WithMeta.x)   # N: Revealed type is "builtins.int"
+reveal_type(WithMeta().x) # N: Revealed type is "builtins.int"
+WithMeta().m              # E: "WithMeta" has no attribute "m"
+WithMeta().a              # E: "WithMeta" has no attribute "a"
+
 [case testMetaclassIterable]
 from typing import Iterable, Iterator
 
@@ -4476,15 +4505,7 @@ from missing import M
 class A(metaclass=M):
     y = 0
 reveal_type(A.y) # N: Revealed type is "builtins.int"
-A.x # E: "Type[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) # N: Revealed type is "builtins.int"
-A.x # E: "Type[A]" has no attribute "x"
+reveal_type(A.x) # N: Revealed type is "Any"
 
 [case testValidTypeAliasAsMetaclass]
 from typing_extensions import TypeAlias