[ty] Specialize bound methods and nominal instances (#17865)

Fixes
https://github.com/astral-sh/ruff/pull/17832#issuecomment-2851224968. We
had a comment that we did not need to apply specializations to generic
aliases, or to the bound `self` of a bound method, because they were
already specialized. But they might be specialized with a type variable,
which _does_ need to be specialized, in the case of a "multi-step"
specialization, such as:

```py
class LinkedList[T]: ...

class C[U]:
    def method(self) -> LinkedList[U]:
        return LinkedList[U]()
```

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
Douglas Creager 2025-05-05 17:17:36 -04:00 committed by GitHub
parent 9a6633da0b
commit 47e3aa40b3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 106 additions and 13 deletions

View file

@ -4962,20 +4962,16 @@ impl<'db> Type<'db> {
Type::FunctionLiteral(function.apply_specialization(db, specialization))
}
// Note that we don't need to apply the specialization to `self_instance`, since it
// must either be a non-generic class literal (which cannot have any typevars to
// specialize) or a generic alias (which has already been fully specialized). For a
// generic alias, the specialization being applied here must be for some _other_
// generic context nested within the generic alias's class literal, which the generic
// alias's context cannot refer to. (The _method_ does need to be specialized, since it
// might be a nested generic method, whose generic context is what is now being
// specialized.)
Type::BoundMethod(method) => Type::BoundMethod(BoundMethodType::new(
db,
method.function(db).apply_specialization(db, specialization),
method.self_instance(db),
method.self_instance(db).apply_specialization(db, specialization),
)),
Type::NominalInstance(instance) => Type::NominalInstance(
instance.apply_specialization(db, specialization),
),
Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderGet(function)) => {
Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderGet(
function.apply_specialization(db, specialization),
@ -5060,9 +5056,6 @@ impl<'db> Type<'db> {
| Type::BytesLiteral(_)
| Type::SliceLiteral(_)
| Type::BoundSuper(_)
// `NominalInstance` contains a ClassType, which has already been specialized if needed,
// like above with BoundMethod's self_instance.
| Type::NominalInstance(_)
// Same for `ProtocolInstance`
| Type::ProtocolInstance(_)
| Type::KnownInstance(_) => self,

View file

@ -146,6 +146,19 @@ impl<'db> GenericAlias<'db> {
pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> {
self.origin(db).definition(db)
}
pub(super) fn apply_specialization(
self,
db: &'db dyn Db,
specialization: Specialization<'db>,
) -> Self {
Self::new(
db,
self.origin(db),
self.specialization(db)
.apply_specialization(db, specialization),
)
}
}
impl<'db> From<GenericAlias<'db>> for Type<'db> {
@ -210,6 +223,19 @@ impl<'db> ClassType<'db> {
self.is_known(db, KnownClass::Object)
}
pub(super) fn apply_specialization(
self,
db: &'db dyn Db,
specialization: Specialization<'db>,
) -> Self {
match self {
Self::NonGeneric(_) => self,
Self::Generic(generic) => {
Self::Generic(generic.apply_specialization(db, specialization))
}
}
}
/// Iterate over the [method resolution order] ("MRO") of the class.
///
/// If the MRO could not be accurately resolved, this method falls back to iterating

View file

@ -3,6 +3,7 @@
use super::protocol_class::ProtocolInterface;
use super::{ClassType, KnownClass, SubclassOfType, Type};
use crate::symbol::{Symbol, SymbolAndQualifiers};
use crate::types::generics::Specialization;
use crate::Db;
pub(super) use synthesized_protocol::SynthesizedProtocolType;
@ -111,6 +112,16 @@ impl<'db> NominalInstanceType<'db> {
pub(super) fn to_meta_type(self, db: &'db dyn Db) -> Type<'db> {
SubclassOfType::from(db, self.class)
}
pub(super) fn apply_specialization(
self,
db: &'db dyn Db,
specialization: Specialization<'db>,
) -> Self {
Self {
class: self.class.apply_specialization(db, specialization),
}
}
}
impl<'db> From<NominalInstanceType<'db>> for Type<'db> {