From 5d3eeea2f5b866f971a89dfe21ff0710ebe08bc8 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Sat, 6 Aug 2022 00:56:58 +0300 Subject: [PATCH] Support type aliases in metaclasses (#13335) * Support type aliases in metaclasses, refs #13334 * More tests * Address review --- mypy/semanal.py | 18 +++++++++-- test-data/unit/check-classes.test | 51 +++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 597f23fc92a7..ec503d9d8ad2 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1985,15 +1985,27 @@ def analyze_metaclass(self, defn: ClassDef) -> None: if isinstance(sym.node, PlaceholderNode): self.defer(defn) return - if not isinstance(sym.node, TypeInfo) or sym.node.tuple_type is not None: + + # Support type aliases, like `_Meta: TypeAlias = type` + if ( + isinstance(sym.node, TypeAlias) + and sym.node.no_args + and isinstance(sym.node.target, ProperType) + and isinstance(sym.node.target, Instance) + ): + metaclass_info: Optional[Node] = sym.node.target.type + else: + metaclass_info = sym.node + + if not isinstance(metaclass_info, TypeInfo) or metaclass_info.tuple_type is not None: self.fail(f'Invalid metaclass "{metaclass_name}"', defn.metaclass) return - if not sym.node.is_metaclass(): + if not metaclass_info.is_metaclass(): self.fail( 'Metaclasses not inheriting from "type" are not supported', defn.metaclass ) return - inst = fill_typevars(sym.node) + inst = fill_typevars(metaclass_info) assert isinstance(inst, Instance) defn.info.declared_metaclass = inst defn.info.metaclass_type = defn.info.calculate_metaclass_type() diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 744c3d778ea5..a620c63ef58b 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -4486,6 +4486,57 @@ class A(metaclass=M): reveal_type(A.y) # N: Revealed type is "builtins.int" A.x # E: "Type[A]" has no attribute "x" +[case testValidTypeAliasAsMetaclass] +from typing_extensions import TypeAlias + +Explicit: TypeAlias = type +Implicit = type + +class E(metaclass=Explicit): ... +class I(metaclass=Implicit): ... +[builtins fixtures/classmethod.pyi] + +[case testValidTypeAliasOfTypeAliasAsMetaclass] +from typing_extensions import TypeAlias + +Explicit: TypeAlias = type +Implicit = type + +A1: TypeAlias = Explicit +A2 = Explicit +A3: TypeAlias = Implicit +A4 = Implicit + +class C1(metaclass=A1): ... +class C2(metaclass=A2): ... +class C3(metaclass=A3): ... +class C4(metaclass=A4): ... +[builtins fixtures/classmethod.pyi] + +[case testTypeAliasWithArgsAsMetaclass] +from typing import Generic, TypeVar +from typing_extensions import TypeAlias + +T = TypeVar('T') +class Meta(Generic[T]): ... + +Explicit: TypeAlias = Meta[T] +Implicit = Meta[T] + +class E(metaclass=Explicit): ... # E: Invalid metaclass "Explicit" +class I(metaclass=Implicit): ... # E: Invalid metaclass "Implicit" +[builtins fixtures/classmethod.pyi] + +[case testTypeAliasNonTypeAsMetaclass] +from typing_extensions import TypeAlias + +Explicit: TypeAlias = int +Implicit = int + +class E(metaclass=Explicit): ... # E: Metaclasses not inheriting from "type" are not supported +class I(metaclass=Implicit): ... # E: Metaclasses not inheriting from "type" are not supported +[builtins fixtures/classmethod.pyi] + [case testInvalidVariableAsMetaclass] from typing import Any M = 0 # type: int