mirror of https://github.com/astral-sh/ruff
[red-knot] fix detecting a metaclass on a not-explicitly-specialized generic base
This commit is contained in:
parent
327b913d68
commit
46a1fd3b3e
|
|
@ -53,6 +53,25 @@ class B(A): ...
|
||||||
reveal_type(B.__class__) # revealed: Literal[M]
|
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)
|
## Conflict (1)
|
||||||
|
|
||||||
The metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its
|
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(..))
|
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 {
|
match self {
|
||||||
Type::ClassLiteral(ClassLiteralType::NonGeneric(non_generic)) => {
|
Type::ClassLiteral(class_literal) => Some(class_literal.default_specialization(db)),
|
||||||
Some(ClassType::NonGeneric(non_generic))
|
|
||||||
}
|
|
||||||
Type::GenericAlias(alias) => Some(ClassType::Generic(alias)),
|
Type::GenericAlias(alias) => Some(ClassType::Generic(alias)),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn expect_class_type(self) -> ClassType<'db> {
|
pub fn expect_class_type(self, db: &'db dyn Db) -> ClassType<'db> {
|
||||||
self.into_class_type()
|
self.to_class_type(db)
|
||||||
.expect("Expected a Type::GenericAlias or non-generic Type::ClassLiteral variant")
|
.expect("Expected a Type::GenericAlias or Type::ClassLiteral variant")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn is_class_type(&self) -> bool {
|
pub const fn is_class_type(&self) -> bool {
|
||||||
|
|
|
||||||
|
|
@ -577,12 +577,13 @@ impl<'db> ClassLiteralType<'db> {
|
||||||
self.explicit_bases_query(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>> {
|
fn fully_static_explicit_bases(self, db: &'db dyn Db) -> impl Iterator<Item = ClassType<'db>> {
|
||||||
self.explicit_bases(db)
|
self.explicit_bases(db)
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.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)]
|
#[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)
|
(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 {
|
MetaclassCandidate {
|
||||||
metaclass: metaclass_ty,
|
metaclass: metaclass_ty,
|
||||||
explicit_metaclass_of: class_metaclass_was_from,
|
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
|
// - https://github.com/python/cpython/blob/83ba8c2bba834c0b92de669cac16fcda17485e0e/Objects/typeobject.c#L3629-L3663
|
||||||
for base_class in base_classes {
|
for base_class in base_classes {
|
||||||
let metaclass = base_class.metaclass(db);
|
let metaclass = base_class.metaclass(db);
|
||||||
let Some(metaclass) = metaclass.into_class_type() else {
|
let Some(metaclass) = metaclass.to_class_type(db) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if metaclass.is_subclass_of(db, candidate.metaclass) {
|
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.
|
/// 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> {
|
pub(crate) fn to_instance(self, db: &'db dyn Db) -> Type<'db> {
|
||||||
self.to_class_literal(db)
|
self.to_class_literal(db)
|
||||||
.into_class_type()
|
.to_class_type(db)
|
||||||
.map(Type::instance)
|
.map(Type::instance)
|
||||||
.unwrap_or_else(Type::unknown)
|
.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.
|
/// 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> {
|
pub(crate) fn to_subclass_of(self, db: &'db dyn Db) -> Type<'db> {
|
||||||
self.to_class_literal(db)
|
self.to_class_literal(db)
|
||||||
.into_class_type()
|
.to_class_type(db)
|
||||||
.map(|class| SubclassOfType::from(db, class))
|
.map(|class| SubclassOfType::from(db, class))
|
||||||
.unwrap_or_else(SubclassOfType::subclass_of_unknown)
|
.unwrap_or_else(SubclassOfType::subclass_of_unknown)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ impl<'db> ClassBase<'db> {
|
||||||
pub(super) fn object(db: &'db dyn Db) -> Self {
|
pub(super) fn object(db: &'db dyn Db) -> Self {
|
||||||
KnownClass::Object
|
KnownClass::Object
|
||||||
.to_class_literal(db)
|
.to_class_literal(db)
|
||||||
.into_class_type()
|
.to_class_type(db)
|
||||||
.map_or(Self::unknown(), Self::Class)
|
.map_or(Self::unknown(), Self::Class)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue