mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 18:58:04 +00:00
[red-knot] fix detecting a metaclass on a not-explicitly-specialized generic base
This commit is contained in:
parent
327b913d68
commit
46a1fd3b3e
4 changed files with 35 additions and 14 deletions
|
@ -53,6 +53,25 @@ class B(A): ...
|
|||
reveal_type(B.__class__) # revealed: Literal[M]
|
||||
```
|
||||
|
||||
## Linear inheritance with PEP 695 generic class
|
||||
|
||||
The same is true if the base with the metaclass is a generic class.
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.13"
|
||||
```
|
||||
|
||||
```py
|
||||
class M(type): ...
|
||||
class A[T](metaclass=M): ...
|
||||
class B(A): ...
|
||||
class C(A[int]): ...
|
||||
|
||||
reveal_type(B.__class__) # revealed: Literal[M]
|
||||
reveal_type(C.__class__) # revealed: Literal[M]
|
||||
```
|
||||
|
||||
## Conflict (1)
|
||||
|
||||
The metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its
|
||||
|
|
|
@ -688,20 +688,21 @@ impl<'db> Type<'db> {
|
|||
matches!(self, Type::ClassLiteral(..))
|
||||
}
|
||||
|
||||
pub const fn into_class_type(self) -> Option<ClassType<'db>> {
|
||||
/// Turn a class literal (`Type::ClassLiteral` or `Type::GenericAlias`) into a `ClassType`.
|
||||
/// Since a `ClassType` must be specialized, apply the default specialization to any
|
||||
/// unspecialized generic class literal.
|
||||
pub fn to_class_type(self, db: &'db dyn Db) -> Option<ClassType<'db>> {
|
||||
match self {
|
||||
Type::ClassLiteral(ClassLiteralType::NonGeneric(non_generic)) => {
|
||||
Some(ClassType::NonGeneric(non_generic))
|
||||
}
|
||||
Type::ClassLiteral(class_literal) => Some(class_literal.default_specialization(db)),
|
||||
Type::GenericAlias(alias) => Some(ClassType::Generic(alias)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn expect_class_type(self) -> ClassType<'db> {
|
||||
self.into_class_type()
|
||||
.expect("Expected a Type::GenericAlias or non-generic Type::ClassLiteral variant")
|
||||
pub fn expect_class_type(self, db: &'db dyn Db) -> ClassType<'db> {
|
||||
self.to_class_type(db)
|
||||
.expect("Expected a Type::GenericAlias or Type::ClassLiteral variant")
|
||||
}
|
||||
|
||||
pub const fn is_class_type(&self) -> bool {
|
||||
|
|
|
@ -577,12 +577,13 @@ impl<'db> ClassLiteralType<'db> {
|
|||
self.explicit_bases_query(db)
|
||||
}
|
||||
|
||||
/// Iterate over this class's explicit bases, filtering out any bases that are not class objects.
|
||||
/// Iterate over this class's explicit bases, filtering out any bases that are not class
|
||||
/// objects, and applying default specialization to any unspecialized generic class literals.
|
||||
fn fully_static_explicit_bases(self, db: &'db dyn Db) -> impl Iterator<Item = ClassType<'db>> {
|
||||
self.explicit_bases(db)
|
||||
.iter()
|
||||
.copied()
|
||||
.filter_map(Type::into_class_type)
|
||||
.filter_map(|ty| ty.to_class_type(db))
|
||||
}
|
||||
|
||||
#[salsa::tracked(return_ref, cycle_fn=explicit_bases_cycle_recover, cycle_initial=explicit_bases_cycle_initial)]
|
||||
|
@ -767,7 +768,7 @@ impl<'db> ClassLiteralType<'db> {
|
|||
(KnownClass::Type.to_class_literal(db), self)
|
||||
};
|
||||
|
||||
let mut candidate = if let Some(metaclass_ty) = metaclass.into_class_type() {
|
||||
let mut candidate = if let Some(metaclass_ty) = metaclass.to_class_type(db) {
|
||||
MetaclassCandidate {
|
||||
metaclass: metaclass_ty,
|
||||
explicit_metaclass_of: class_metaclass_was_from,
|
||||
|
@ -809,7 +810,7 @@ impl<'db> ClassLiteralType<'db> {
|
|||
// - https://github.com/python/cpython/blob/83ba8c2bba834c0b92de669cac16fcda17485e0e/Objects/typeobject.c#L3629-L3663
|
||||
for base_class in base_classes {
|
||||
let metaclass = base_class.metaclass(db);
|
||||
let Some(metaclass) = metaclass.into_class_type() else {
|
||||
let Some(metaclass) = metaclass.to_class_type(db) else {
|
||||
continue;
|
||||
};
|
||||
if metaclass.is_subclass_of(db, candidate.metaclass) {
|
||||
|
@ -2164,7 +2165,7 @@ impl<'db> KnownClass {
|
|||
/// If the class cannot be found in typeshed, a debug-level log message will be emitted stating this.
|
||||
pub(crate) fn to_instance(self, db: &'db dyn Db) -> Type<'db> {
|
||||
self.to_class_literal(db)
|
||||
.into_class_type()
|
||||
.to_class_type(db)
|
||||
.map(Type::instance)
|
||||
.unwrap_or_else(Type::unknown)
|
||||
}
|
||||
|
@ -2231,7 +2232,7 @@ impl<'db> KnownClass {
|
|||
/// If the class cannot be found in typeshed, a debug-level log message will be emitted stating this.
|
||||
pub(crate) fn to_subclass_of(self, db: &'db dyn Db) -> Type<'db> {
|
||||
self.to_class_literal(db)
|
||||
.into_class_type()
|
||||
.to_class_type(db)
|
||||
.map(|class| SubclassOfType::from(db, class))
|
||||
.unwrap_or_else(SubclassOfType::subclass_of_unknown)
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ impl<'db> ClassBase<'db> {
|
|||
pub(super) fn object(db: &'db dyn Db) -> Self {
|
||||
KnownClass::Object
|
||||
.to_class_literal(db)
|
||||
.into_class_type()
|
||||
.to_class_type(db)
|
||||
.map_or(Self::unknown(), Self::Class)
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue