Files
ruff/crates/red_knot_python_semantic/resources/mdtest/metaclass.md
Charlie Marsh 626f716de6 Add support for resolving metaclasses (#14120)
## Summary

I mirrored some of the idioms that @AlexWaygood used in the MRO work.

Closes https://github.com/astral-sh/ruff/issues/14096.

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2024-11-06 15:41:35 -05:00

4.2 KiB

Default

class M(type): ...

reveal_type(M.__class__)  # revealed: Literal[type]

object

reveal_type(object.__class__)  # revealed: Literal[type]

type

reveal_type(type.__class__)  # revealed: Literal[type]

Basic

class A(type): ...
class B(metaclass=A): ...

reveal_type(B.__class__)  # revealed: Literal[A]

Invalid metaclass

If a class is a subclass of a class with a custom metaclass, then the subclass will also have that metaclass.

class M: ...
class A(metaclass=M): ...

# TODO: emit a diagnostic for the invalid metaclass
reveal_type(A.__class__)  # revealed: Literal[M]

Linear inheritance

If a class is a subclass of a class with a custom metaclass, then the subclass will also have that metaclass.

class M(type): ...
class A(metaclass=M): ...
class B(A): ...

reveal_type(B.__class__)  # revealed: Literal[M]

Conflict (1)

The metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases. ("Strict subclass" is a synonym for "proper subclass"; a non-strict subclass can be a subclass or the class itself.)

class M1(type): ...
class M2(type): ...
class A(metaclass=M1): ...
class B(metaclass=M2): ...

# error: [conflicting-metaclass] "The metaclass of a derived class (`C`) must be a subclass of the metaclasses of all its bases, but `M1` and `M2` have no subclass relationship"
class C(A, B): ...

reveal_type(C.__class__)  # revealed: Unknown

Conflict (2)

The metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases. ("Strict subclass" is a synonym for "proper subclass"; a non-strict subclass can be a subclass or the class itself.)

class M1(type): ...
class M2(type): ...
class A(metaclass=M1): ...

# error: [conflicting-metaclass] "The metaclass of a derived class (`B`) must be a subclass of the metaclasses of all its bases, but `M2` and `M1` have no subclass relationship"
class B(A, metaclass=M2): ...

reveal_type(B.__class__)  # revealed: Unknown

Common metaclass

A class has two explicit bases, both of which have the same metaclass.

class M(type): ...
class A(metaclass=M): ...
class B(metaclass=M): ...
class C(A, B): ...

reveal_type(C.__class__)  # revealed: Literal[M]

Metaclass metaclass

A class has an explicit base with a custom metaclass. That metaclass itself has a custom metaclass.

class M1(type): ...
class M2(type, metaclass=M1): ...
class M3(M2): ...
class A(metaclass=M3): ...
class B(A): ...

reveal_type(A.__class__)  # revealed: Literal[M3]

Diamond inheritance

class M(type): ...
class M1(M): ...
class M2(M): ...
class M12(M1, M2): ...
class A(metaclass=M1): ...
class B(metaclass=M2): ...
class C(metaclass=M12): ...

# error: [conflicting-metaclass] "The metaclass of a derived class (`D`) must be a subclass of the metaclasses of all its bases, but `M1` and `M2` have no subclass relationship"
class D(A, B, C): ...

reveal_type(D.__class__)  # revealed: Unknown

Unknown

from nonexistent_module import UnknownClass  # error: [unresolved-import]

class C(UnknownClass): ...

reveal_type(C.__class__)  # revealed: Literal[type]

class M(type): ...
class A(metaclass=M): ...
class B(A, UnknownClass): ...

# TODO: This should resolve to `type[M] | Unknown` instead
reveal_type(B.__class__)  # revealed: Literal[M]

Duplicate

class M(type): ...
class A(metaclass=M): ...
class B(A, A): ...  # error: [duplicate-base] "Duplicate base class `A`"

reveal_type(B.__class__)  # revealed: Literal[M]

Non-class

When a class has an explicit metaclass that is not a class, the value should be returned as is.

class A(metaclass=1): ...

reveal_type(A.__class__)  # revealed: Literal[1]

Cyclic

Retrieving the metaclass of a cyclically defined class should not cause an infinite loop.

class A(B): ...  # error: [cyclic-class-def]
class B(C): ...  # error: [cyclic-class-def]
class C(A): ...  # error: [cyclic-class-def]

reveal_type(A.__class__)  # revealed: Unknown

PEP 695 generic

class M(type): ...
class A[T: str](metaclass=M): ...

reveal_type(A.__class__)  # revealed: Literal[M]