Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/ty_python_semantic/resources/mdtest/decorators.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def f(x: int) -> int:
return x**2

# 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`
reveal_type(f(1)) # revealed: Unknown
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,40 @@ c: C[int] = C[int]()
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

### F-bounded quantification
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,35 @@ c: C[int] = C[int]()
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

### F-bounded quantification
Expand Down
17 changes: 5 additions & 12 deletions crates/ty_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
),
Comment on lines +4971 to +4973
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we probably need to do the same for the Type::ProtocolInstance variant, since protocols can also be generic. But I can do that as part of #17832, if you like, since that's the "hook up the generics infrastructure with the protocols infrastructure" PR 😄

Copy link
Member

@AlexWaygood AlexWaygood May 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did this in de14158 as part of #17832


Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderGet(function)) => {
Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderGet(
function.apply_specialization(db, specialization),
Expand Down Expand Up @@ -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,
Expand Down
26 changes: 26 additions & 0 deletions crates/ty_python_semantic/src/types/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions crates/ty_python_semantic/src/types/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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> {
Expand Down
Loading