Skip to content

Commit

Permalink
Docs: how to work around metaclass limitation
Browse files Browse the repository at this point in the history
  • Loading branch information
robsdedude committed Aug 16, 2024
1 parent be3a646 commit 0756cbc
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 8 deletions.
3 changes: 3 additions & 0 deletions docs/source/error_code_list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://docs.python.org/3.13/reference/datamodel.html#determining-the-appropriate-metaclass>`_

Note that mypy's metaclass checking is limited and may produce false-positives.
See also :ref:`limitations`.

Example with an error:

.. code-block:: python
Expand Down
31 changes: 31 additions & 0 deletions docs/source/metaclasses.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 1 addition & 5 deletions mypy/errorcodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
10 changes: 8 additions & 2 deletions mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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(
Expand Down

0 comments on commit 0756cbc

Please sign in to comment.