mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-03 02:12:22 +00:00
[red-knot] a few metaclass cleanups (#14142)
Just cleaning up a few small things I noticed in post-land review.
This commit is contained in:
parent
626f716de6
commit
03a5788aa1
2 changed files with 18 additions and 11 deletions
|
@ -21,16 +21,16 @@ reveal_type(type.__class__) # revealed: Literal[type]
|
|||
## Basic
|
||||
|
||||
```py
|
||||
class A(type): ...
|
||||
class B(metaclass=A): ...
|
||||
class M(type): ...
|
||||
class B(metaclass=M): ...
|
||||
|
||||
reveal_type(B.__class__) # revealed: Literal[A]
|
||||
reveal_type(B.__class__) # revealed: Literal[M]
|
||||
```
|
||||
|
||||
## Invalid metaclass
|
||||
|
||||
If a class is a subclass of a class with a custom metaclass, then the subclass will also have that
|
||||
metaclass.
|
||||
A class which doesn't inherit `type` (and/or doesn't implement a custom `__new__` accepting the same
|
||||
arguments as `type.__new__`) isn't a valid metaclass.
|
||||
|
||||
```py
|
||||
class M: ...
|
||||
|
@ -139,13 +139,14 @@ from nonexistent_module import UnknownClass # error: [unresolved-import]
|
|||
|
||||
class C(UnknownClass): ...
|
||||
|
||||
# TODO: should be `type[type] & Unknown`
|
||||
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
|
||||
# TODO: should be `type[M] & Unknown`
|
||||
reveal_type(B.__class__) # revealed: Literal[M]
|
||||
```
|
||||
|
||||
|
@ -161,12 +162,16 @@ 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.
|
||||
When a class has an explicit `metaclass` that is not a class, but is a callable that accepts
|
||||
`type.__new__` arguments, we should return the meta type of its return type.
|
||||
|
||||
```py
|
||||
class A(metaclass=1): ...
|
||||
def f(*args, **kwargs) -> int: ...
|
||||
|
||||
reveal_type(A.__class__) # revealed: Literal[1]
|
||||
class A(metaclass=f): ...
|
||||
|
||||
# TODO should be `type[int]`
|
||||
reveal_type(A.__class__) # revealed: @Todo
|
||||
```
|
||||
|
||||
## Cyclic
|
||||
|
|
|
@ -2100,8 +2100,10 @@ impl<'db> Class<'db> {
|
|||
};
|
||||
|
||||
let Type::ClassLiteral(mut candidate) = metaclass else {
|
||||
// If the metaclass is not a class, return it directly.
|
||||
return Ok(metaclass);
|
||||
// TODO: If the metaclass is not a class, we should verify that it's a callable
|
||||
// which accepts the same arguments as `type.__new__` (otherwise error), and return
|
||||
// the meta-type of its return type. (And validate that is a class type?)
|
||||
return Ok(Type::Todo);
|
||||
};
|
||||
|
||||
// Reconcile all base classes' metaclasses with the candidate metaclass.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue