From 56b7af60873cd7a1f3841f6af5f08f652fa0f8cf Mon Sep 17 00:00:00 2001 From: Robsdedude Date: Mon, 14 Nov 2022 13:03:01 +0100 Subject: [PATCH 01/10] Improve error message of metaclass conflict --- mypy/checker.py | 22 +++++++++++++++++++--- test-data/unit/check-classes.test | 20 ++++++++++---------- test-data/unit/fine-grained.test | 12 ++++++++---- 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index db65660bbfbdc..68b27182cb07e 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2809,7 +2809,7 @@ class C(B, A[int]): ... # this is unsafe because... self.msg.base_class_definitions_incompatible(name, base1, base2, ctx) def check_metaclass_compatibility(self, typ: TypeInfo) -> None: - """Ensures that metaclasses of all parent types are compatible.""" + """Ensure that metaclasses of all parent types are compatible.""" if ( typ.is_metaclass() or typ.is_protocol @@ -2831,9 +2831,25 @@ def check_metaclass_compatibility(self, typ: TypeInfo) -> None: is_subtype(typ.metaclass_type, meta) for meta in metaclasses ): return + if typ.declared_metaclass is None: + metaclass_names = { # using a dict as ordered set + str(meta): None for meta in metaclasses + }.keys() + conflict_info = f"found metaclasses of bases: {', '.join(metaclass_names)}" + else: + uncovered_metaclass_names = { # using a dict as ordered set + str(meta): None + for meta in metaclasses + if not is_subtype(typ.declared_metaclass, meta) + }.keys() + conflict_info = ( + f"own metaclass {typ.declared_metaclass} is not a subclass of " + f"{', '.join(uncovered_metaclass_names)}" + ) self.fail( - "Metaclass conflict: the metaclass of a derived class must be " - "a (non-strict) subclass of the metaclasses of all its bases", + "Metaclass conflict: the metaclass of a derived class must be a " + "(non-strict) subclass of the metaclasses of all its bases - " + f"{conflict_info}", typ, ) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 82208d27df41a..f9469caff5e6e 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -4558,7 +4558,7 @@ class C(B): class X(type): pass class Y(type): pass class A(metaclass=X): pass -class B(A, metaclass=Y): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases +class B(A, metaclass=Y): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases - own metaclass __main__.Y is not a subclass of __main__.X [case testMetaclassNoTypeReveal] class M: @@ -5552,8 +5552,8 @@ class CD(six.with_metaclass(M)): pass # E: Multiple metaclass definitions class M1(type): pass class Q1(metaclass=M1): pass @six.add_metaclass(M) -class CQA(Q1): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases -class CQW(six.with_metaclass(M, Q1)): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases +class CQA(Q1): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases - own metaclass __main__.M is not a subclass of __main__.M1 +class CQW(six.with_metaclass(M, Q1)): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases - own metaclass __main__.M is not a subclass of __main__.M1 [builtins fixtures/tuple.pyi] [case testSixMetaclassAny] @@ -5671,7 +5671,7 @@ class C5(future.utils.with_metaclass(f())): pass # E: Dynamic metaclass not sup class M1(type): pass class Q1(metaclass=M1): pass -class CQW(future.utils.with_metaclass(M, Q1)): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases +class CQW(future.utils.with_metaclass(M, Q1)): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases - own metaclass __main__.M is not a subclass of __main__.M1 [builtins fixtures/tuple.pyi] [case testFutureMetaclassAny] @@ -7100,17 +7100,17 @@ class ChildOfCorrectSubclass1(CorrectSubclass1): ... class CorrectWithType1(C, A1): ... class CorrectWithType2(B, C): ... -class Conflict1(A1, B, E): ... # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases -class Conflict2(A, B): ... # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases -class Conflict3(B, A): ... # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases +class Conflict1(A1, B, E): ... # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases - found metaclasses of bases: __main__.MyMeta1, __main__.MyMeta2 +class Conflict2(A, B): ... # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases - found metaclasses of bases: __main__.MyMeta1, __main__.MyMeta2 +class Conflict3(B, A): ... # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases - found metaclasses of bases: __main__.MyMeta2, __main__.MyMeta1 -class ChildOfConflict1(Conflict3): ... # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases +class ChildOfConflict1(Conflict3): ... # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases - found metaclasses of bases: __main__.MyMeta2, __main__.MyMeta1 class ChildOfConflict2(Conflict3, metaclass=CorrectMeta): ... class ConflictingMeta(MyMeta1, MyMeta3): ... -class Conflict4(A1, B, E, metaclass=ConflictingMeta): ... # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases +class Conflict4(A1, B, E, metaclass=ConflictingMeta): ... # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases - own metaclass __main__.ConflictingMeta is not a subclass of __main__.MyMeta2 -class ChildOfCorrectButWrongMeta(CorrectSubclass1, metaclass=ConflictingMeta): # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases +class ChildOfCorrectButWrongMeta(CorrectSubclass1, metaclass=ConflictingMeta): # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases - own metaclass __main__.ConflictingMeta is not a subclass of __main__.CorrectMeta, __main__.MyMeta2 ... [case testGenericOverride] diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 2ad31311a4022..40ae232354e81 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -2936,10 +2936,12 @@ a.py:6: error: Argument 1 to "f" has incompatible type "Type[B]"; expected "M" [case testFineMetaclassRecalculation] import a + [file a.py] from b import B class M2(type): pass class D(B, metaclass=M2): pass + [file b.py] import c class B: pass @@ -2949,27 +2951,29 @@ import c class B(metaclass=c.M): pass [file c.py] -class M(type): - pass +class M(type): pass [out] == -a.py:3: error: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases +a.py:3: error: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases - own metaclass a.M2 is not a subclass of c.M [case testFineMetaclassDeclaredUpdate] import a + [file a.py] import b class B(metaclass=b.M): pass class D(B, metaclass=b.M2): pass + [file b.py] class M(type): pass class M2(M): pass + [file b.py.2] class M(type): pass class M2(type): pass [out] == -a.py:3: error: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases +a.py:3: error: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases - own metaclass b.M2 is not a subclass of b.M [case testFineMetaclassRemoveFromClass] import a From be3a64604f46e471a44af41a303ec5f9ca203ad2 Mon Sep 17 00:00:00 2001 From: Robsdedude Date: Mon, 14 Nov 2022 14:50:08 +0100 Subject: [PATCH 02/10] Introduce new error code `metaclass` --- docs/source/error_code_list.rst | 26 ++++++++++++++++++++++++ mypy/checker.py | 1 + mypy/errorcodes.py | 5 +++++ mypy/semanal.py | 11 ++++++---- test-data/unit/check-errorcodes.test | 30 ++++++++++++++++++++++++++++ 5 files changed, 69 insertions(+), 4 deletions(-) diff --git a/docs/source/error_code_list.rst b/docs/source/error_code_list.rst index 85c8d437a856f..9c8e3871950e0 100644 --- a/docs/source/error_code_list.rst +++ b/docs/source/error_code_list.rst @@ -221,6 +221,32 @@ You can use :py:data:`~typing.Callable` as the type for callable objects: for x in objs: f(x) +.. _code-metaclass: + +Check the validity of a class's metaclass [metaclass] +----------------------------------------------------- + +Mypy checks whether the metaclass of a class is valid. The metaclass +must be a subclass of ``type``. Further, the class hierarchy must yield +a consistent metaclass. For more details, see the +`Python documentation `_ + +Example with an error: + +.. code-block:: python + + class GoodMeta(type): + pass + + class BadMeta: + pass + + class A1(metaclass=GoodMeta): # OK + pass + + class A2(metaclass=BadMeta): # Error: Metaclasses not inheriting from "type" are not supported [metaclass] + pass + .. _code-var-annotated: Require annotation if variable type is unclear [var-annotated] diff --git a/mypy/checker.py b/mypy/checker.py index 68b27182cb07e..b7a16ccdbbd06 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2851,6 +2851,7 @@ def check_metaclass_compatibility(self, typ: TypeInfo) -> None: "(non-strict) subclass of the metaclasses of all its bases - " f"{conflict_info}", typ, + code=codes.METACLASS, ) def visit_import_from(self, node: ImportFrom) -> None: diff --git a/mypy/errorcodes.py b/mypy/errorcodes.py index 6e8763264ddd3..2c46a3f7dd559 100644 --- a/mypy/errorcodes.py +++ b/mypy/errorcodes.py @@ -261,6 +261,11 @@ def __hash__(self) -> int: "General", default_enabled=False, ) +METACLASS: Final[ErrorCode] = ErrorCode( + "metaclass", + "Ensure that metaclass is valid", + "General", +) # Syntax errors are often blocking. SYNTAX: Final[ErrorCode] = ErrorCode("syntax", "Report syntax errors", "General") diff --git a/mypy/semanal.py b/mypy/semanal.py index 782985e3fbab1..edd6bfd02a571 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2592,7 +2592,7 @@ def infer_metaclass_and_bases_from_compat_helpers(self, defn: ClassDef) -> None: if len(metas) == 0: return if len(metas) > 1: - self.fail("Multiple metaclass definitions", defn) + self.fail("Multiple metaclass definitions", defn, code=codes.METACLASS) return defn.metaclass = metas.pop() @@ -2648,7 +2648,7 @@ def get_declared_metaclass( elif isinstance(metaclass_expr, MemberExpr): metaclass_name = get_member_expr_fullname(metaclass_expr) if metaclass_name is None: - self.fail(f'Dynamic metaclass not supported for "{name}"', metaclass_expr) + self.fail(f'Dynamic metaclass not supported for "{name}"', metaclass_expr, code=codes.METACLASS) return None, False, True sym = self.lookup_qualified(metaclass_name, metaclass_expr) if sym is None: @@ -2659,6 +2659,7 @@ def get_declared_metaclass( self.fail( f'Class cannot use "{sym.node.name}" as a metaclass (has type "Any")', metaclass_expr, + code=codes.METACLASS, ) return None, False, True if isinstance(sym.node, PlaceholderNode): @@ -2676,11 +2677,13 @@ def get_declared_metaclass( 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}"', metaclass_expr) + self.fail(f'Invalid metaclass "{metaclass_name}"', metaclass_expr, code=codes.METACLASS) return None, False, False if not metaclass_info.is_metaclass(): self.fail( - 'Metaclasses not inheriting from "type" are not supported', metaclass_expr + 'Metaclasses not inheriting from "type" are not supported', + metaclass_expr, + code=codes.METACLASS, ) return None, False, False inst = fill_typevars(metaclass_info) diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index c4d72388fba9d..17ebb91e46847 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -1195,4 +1195,34 @@ from typing_extensions import TypeIs def f(x: str) -> TypeIs[int]: # E: Narrowed type "int" is not a subtype of input type "str" [narrowed-type-not-subtype] pass +[case testDynamicMetaclass] +class A(metaclass=type(tuple)): pass # E: Dynamic metaclass not supported for "A" [metaclass] + +[case testMetaclassOfTypeAny] +# mypy: disallow-subclassing-any=True +from typing import Any +foo: Any = ... +class A(metaclass=foo): pass # E: Class cannot use "foo" as a metaclass (has type "Any") [metaclass] + +[case testMetaclassOfWrongType] +class Foo: + bar = 1 +class A2(metaclass=Foo.bar): pass # E: Invalid metaclass "Foo.bar" [metaclass] + +[case testMetaclassNotTypeSubclass] +class M: pass +class A(metaclass=M): pass # E: Metaclasses not inheriting from "type" are not supported [metaclass] + +[case testMultipleMetaclasses] +import six +class M1(type): pass + +@six.add_metaclass(M1) +class A1(metaclass=M1): pass # E: Multiple metaclass definitions [metaclass] + +class A2(six.with_metaclass(M1), metaclass=M1): pass # E: Multiple metaclass definitions [metaclass] + +@six.add_metaclass(M1) +class A3(six.with_metaclass(M1)): pass # E: Multiple metaclass definitions [metaclass] + [builtins fixtures/tuple.pyi] From 0756cbc5fc144bb498819fdcafde1865027e511d Mon Sep 17 00:00:00 2001 From: Robsdedude Date: Fri, 16 Aug 2024 12:06:08 +0200 Subject: [PATCH 03/10] Docs: how to work around metaclass limitation --- docs/source/error_code_list.rst | 3 +++ docs/source/metaclasses.rst | 31 +++++++++++++++++++++++++++++++ mypy/checker.py | 2 +- mypy/errorcodes.py | 6 +----- mypy/semanal.py | 10 ++++++++-- 5 files changed, 44 insertions(+), 8 deletions(-) diff --git a/docs/source/error_code_list.rst b/docs/source/error_code_list.rst index 9c8e3871950e0..b197fabbdbc24 100644 --- a/docs/source/error_code_list.rst +++ b/docs/source/error_code_list.rst @@ -231,6 +231,9 @@ must be a subclass of ``type``. Further, the class hierarchy must yield a consistent metaclass. For more details, see the `Python documentation `_ +Note that mypy's metaclass checking is limited and may produce false-positives. +See also :ref:`limitations`. + Example with an error: .. code-block:: python diff --git a/docs/source/metaclasses.rst b/docs/source/metaclasses.rst index 396d7dbb42cc0..cb73c1d13e61d 100644 --- a/docs/source/metaclasses.rst +++ b/docs/source/metaclasses.rst @@ -86,3 +86,34 @@ so it's better not to combine metaclasses and class hierarchies: such as ``class A(metaclass=f()): ...`` * Mypy does not and cannot understand arbitrary metaclass code. * Mypy only recognizes subclasses of :py:class:`type` as potential metaclasses. + +For some builtin types, mypy assumes that their metaclass is :py:class:`abc.ABCMeta` +even if it's :py:class:`type`. In those cases, you can either + +* use :py:class:`abc.ABCMetaclass` instead of :py:class:`type` as the + superclass of your metaclass if that works in your use case, +* mute the error with ``# type: ignore[metaclass]``, or +* compute the metaclass' superclass dynamically, which mypy doesn't understand + so it will also need to be muted. + +.. code-block:: python + + import abc + + assert type(tuple) is type # metaclass of tuple is type + + # the problem: + class M0(type): pass + class A0(tuple, metaclass=M1): pass # Mypy Error: metaclass conflict + + # option 1: use ABCMeta instead of type + class M1(abc.ABCMeta): pass + class A1(tuple, metaclass=M1): pass + + # option 2: mute the error + class M2(type): pass + class A2(tuple, metaclass=M2): pass # type: ignore[metaclass] + + # option 3: compute the metaclass dynamically + class M3(type(tuple)): pass # type: ignore[metaclass] + class A3(tuple, metaclass=M3): pass diff --git a/mypy/checker.py b/mypy/checker.py index b7a16ccdbbd06..36a2075b79e37 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2809,7 +2809,7 @@ class C(B, A[int]): ... # this is unsafe because... self.msg.base_class_definitions_incompatible(name, base1, base2, ctx) def check_metaclass_compatibility(self, typ: TypeInfo) -> None: - """Ensure that metaclasses of all parent types are compatible.""" + """Ensures that metaclasses of all parent types are compatible.""" if ( typ.is_metaclass() or typ.is_protocol diff --git a/mypy/errorcodes.py b/mypy/errorcodes.py index 2c46a3f7dd559..dc908a70fa844 100644 --- a/mypy/errorcodes.py +++ b/mypy/errorcodes.py @@ -261,11 +261,7 @@ def __hash__(self) -> int: "General", default_enabled=False, ) -METACLASS: Final[ErrorCode] = ErrorCode( - "metaclass", - "Ensure that metaclass is valid", - "General", -) +METACLASS: Final[ErrorCode] = ErrorCode("metaclass", "Ensure that metaclass is valid", "General") # Syntax errors are often blocking. SYNTAX: Final[ErrorCode] = ErrorCode("syntax", "Report syntax errors", "General") diff --git a/mypy/semanal.py b/mypy/semanal.py index edd6bfd02a571..18b0b10909fa3 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -2648,7 +2648,11 @@ def get_declared_metaclass( elif isinstance(metaclass_expr, MemberExpr): metaclass_name = get_member_expr_fullname(metaclass_expr) if metaclass_name is None: - self.fail(f'Dynamic metaclass not supported for "{name}"', metaclass_expr, code=codes.METACLASS) + self.fail( + f'Dynamic metaclass not supported for "{name}"', + metaclass_expr, + code=codes.METACLASS, + ) return None, False, True sym = self.lookup_qualified(metaclass_name, metaclass_expr) if sym is None: @@ -2677,7 +2681,9 @@ def get_declared_metaclass( 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}"', metaclass_expr, code=codes.METACLASS) + self.fail( + f'Invalid metaclass "{metaclass_name}"', metaclass_expr, code=codes.METACLASS + ) return None, False, False if not metaclass_info.is_metaclass(): self.fail( From 0d2fd528be22d0a500972329b0e26fcc87530d44 Mon Sep 17 00:00:00 2001 From: Robsdedude Date: Fri, 16 Aug 2024 15:46:24 +0200 Subject: [PATCH 04/10] Docs: fix reference --- docs/source/metaclasses.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/metaclasses.rst b/docs/source/metaclasses.rst index cb73c1d13e61d..fe82399a39956 100644 --- a/docs/source/metaclasses.rst +++ b/docs/source/metaclasses.rst @@ -90,7 +90,7 @@ so it's better not to combine metaclasses and class hierarchies: For some builtin types, mypy assumes that their metaclass is :py:class:`abc.ABCMeta` even if it's :py:class:`type`. In those cases, you can either -* use :py:class:`abc.ABCMetaclass` instead of :py:class:`type` as the +* use :py:class:`abc.ABCMeta` instead of :py:class:`type` as the superclass of your metaclass if that works in your use case, * mute the error with ``# type: ignore[metaclass]``, or * compute the metaclass' superclass dynamically, which mypy doesn't understand From 969562df6918b99bc7e30530c2ac7ea7e8705dc1 Mon Sep 17 00:00:00 2001 From: Robsdedude Date: Fri, 16 Aug 2024 15:49:09 +0200 Subject: [PATCH 05/10] Tests: fix missing fixtures --- test-data/unit/check-errorcodes.test | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index 17ebb91e46847..f5daceaf7322d 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -1195,8 +1195,11 @@ from typing_extensions import TypeIs def f(x: str) -> TypeIs[int]: # E: Narrowed type "int" is not a subtype of input type "str" [narrowed-type-not-subtype] pass +[builtins fixtures/tuple.pyi] + [case testDynamicMetaclass] class A(metaclass=type(tuple)): pass # E: Dynamic metaclass not supported for "A" [metaclass] +[builtins fixtures/tuple.pyi] [case testMetaclassOfTypeAny] # mypy: disallow-subclassing-any=True @@ -1224,5 +1227,3 @@ class A2(six.with_metaclass(M1), metaclass=M1): pass # E: Multiple metaclass de @six.add_metaclass(M1) class A3(six.with_metaclass(M1)): pass # E: Multiple metaclass definitions [metaclass] - -[builtins fixtures/tuple.pyi] From b6c8eb92086aba1a3f83b7acb0adf027b3b216db Mon Sep 17 00:00:00 2001 From: Robsdedude Date: Mon, 26 Aug 2024 15:14:06 +0200 Subject: [PATCH 06/10] Fix tests: missing tuple type fixture --- test-data/unit/check-errorcodes.test | 1 + 1 file changed, 1 insertion(+) diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index f5daceaf7322d..f90c70a57fa75 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -1227,3 +1227,4 @@ class A2(six.with_metaclass(M1), metaclass=M1): pass # E: Multiple metaclass de @six.add_metaclass(M1) class A3(six.with_metaclass(M1)): pass # E: Multiple metaclass definitions [metaclass] +[builtins fixtures/tuple.pyi] From 79ab8b8bdba4c0e04aea28905a34577069e12559 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Mon, 30 Jun 2025 16:07:19 -0700 Subject: [PATCH 07/10] use note --- mypy/checker.py | 8 +++--- mypy/nodes.py | 2 +- test-data/unit/check-classes.test | 40 +++++++++++++++++++--------- test-data/unit/check-errorcodes.test | 4 ++- 4 files changed, 35 insertions(+), 19 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 6be4136ea6e15..7859934c1ef70 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2955,15 +2955,15 @@ def check_metaclass_compatibility(self, typ: TypeInfo) -> None: if typ.metaclass_type is None and any( base.type.metaclass_type is not None for base in typ.bases ): - explanation = typ.explain_metaclass_conflict() or "" - if explanation: - explanation = f" - {explanation}" self.fail( "Metaclass conflict: the metaclass of a derived class must be " - f"a (non-strict) subclass of the metaclasses of all its bases{explanation}", + "a (non-strict) subclass of the metaclasses of all its bases", typ, code=codes.METACLASS, ) + explanation = typ.explain_metaclass_conflict() + if explanation: + self.note(explanation, typ, code=codes.METACLASS) def visit_import_from(self, node: ImportFrom) -> None: for name, _ in node.names: diff --git a/mypy/nodes.py b/mypy/nodes.py index 7d5733f042075..e9d39a92b07dc 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -3434,7 +3434,7 @@ def explain_metaclass_conflict(self) -> str | None: continue # metaclass conflict conflict = f"{super_meta.type.fullname} (metaclass of {super_class.fullname})" - return f"{' > '.join(resolution_steps)} conflicting with {conflict}" + return f"{' > '.join(resolution_steps)} conflicts with {conflict}" return None diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index f7b551dbbe8f6..618e763d95f93 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -4757,7 +4757,9 @@ class C(B): class X(type): pass class Y(type): pass class A(metaclass=X): pass -class B(A, metaclass=Y): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases - __main__.Y (meta of __main__.B) conflicting with __main__.X (metaclass of __main__.A) +class B(A, metaclass=Y): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases \ + # N: __main__.Y (meta of __main__.B) conflicts with __main__.X (metaclass of __main__.A) + [case testMetaclassNoTypeReveal] class M: @@ -5737,8 +5739,8 @@ def f() -> type: return M class C1(six.with_metaclass(M), object): pass # E: Unsupported dynamic base class "six.with_metaclass" class C2(C1, six.with_metaclass(M)): pass # E: Unsupported dynamic base class "six.with_metaclass" class C3(six.with_metaclass(A)): pass # E: Metaclasses not inheriting from "type" are not supported -@six.add_metaclass(A) # E: Metaclasses not inheriting from "type" are not supported \ - # E: Argument 1 to "add_metaclass" has incompatible type "type[A]"; expected "type[type]" +@six.add_metaclass(A) # E: Metaclasses not inheriting from "type" are not supported \ + # E: Argument 1 to "add_metaclass" has incompatible type "type[A]"; expected "type[type]" class D3(A): pass class C4(six.with_metaclass(M), metaclass=M): pass # E: Multiple metaclass definitions @@ -5754,8 +5756,11 @@ class CD(six.with_metaclass(M)): pass # E: Multiple metaclass definitions class M1(type): pass class Q1(metaclass=M1): pass @six.add_metaclass(M) -class CQA(Q1): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases - __main__.M (meta of __main__.CQA) conflicting with __main__.M1 (metaclass of __main__.Q1) -class CQW(six.with_metaclass(M, Q1)): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases - __main__.M (meta of __main__.CQW) conflicting with __main__.M1 (metaclass of __main__.Q1) +class CQA(Q1): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases \ + # N: __main__.M (meta of __main__.CQA) conflicts with __main__.M1 (metaclass of __main__.Q1) +class CQW(six.with_metaclass(M, Q1)): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases \ + # N: __main__.M (meta of __main__.CQW) conflicts with __main__.M1 (metaclass of __main__.Q1) + [builtins fixtures/tuple.pyi] [case testSixMetaclassAny] @@ -5873,7 +5878,9 @@ class C5(future.utils.with_metaclass(f())): pass # E: Dynamic metaclass not sup class M1(type): pass class Q1(metaclass=M1): pass -class CQW(future.utils.with_metaclass(M, Q1)): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases - __main__.M (meta of __main__.CQW) conflicting with __main__.M1 (metaclass of __main__.Q1) +class CQW(future.utils.with_metaclass(M, Q1)): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases \ + # N: __main__.M (meta of __main__.CQW) conflicts with __main__.M1 (metaclass of __main__.Q1) + [builtins fixtures/tuple.pyi] [case testFutureMetaclassAny] @@ -7342,17 +7349,22 @@ class ChildOfCorrectSubclass1(CorrectSubclass1): ... class CorrectWithType1(C, A1): ... class CorrectWithType2(B, C): ... -class Conflict1(A1, B, E): ... # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases - __main__.MyMeta1 (metaclass of __main__.A) conflicting with __main__.MyMeta2 (metaclass of __main__.B) -class Conflict2(A, B): ... # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases - __main__.MyMeta1 (metaclass of __main__.A) conflicting with __main__.MyMeta2 (metaclass of __main__.B) -class Conflict3(B, A): ... # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases - __main__.MyMeta2 (metaclass of __main__.B) conflicting with __main__.MyMeta1 (metaclass of __main__.A) +class Conflict1(A1, B, E): ... # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases \ + # N: __main__.MyMeta1 (metaclass of __main__.A) conflicts with __main__.MyMeta2 (metaclass of __main__.B) +class Conflict2(A, B): ... # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases \ + # N: __main__.MyMeta1 (metaclass of __main__.A) conflicts with __main__.MyMeta2 (metaclass of __main__.B) +class Conflict3(B, A): ... # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases \ + # N: __main__.MyMeta2 (metaclass of __main__.B) conflicts with __main__.MyMeta1 (metaclass of __main__.A) class ChildOfConflict1(Conflict3): ... class ChildOfConflict2(Conflict3, metaclass=CorrectMeta): ... class ConflictingMeta(MyMeta1, MyMeta3): ... -class Conflict4(A1, B, E, metaclass=ConflictingMeta): ... # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases - __main__.ConflictingMeta (meta of __main__.Conflict4) conflicting with __main__.MyMeta2 (metaclass of __main__.B) +class Conflict4(A1, B, E, metaclass=ConflictingMeta): ... # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases \ + # N: __main__.ConflictingMeta (meta of __main__.Conflict4) conflicts with __main__.MyMeta2 (metaclass of __main__.B) -class ChildOfCorrectButWrongMeta(CorrectSubclass1, metaclass=ConflictingMeta): # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases - __main__.ConflictingMeta (meta of __main__.ChildOfCorrectButWrongMeta) conflicting with __main__.CorrectMeta (metaclass of __main__.CorrectSubclass1) +class ChildOfCorrectButWrongMeta(CorrectSubclass1, metaclass=ConflictingMeta): # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases \ + # N: __main__.ConflictingMeta (meta of __main__.ChildOfCorrectButWrongMeta) conflicts with __main__.CorrectMeta (metaclass of __main__.CorrectSubclass1) ... [case testMetaClassConflictIssue14033] @@ -7367,8 +7379,10 @@ class B1(metaclass=M2): pass class C1(metaclass=Mx): pass -class TestABC(A2, B1, C1): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases - __main__.M1 (metaclass of __main__.A1) conflicting with __main__.M2 (metaclass of __main__.B1) -class TestBAC(B1, A2, C1): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases - __main__.M2 (metaclass of __main__.B1) conflicting with __main__.M1 (metaclass of __main__.A1) +class TestABC(A2, B1, C1): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases \ + # N: __main__.M1 (metaclass of __main__.A1) conflicts with __main__.M2 (metaclass of __main__.B1) +class TestBAC(B1, A2, C1): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases \ + # N: __main__.M2 (metaclass of __main__.B1) conflicts with __main__.M1 (metaclass of __main__.A1) # should not warn again for children class ChildOfTestABC(TestABC): pass diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index 4ad06a1561a69..80ca1df274af5 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -1275,7 +1275,9 @@ class A3(six.with_metaclass(M1)): pass # E: Multiple metaclass definitions [me class X(type): pass class Y(type): pass class A(metaclass=X): pass -class B(A, metaclass=Y): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases - __main__.Y (meta of __main__.B) conflicting with __main__.X (metaclass of __main__.A) [metaclass] +class B(A, metaclass=Y): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases [metaclass] \ + # N: __main__.Y (meta of __main__.B) conflicts with __main__.X (metaclass of __main__.A) + [case testOverloadedFunctionSignature] from typing import overload, Union From be8fdc45d08a7ace38e3882be89786b4b958cde6 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Mon, 30 Jun 2025 16:12:19 -0700 Subject: [PATCH 08/10] fix docs nits --- docs/source/metaclasses.rst | 24 +++++++++--------------- mypy/nodes.py | 1 + 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/docs/source/metaclasses.rst b/docs/source/metaclasses.rst index e4f7cfbab43b6..e30dfe80f9f95 100644 --- a/docs/source/metaclasses.rst +++ b/docs/source/metaclasses.rst @@ -91,33 +91,27 @@ so it's better not to combine metaclasses and class hierarchies: .. _PEP 673: https://peps.python.org/pep-0673/#valid-locations-for-self -For some builtin types, mypy assumes that their metaclass is :py:class:`abc.ABCMeta` -even if it's :py:class:`type`. In those cases, you can either +For some builtin types, mypy may think their metaclass is :py:class:`abc.ABCMeta` +even if it is :py:class:`type` at runtime. In those cases, you can either: * use :py:class:`abc.ABCMeta` instead of :py:class:`type` as the - superclass of your metaclass if that works in your use-case, -* mute the error with ``# type: ignore[metaclass]``, or -* compute the metaclass' superclass dynamically, which mypy doesn't understand - so it will also need to be muted. + superclass of your metaclass if that works in your use-case +* mute the error with ``# type: ignore[metaclass]`` .. code-block:: python import abc - assert type(tuple) is type # metaclass of tuple is type + assert type(tuple) is type # metaclass of tuple is type at runtime - # the problem: + # The problem: class M0(type): pass - class A0(tuple, metaclass=M1): pass # Mypy Error: metaclass conflict + class A0(tuple, metaclass=M0): pass # Mypy Error: metaclass conflict - # option 1: use ABCMeta instead of type + # Option 1: use ABCMeta instead of type class M1(abc.ABCMeta): pass class A1(tuple, metaclass=M1): pass - # option 2: mute the error + # Option 2: mute the error class M2(type): pass class A2(tuple, metaclass=M2): pass # type: ignore[metaclass] - - # option 3: compute the metaclass dynamically - class M3(type(tuple)): pass # type: ignore[metaclass] - class A3(tuple, metaclass=M3): pass diff --git a/mypy/nodes.py b/mypy/nodes.py index e9d39a92b07dc..5b8ccc790bda3 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -3403,6 +3403,7 @@ def calculate_metaclass_type(self) -> mypy.types.Instance | None: return winner def explain_metaclass_conflict(self) -> str | None: + # Compare to logic in calculate_metaclass_type declared = self.declared_metaclass if declared is not None and not declared.type.has_base("builtins.type"): return None From 570b9f6be4858b3419e89c8c9baee93fc24d7647 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Mon, 30 Jun 2025 16:14:34 -0700 Subject: [PATCH 09/10] update another test --- test-data/unit/fine-grained.test | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index b8ad28a5d4212..443a4c38b753f 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -2954,7 +2954,8 @@ class B(metaclass=c.M): pass class M(type): pass [out] == -a.py:3: error: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases - a.M2 (meta of a.D) conflicting with c.M (metaclass of b.B) +a.py:3: error: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases +a.py:3: note: a.M2 (meta of a.D) conflicts with c.M (metaclass of b.B) [case testFineMetaclassDeclaredUpdate] import a @@ -2973,7 +2974,8 @@ class M(type): pass class M2(type): pass [out] == -a.py:3: error: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases - b.M2 (meta of a.D) conflicting with b.M (metaclass of a.B) +a.py:3: error: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases +a.py:3: note: b.M2 (meta of a.D) conflicts with b.M (metaclass of a.B) [case testFineMetaclassRemoveFromClass] import a From 4aed6088bfb430d7f50f58cafbb01f6d74d41351 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Mon, 30 Jun 2025 17:32:55 -0700 Subject: [PATCH 10/10] consistent quoting --- mypy/nodes.py | 8 ++++---- test-data/unit/check-classes.test | 26 +++++++++++--------------- test-data/unit/check-errorcodes.test | 4 +++- test-data/unit/fine-grained.test | 4 ++-- 4 files changed, 20 insertions(+), 22 deletions(-) diff --git a/mypy/nodes.py b/mypy/nodes.py index 5b8ccc790bda3..fc2656ce2130e 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -3414,7 +3414,7 @@ def explain_metaclass_conflict(self) -> str | None: if declared is None: resolution_steps = [] else: - resolution_steps = [f"{declared.type.fullname} (meta of {self.fullname})"] + resolution_steps = [f'"{declared.type.fullname}" (metaclass of "{self.fullname}")'] for super_class in self.mro[1:]: super_meta = super_class.declared_metaclass if super_meta is None or super_meta.type is None: @@ -3422,7 +3422,7 @@ def explain_metaclass_conflict(self) -> str | None: if winner is None: winner = super_meta resolution_steps.append( - f"{winner.type.fullname} (metaclass of {super_class.fullname})" + f'"{winner.type.fullname}" (metaclass of "{super_class.fullname}")' ) continue if winner.type.has_base(super_meta.type.fullname): @@ -3430,11 +3430,11 @@ def explain_metaclass_conflict(self) -> str | None: if super_meta.type.has_base(winner.type.fullname): winner = super_meta resolution_steps.append( - f"{winner.type.fullname} (metaclass of {super_class.fullname})" + f'"{winner.type.fullname}" (metaclass of "{super_class.fullname}")' ) continue # metaclass conflict - conflict = f"{super_meta.type.fullname} (metaclass of {super_class.fullname})" + conflict = f'"{super_meta.type.fullname}" (metaclass of "{super_class.fullname}")' return f"{' > '.join(resolution_steps)} conflicts with {conflict}" return None diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 618e763d95f93..173657620304c 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -4758,9 +4758,7 @@ class X(type): pass class Y(type): pass class A(metaclass=X): pass class B(A, metaclass=Y): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases \ - # N: __main__.Y (meta of __main__.B) conflicts with __main__.X (metaclass of __main__.A) - - + # N: "__main__.Y" (metaclass of "__main__.B") conflicts with "__main__.X" (metaclass of "__main__.A") [case testMetaclassNoTypeReveal] class M: x = 0 # type: int @@ -5757,10 +5755,9 @@ class M1(type): pass class Q1(metaclass=M1): pass @six.add_metaclass(M) class CQA(Q1): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases \ - # N: __main__.M (meta of __main__.CQA) conflicts with __main__.M1 (metaclass of __main__.Q1) + # N: "__main__.M" (metaclass of "__main__.CQA") conflicts with "__main__.M1" (metaclass of "__main__.Q1") class CQW(six.with_metaclass(M, Q1)): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases \ - # N: __main__.M (meta of __main__.CQW) conflicts with __main__.M1 (metaclass of __main__.Q1) - + # N: "__main__.M" (metaclass of "__main__.CQW") conflicts with "__main__.M1" (metaclass of "__main__.Q1") [builtins fixtures/tuple.pyi] [case testSixMetaclassAny] @@ -5879,8 +5876,7 @@ class C5(future.utils.with_metaclass(f())): pass # E: Dynamic metaclass not sup class M1(type): pass class Q1(metaclass=M1): pass class CQW(future.utils.with_metaclass(M, Q1)): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases \ - # N: __main__.M (meta of __main__.CQW) conflicts with __main__.M1 (metaclass of __main__.Q1) - + # N: "__main__.M" (metaclass of "__main__.CQW") conflicts with "__main__.M1" (metaclass of "__main__.Q1") [builtins fixtures/tuple.pyi] [case testFutureMetaclassAny] @@ -7350,21 +7346,21 @@ class CorrectWithType1(C, A1): ... class CorrectWithType2(B, C): ... class Conflict1(A1, B, E): ... # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases \ - # N: __main__.MyMeta1 (metaclass of __main__.A) conflicts with __main__.MyMeta2 (metaclass of __main__.B) + # N: "__main__.MyMeta1" (metaclass of "__main__.A") conflicts with "__main__.MyMeta2" (metaclass of "__main__.B") class Conflict2(A, B): ... # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases \ - # N: __main__.MyMeta1 (metaclass of __main__.A) conflicts with __main__.MyMeta2 (metaclass of __main__.B) + # N: "__main__.MyMeta1" (metaclass of "__main__.A") conflicts with "__main__.MyMeta2" (metaclass of "__main__.B") class Conflict3(B, A): ... # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases \ - # N: __main__.MyMeta2 (metaclass of __main__.B) conflicts with __main__.MyMeta1 (metaclass of __main__.A) + # N: "__main__.MyMeta2" (metaclass of "__main__.B") conflicts with "__main__.MyMeta1" (metaclass of "__main__.A") class ChildOfConflict1(Conflict3): ... class ChildOfConflict2(Conflict3, metaclass=CorrectMeta): ... class ConflictingMeta(MyMeta1, MyMeta3): ... class Conflict4(A1, B, E, metaclass=ConflictingMeta): ... # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases \ - # N: __main__.ConflictingMeta (meta of __main__.Conflict4) conflicts with __main__.MyMeta2 (metaclass of __main__.B) + # N: "__main__.ConflictingMeta" (metaclass of "__main__.Conflict4") conflicts with "__main__.MyMeta2" (metaclass of "__main__.B") class ChildOfCorrectButWrongMeta(CorrectSubclass1, metaclass=ConflictingMeta): # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases \ - # N: __main__.ConflictingMeta (meta of __main__.ChildOfCorrectButWrongMeta) conflicts with __main__.CorrectMeta (metaclass of __main__.CorrectSubclass1) + # N: "__main__.ConflictingMeta" (metaclass of "__main__.ChildOfCorrectButWrongMeta") conflicts with "__main__.CorrectMeta" (metaclass of "__main__.CorrectSubclass1") ... [case testMetaClassConflictIssue14033] @@ -7380,9 +7376,9 @@ class B1(metaclass=M2): pass class C1(metaclass=Mx): pass class TestABC(A2, B1, C1): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases \ - # N: __main__.M1 (metaclass of __main__.A1) conflicts with __main__.M2 (metaclass of __main__.B1) + # N: "__main__.M1" (metaclass of "__main__.A1") conflicts with "__main__.M2" (metaclass of "__main__.B1") class TestBAC(B1, A2, C1): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases \ - # N: __main__.M2 (metaclass of __main__.B1) conflicts with __main__.M1 (metaclass of __main__.A1) + # N: "__main__.M2" (metaclass of "__main__.B1") conflicts with "__main__.M1" (metaclass of "__main__.A1") # should not warn again for children class ChildOfTestABC(TestABC): pass diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index 80ca1df274af5..bb5f658ebb50b 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -1276,7 +1276,9 @@ class X(type): pass class Y(type): pass class A(metaclass=X): pass class B(A, metaclass=Y): pass # E: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases [metaclass] \ - # N: __main__.Y (meta of __main__.B) conflicts with __main__.X (metaclass of __main__.A) + # N: "__main__.Y" (metaclass of "__main__.B") conflicts with "__main__.X" (metaclass of "__main__.A") + + [case testOverloadedFunctionSignature] diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 443a4c38b753f..503135d901f89 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -2955,7 +2955,7 @@ class M(type): pass [out] == a.py:3: error: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases -a.py:3: note: a.M2 (meta of a.D) conflicts with c.M (metaclass of b.B) +a.py:3: note: "a.M2" (metaclass of "a.D") conflicts with "c.M" (metaclass of "b.B") [case testFineMetaclassDeclaredUpdate] import a @@ -2975,7 +2975,7 @@ class M2(type): pass [out] == a.py:3: error: Metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases -a.py:3: note: b.M2 (meta of a.D) conflicts with b.M (metaclass of a.B) +a.py:3: note: "b.M2" (metaclass of "a.D") conflicts with "b.M" (metaclass of "a.B") [case testFineMetaclassRemoveFromClass] import a