mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 22:01:18 +00:00
[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:
parent
9a6633da0b
commit
47e3aa40b3
6 changed files with 106 additions and 13 deletions
|
@ -145,7 +145,7 @@ def f(x: int) -> int:
|
||||||
return x**2
|
return x**2
|
||||||
|
|
||||||
# TODO: Should be `_lru_cache_wrapper[int]`
|
# TODO: Should be `_lru_cache_wrapper[int]`
|
||||||
reveal_type(f) # revealed: _lru_cache_wrapper[_T]
|
reveal_type(f) # revealed: _lru_cache_wrapper[Unknown]
|
||||||
|
|
||||||
# TODO: Should be `int`
|
# TODO: Should be `int`
|
||||||
reveal_type(f(1)) # revealed: Unknown
|
reveal_type(f(1)) # revealed: Unknown
|
||||||
|
|
|
@ -361,6 +361,40 @@ c: C[int] = C[int]()
|
||||||
reveal_type(c.method("string")) # revealed: Literal["string"]
|
reveal_type(c.method("string")) # revealed: Literal["string"]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Specializations propagate
|
||||||
|
|
||||||
|
In a specialized generic alias, the specialization is applied to the attributes and methods of the
|
||||||
|
class.
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Generic, TypeVar
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
U = TypeVar("U")
|
||||||
|
|
||||||
|
class LinkedList(Generic[T]): ...
|
||||||
|
|
||||||
|
class C(Generic[T, U]):
|
||||||
|
x: T
|
||||||
|
y: U
|
||||||
|
|
||||||
|
def method1(self) -> T:
|
||||||
|
return self.x
|
||||||
|
|
||||||
|
def method2(self) -> U:
|
||||||
|
return self.y
|
||||||
|
|
||||||
|
def method3(self) -> LinkedList[T]:
|
||||||
|
return LinkedList[T]()
|
||||||
|
|
||||||
|
c = C[int, str]()
|
||||||
|
reveal_type(c.x) # revealed: int
|
||||||
|
reveal_type(c.y) # revealed: str
|
||||||
|
reveal_type(c.method1()) # revealed: int
|
||||||
|
reveal_type(c.method2()) # revealed: str
|
||||||
|
reveal_type(c.method3()) # revealed: LinkedList[int]
|
||||||
|
```
|
||||||
|
|
||||||
## Cyclic class definitions
|
## Cyclic class definitions
|
||||||
|
|
||||||
### F-bounded quantification
|
### F-bounded quantification
|
||||||
|
|
|
@ -305,6 +305,35 @@ c: C[int] = C[int]()
|
||||||
reveal_type(c.method("string")) # revealed: Literal["string"]
|
reveal_type(c.method("string")) # revealed: Literal["string"]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Specializations propagate
|
||||||
|
|
||||||
|
In a specialized generic alias, the specialization is applied to the attributes and methods of the
|
||||||
|
class.
|
||||||
|
|
||||||
|
```py
|
||||||
|
class LinkedList[T]: ...
|
||||||
|
|
||||||
|
class C[T, U]:
|
||||||
|
x: T
|
||||||
|
y: U
|
||||||
|
|
||||||
|
def method1(self) -> T:
|
||||||
|
return self.x
|
||||||
|
|
||||||
|
def method2(self) -> U:
|
||||||
|
return self.y
|
||||||
|
|
||||||
|
def method3(self) -> LinkedList[T]:
|
||||||
|
return LinkedList[T]()
|
||||||
|
|
||||||
|
c = C[int, str]()
|
||||||
|
reveal_type(c.x) # revealed: int
|
||||||
|
reveal_type(c.y) # revealed: str
|
||||||
|
reveal_type(c.method1()) # revealed: int
|
||||||
|
reveal_type(c.method2()) # revealed: str
|
||||||
|
reveal_type(c.method3()) # revealed: LinkedList[int]
|
||||||
|
```
|
||||||
|
|
||||||
## Cyclic class definitions
|
## Cyclic class definitions
|
||||||
|
|
||||||
### F-bounded quantification
|
### F-bounded quantification
|
||||||
|
|
|
@ -4962,20 +4962,16 @@ impl<'db> Type<'db> {
|
||||||
Type::FunctionLiteral(function.apply_specialization(db, specialization))
|
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(
|
Type::BoundMethod(method) => Type::BoundMethod(BoundMethodType::new(
|
||||||
db,
|
db,
|
||||||
method.function(db).apply_specialization(db, specialization),
|
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)) => {
|
||||||
Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderGet(
|
Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderGet(
|
||||||
function.apply_specialization(db, specialization),
|
function.apply_specialization(db, specialization),
|
||||||
|
@ -5060,9 +5056,6 @@ impl<'db> Type<'db> {
|
||||||
| Type::BytesLiteral(_)
|
| Type::BytesLiteral(_)
|
||||||
| Type::SliceLiteral(_)
|
| Type::SliceLiteral(_)
|
||||||
| Type::BoundSuper(_)
|
| 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`
|
// Same for `ProtocolInstance`
|
||||||
| Type::ProtocolInstance(_)
|
| Type::ProtocolInstance(_)
|
||||||
| Type::KnownInstance(_) => self,
|
| Type::KnownInstance(_) => self,
|
||||||
|
|
|
@ -146,6 +146,19 @@ impl<'db> GenericAlias<'db> {
|
||||||
pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> {
|
pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> {
|
||||||
self.origin(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> {
|
impl<'db> From<GenericAlias<'db>> for Type<'db> {
|
||||||
|
@ -210,6 +223,19 @@ impl<'db> ClassType<'db> {
|
||||||
self.is_known(db, KnownClass::Object)
|
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.
|
/// Iterate over the [method resolution order] ("MRO") of the class.
|
||||||
///
|
///
|
||||||
/// If the MRO could not be accurately resolved, this method falls back to iterating
|
/// If the MRO could not be accurately resolved, this method falls back to iterating
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
use super::protocol_class::ProtocolInterface;
|
use super::protocol_class::ProtocolInterface;
|
||||||
use super::{ClassType, KnownClass, SubclassOfType, Type};
|
use super::{ClassType, KnownClass, SubclassOfType, Type};
|
||||||
use crate::symbol::{Symbol, SymbolAndQualifiers};
|
use crate::symbol::{Symbol, SymbolAndQualifiers};
|
||||||
|
use crate::types::generics::Specialization;
|
||||||
use crate::Db;
|
use crate::Db;
|
||||||
|
|
||||||
pub(super) use synthesized_protocol::SynthesizedProtocolType;
|
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> {
|
pub(super) fn to_meta_type(self, db: &'db dyn Db) -> Type<'db> {
|
||||||
SubclassOfType::from(db, self.class)
|
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> {
|
impl<'db> From<NominalInstanceType<'db>> for Type<'db> {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue