[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:
Carl Meyer 2024-11-06 14:13:39 -08:00 committed by GitHub
parent 626f716de6
commit 03a5788aa1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 18 additions and 11 deletions

View file

@ -21,16 +21,16 @@ reveal_type(type.__class__) # revealed: Literal[type]
## Basic ## Basic
```py ```py
class A(type): ... class M(type): ...
class B(metaclass=A): ... class B(metaclass=M): ...
reveal_type(B.__class__) # revealed: Literal[A] reveal_type(B.__class__) # revealed: Literal[M]
``` ```
## Invalid metaclass ## Invalid metaclass
If a class is a subclass of a class with a custom metaclass, then the subclass will also have that A class which doesn't inherit `type` (and/or doesn't implement a custom `__new__` accepting the same
metaclass. arguments as `type.__new__`) isn't a valid metaclass.
```py ```py
class M: ... class M: ...
@ -139,13 +139,14 @@ from nonexistent_module import UnknownClass # error: [unresolved-import]
class C(UnknownClass): ... class C(UnknownClass): ...
# TODO: should be `type[type] & Unknown`
reveal_type(C.__class__) # revealed: Literal[type] reveal_type(C.__class__) # revealed: Literal[type]
class M(type): ... class M(type): ...
class A(metaclass=M): ... class A(metaclass=M): ...
class B(A, UnknownClass): ... 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] reveal_type(B.__class__) # revealed: Literal[M]
``` ```
@ -161,12 +162,16 @@ reveal_type(B.__class__) # revealed: Literal[M]
## Non-class ## 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 ```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 ## Cyclic

View file

@ -2100,8 +2100,10 @@ impl<'db> Class<'db> {
}; };
let Type::ClassLiteral(mut candidate) = metaclass else { let Type::ClassLiteral(mut candidate) = metaclass else {
// If the metaclass is not a class, return it directly. // TODO: If the metaclass is not a class, we should verify that it's a callable
return Ok(metaclass); // 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. // Reconcile all base classes' metaclasses with the candidate metaclass.