mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-15 16:10:17 +00:00
[ty] Handle typevars that have other typevars as a default (#17956)
It's possible for a typevar to list another typevar as its default value: ```py class C[T, U = T]: ... ``` When specializing this class, if a type isn't provided for `U`, we would previously use the default as-is, leaving an unspecialized `T` typevar in the specialization. Instead, we want to use what `T` is mapped to as the type of `U`. ```py reveal_type(C()) # revealed: C[Unknown, Unknown] reveal_type(C[int]()) # revealed: C[int, int] reveal_type(C[int, str]()) # revealed: C[int, str] ``` This is especially important for the `slice` built-in type.
This commit is contained in:
parent
f51f1f7153
commit
b705664d49
11 changed files with 226 additions and 102 deletions
|
@ -1945,7 +1945,7 @@ reveal_type(C.a_complex) # revealed: int | float | complex
|
||||||
reveal_type(C.a_tuple) # revealed: tuple[int]
|
reveal_type(C.a_tuple) # revealed: tuple[int]
|
||||||
reveal_type(C.a_range) # revealed: range
|
reveal_type(C.a_range) # revealed: range
|
||||||
# TODO: revealed: slice[Any, Literal[1], Any]
|
# TODO: revealed: slice[Any, Literal[1], Any]
|
||||||
reveal_type(C.a_slice) # revealed: slice[Any, _StartT_co, _StartT_co | _StopT_co]
|
reveal_type(C.a_slice) # revealed: slice[Any, Any, Any]
|
||||||
reveal_type(C.a_type) # revealed: type
|
reveal_type(C.a_type) # revealed: type
|
||||||
reveal_type(C.a_none) # revealed: None
|
reveal_type(C.a_none) # revealed: None
|
||||||
```
|
```
|
||||||
|
|
|
@ -86,6 +86,26 @@ S = TypeVar("S")
|
||||||
reveal_type(S.__default__) # revealed: NoDefault
|
reveal_type(S.__default__) # revealed: NoDefault
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Using other typevars as a default
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Generic, TypeVar, Union
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
U = TypeVar("U", default=T)
|
||||||
|
V = TypeVar("V", default=Union[T, U])
|
||||||
|
|
||||||
|
class Valid(Generic[T, U, V]): ...
|
||||||
|
|
||||||
|
reveal_type(Valid()) # revealed: Valid[Unknown, Unknown, Unknown]
|
||||||
|
reveal_type(Valid[int]()) # revealed: Valid[int, int, int]
|
||||||
|
reveal_type(Valid[int, str]()) # revealed: Valid[int, str, int | str]
|
||||||
|
reveal_type(Valid[int, str, None]()) # revealed: Valid[int, str, None]
|
||||||
|
|
||||||
|
# TODO: error, default value for U isn't available in the generic context
|
||||||
|
class Invalid(Generic[U]): ...
|
||||||
|
```
|
||||||
|
|
||||||
### Type variables with an upper bound
|
### Type variables with an upper bound
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
|
|
@ -40,6 +40,25 @@ def g[S]():
|
||||||
reveal_type(S.__default__) # revealed: NoDefault
|
reveal_type(S.__default__) # revealed: NoDefault
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Using other typevars as a default
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.13"
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
class Valid[T, U = T, V = T | U]: ...
|
||||||
|
|
||||||
|
reveal_type(Valid()) # revealed: Valid[Unknown, Unknown, Unknown]
|
||||||
|
reveal_type(Valid[int]()) # revealed: Valid[int, int, int]
|
||||||
|
reveal_type(Valid[int, str]()) # revealed: Valid[int, str, int | str]
|
||||||
|
reveal_type(Valid[int, str, None]()) # revealed: Valid[int, str, None]
|
||||||
|
|
||||||
|
# error: [unresolved-reference]
|
||||||
|
class Invalid[S = T]: ...
|
||||||
|
```
|
||||||
|
|
||||||
### Type variables with an upper bound
|
### Type variables with an upper bound
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
|
|
@ -45,7 +45,7 @@ use crate::types::call::{Bindings, CallArgumentTypes, CallableBinding};
|
||||||
pub(crate) use crate::types::class_base::ClassBase;
|
pub(crate) use crate::types::class_base::ClassBase;
|
||||||
use crate::types::context::{LintDiagnosticGuard, LintDiagnosticGuardBuilder};
|
use crate::types::context::{LintDiagnosticGuard, LintDiagnosticGuardBuilder};
|
||||||
use crate::types::diagnostic::{INVALID_TYPE_FORM, UNSUPPORTED_BOOL_CONVERSION};
|
use crate::types::diagnostic::{INVALID_TYPE_FORM, UNSUPPORTED_BOOL_CONVERSION};
|
||||||
use crate::types::generics::{GenericContext, Specialization};
|
use crate::types::generics::{GenericContext, Specialization, TypeMapping};
|
||||||
use crate::types::infer::infer_unpack_types;
|
use crate::types::infer::infer_unpack_types;
|
||||||
use crate::types::mro::{Mro, MroError, MroIterator};
|
use crate::types::mro::{Mro, MroError, MroIterator};
|
||||||
pub(crate) use crate::types::narrow::infer_narrowing_constraint;
|
pub(crate) use crate::types::narrow::infer_narrowing_constraint;
|
||||||
|
@ -342,13 +342,13 @@ pub struct PropertyInstanceType<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> PropertyInstanceType<'db> {
|
impl<'db> PropertyInstanceType<'db> {
|
||||||
fn apply_specialization(self, db: &'db dyn Db, specialization: Specialization<'db>) -> Self {
|
fn apply_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: TypeMapping<'a, 'db>) -> Self {
|
||||||
let getter = self
|
let getter = self
|
||||||
.getter(db)
|
.getter(db)
|
||||||
.map(|ty| ty.apply_specialization(db, specialization));
|
.map(|ty| ty.apply_type_mapping(db, type_mapping));
|
||||||
let setter = self
|
let setter = self
|
||||||
.setter(db)
|
.setter(db)
|
||||||
.map(|ty| ty.apply_specialization(db, specialization));
|
.map(|ty| ty.apply_type_mapping(db, type_mapping));
|
||||||
Self::new(db, getter, setter)
|
Self::new(db, getter, setter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5043,75 +5043,83 @@ impl<'db> Type<'db> {
|
||||||
self,
|
self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
specialization: Specialization<'db>,
|
specialization: Specialization<'db>,
|
||||||
|
) -> Type<'db> {
|
||||||
|
self.apply_type_mapping(db, specialization.type_mapping())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_type_mapping<'a>(
|
||||||
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
type_mapping: TypeMapping<'a, 'db>,
|
||||||
) -> Type<'db> {
|
) -> Type<'db> {
|
||||||
match self {
|
match self {
|
||||||
Type::TypeVar(typevar) => specialization.get(db, typevar).unwrap_or(self),
|
Type::TypeVar(typevar) => type_mapping.get(db, typevar).unwrap_or(self),
|
||||||
|
|
||||||
Type::FunctionLiteral(function) => {
|
Type::FunctionLiteral(function) => {
|
||||||
Type::FunctionLiteral(function.apply_specialization(db, specialization))
|
Type::FunctionLiteral(function.apply_type_mapping(db, type_mapping))
|
||||||
}
|
}
|
||||||
|
|
||||||
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_type_mapping(db, type_mapping),
|
||||||
method.self_instance(db).apply_specialization(db, specialization),
|
method.self_instance(db).apply_type_mapping(db, type_mapping),
|
||||||
)),
|
)),
|
||||||
|
|
||||||
Type::NominalInstance(instance) => Type::NominalInstance(
|
Type::NominalInstance(instance) => Type::NominalInstance(
|
||||||
instance.apply_specialization(db, specialization),
|
instance.apply_type_mapping(db, type_mapping),
|
||||||
),
|
),
|
||||||
|
|
||||||
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_type_mapping(db, type_mapping),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderCall(function)) => {
|
Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderCall(function)) => {
|
||||||
Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderCall(
|
Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderCall(
|
||||||
function.apply_specialization(db, specialization),
|
function.apply_type_mapping(db, type_mapping),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
Type::MethodWrapper(MethodWrapperKind::PropertyDunderGet(property)) => {
|
Type::MethodWrapper(MethodWrapperKind::PropertyDunderGet(property)) => {
|
||||||
Type::MethodWrapper(MethodWrapperKind::PropertyDunderGet(
|
Type::MethodWrapper(MethodWrapperKind::PropertyDunderGet(
|
||||||
property.apply_specialization(db, specialization),
|
property.apply_type_mapping(db, type_mapping),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
Type::MethodWrapper(MethodWrapperKind::PropertyDunderSet(property)) => {
|
Type::MethodWrapper(MethodWrapperKind::PropertyDunderSet(property)) => {
|
||||||
Type::MethodWrapper(MethodWrapperKind::PropertyDunderSet(
|
Type::MethodWrapper(MethodWrapperKind::PropertyDunderSet(
|
||||||
property.apply_specialization(db, specialization),
|
property.apply_type_mapping(db, type_mapping),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
Type::Callable(callable) => {
|
Type::Callable(callable) => {
|
||||||
Type::Callable(callable.apply_specialization(db, specialization))
|
Type::Callable(callable.apply_type_mapping(db, type_mapping))
|
||||||
}
|
}
|
||||||
|
|
||||||
Type::GenericAlias(generic) => {
|
Type::GenericAlias(generic) => {
|
||||||
let specialization = generic
|
let specialization = generic
|
||||||
.specialization(db)
|
.specialization(db)
|
||||||
.apply_specialization(db, specialization);
|
.apply_type_mapping(db, type_mapping);
|
||||||
Type::GenericAlias(GenericAlias::new(db, generic.origin(db), specialization))
|
Type::GenericAlias(GenericAlias::new(db, generic.origin(db), specialization))
|
||||||
}
|
}
|
||||||
|
|
||||||
Type::PropertyInstance(property) => {
|
Type::PropertyInstance(property) => {
|
||||||
Type::PropertyInstance(property.apply_specialization(db, specialization))
|
Type::PropertyInstance(property.apply_type_mapping(db, type_mapping))
|
||||||
}
|
}
|
||||||
|
|
||||||
Type::Union(union) => union.map(db, |element| {
|
Type::Union(union) => union.map(db, |element| {
|
||||||
element.apply_specialization(db, specialization)
|
element.apply_type_mapping(db, type_mapping)
|
||||||
}),
|
}),
|
||||||
Type::Intersection(intersection) => {
|
Type::Intersection(intersection) => {
|
||||||
let mut builder = IntersectionBuilder::new(db);
|
let mut builder = IntersectionBuilder::new(db);
|
||||||
for positive in intersection.positive(db) {
|
for positive in intersection.positive(db) {
|
||||||
builder =
|
builder =
|
||||||
builder.add_positive(positive.apply_specialization(db, specialization));
|
builder.add_positive(positive.apply_type_mapping(db, type_mapping));
|
||||||
}
|
}
|
||||||
for negative in intersection.negative(db) {
|
for negative in intersection.negative(db) {
|
||||||
builder =
|
builder =
|
||||||
builder.add_negative(negative.apply_specialization(db, specialization));
|
builder.add_negative(negative.apply_type_mapping(db, type_mapping));
|
||||||
}
|
}
|
||||||
builder.build()
|
builder.build()
|
||||||
}
|
}
|
||||||
|
@ -5119,7 +5127,7 @@ impl<'db> Type<'db> {
|
||||||
db,
|
db,
|
||||||
tuple
|
tuple
|
||||||
.iter(db)
|
.iter(db)
|
||||||
.map(|ty| ty.apply_specialization(db, specialization)),
|
.map(|ty| ty.apply_type_mapping(db, type_mapping)),
|
||||||
),
|
),
|
||||||
|
|
||||||
Type::Dynamic(_)
|
Type::Dynamic(_)
|
||||||
|
@ -6844,6 +6852,10 @@ impl<'db> FunctionType<'db> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn apply_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: TypeMapping<'a, 'db>) -> Self {
|
||||||
|
self.apply_specialization(db, type_mapping.into_specialization(db))
|
||||||
|
}
|
||||||
|
|
||||||
fn find_legacy_typevars(
|
fn find_legacy_typevars(
|
||||||
self,
|
self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
|
@ -7192,15 +7204,12 @@ impl<'db> CallableType<'db> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply a specialization to this callable type.
|
fn apply_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: TypeMapping<'a, 'db>) -> Self {
|
||||||
///
|
|
||||||
/// See [`Type::apply_specialization`] for more details.
|
|
||||||
fn apply_specialization(self, db: &'db dyn Db, specialization: Specialization<'db>) -> Self {
|
|
||||||
CallableType::from_overloads(
|
CallableType::from_overloads(
|
||||||
db,
|
db,
|
||||||
self.signatures(db)
|
self.signatures(db)
|
||||||
.iter()
|
.iter()
|
||||||
.map(|signature| signature.apply_specialization(db, specialization)),
|
.map(|signature| signature.apply_type_mapping(db, type_mapping)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1368,21 +1368,6 @@ impl<'db> Binding<'db> {
|
||||||
&self.parameter_tys
|
&self.parameter_tys
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the bound types for each parameter, in parameter source order, with default values
|
|
||||||
/// applied for arguments that weren't matched to a parameter. Returns `None` if there are any
|
|
||||||
/// non-default arguments that weren't matched to a parameter.
|
|
||||||
pub(crate) fn parameter_types_with_defaults(
|
|
||||||
&self,
|
|
||||||
signature: &Signature<'db>,
|
|
||||||
) -> Option<Box<[Type<'db>]>> {
|
|
||||||
signature
|
|
||||||
.parameters()
|
|
||||||
.iter()
|
|
||||||
.zip(&self.parameter_tys)
|
|
||||||
.map(|(parameter, parameter_ty)| parameter_ty.or(parameter.default_type()))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn arguments_for_parameter<'a>(
|
pub(crate) fn arguments_for_parameter<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
argument_types: &'a CallArgumentTypes<'a, 'db>,
|
argument_types: &'a CallArgumentTypes<'a, 'db>,
|
||||||
|
|
|
@ -8,7 +8,7 @@ use super::{
|
||||||
};
|
};
|
||||||
use crate::semantic_index::definition::Definition;
|
use crate::semantic_index::definition::Definition;
|
||||||
use crate::semantic_index::DeclarationWithConstraint;
|
use crate::semantic_index::DeclarationWithConstraint;
|
||||||
use crate::types::generics::{GenericContext, Specialization};
|
use crate::types::generics::{GenericContext, Specialization, TypeMapping};
|
||||||
use crate::types::signatures::{Parameter, Parameters};
|
use crate::types::signatures::{Parameter, Parameters};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
CallableType, DataclassParams, DataclassTransformerParams, KnownInstanceType, Signature,
|
CallableType, DataclassParams, DataclassTransformerParams, KnownInstanceType, Signature,
|
||||||
|
@ -147,16 +147,11 @@ impl<'db> GenericAlias<'db> {
|
||||||
self.origin(db).definition(db)
|
self.origin(db).definition(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn apply_specialization(
|
fn apply_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: TypeMapping<'a, 'db>) -> Self {
|
||||||
self,
|
|
||||||
db: &'db dyn Db,
|
|
||||||
specialization: Specialization<'db>,
|
|
||||||
) -> Self {
|
|
||||||
Self::new(
|
Self::new(
|
||||||
db,
|
db,
|
||||||
self.origin(db),
|
self.origin(db),
|
||||||
self.specialization(db)
|
self.specialization(db).apply_type_mapping(db, type_mapping),
|
||||||
.apply_specialization(db, specialization),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -236,16 +231,14 @@ impl<'db> ClassType<'db> {
|
||||||
self.is_known(db, KnownClass::Object)
|
self.is_known(db, KnownClass::Object)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn apply_specialization(
|
pub(super) fn apply_type_mapping<'a>(
|
||||||
self,
|
self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
specialization: Specialization<'db>,
|
type_mapping: TypeMapping<'a, 'db>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
match self {
|
match self {
|
||||||
Self::NonGeneric(_) => self,
|
Self::NonGeneric(_) => self,
|
||||||
Self::Generic(generic) => {
|
Self::Generic(generic) => Self::Generic(generic.apply_type_mapping(db, type_mapping)),
|
||||||
Self::Generic(generic.apply_specialization(db, specialization))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::types::generics::{GenericContext, Specialization};
|
use crate::types::generics::{GenericContext, Specialization, TypeMapping};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
todo_type, ClassType, DynamicType, KnownClass, KnownInstanceType, MroIterator, Type,
|
todo_type, ClassType, DynamicType, KnownClass, KnownInstanceType, MroIterator, Type,
|
||||||
};
|
};
|
||||||
|
@ -215,13 +215,9 @@ impl<'db> ClassBase<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn apply_specialization(
|
fn apply_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: TypeMapping<'a, 'db>) -> Self {
|
||||||
self,
|
|
||||||
db: &'db dyn Db,
|
|
||||||
specialization: Specialization<'db>,
|
|
||||||
) -> Self {
|
|
||||||
match self {
|
match self {
|
||||||
Self::Class(class) => Self::Class(class.apply_specialization(db, specialization)),
|
Self::Class(class) => Self::Class(class.apply_type_mapping(db, type_mapping)),
|
||||||
Self::Dynamic(_) | Self::Generic(_) | Self::Protocol => self,
|
Self::Dynamic(_) | Self::Generic(_) | Self::Protocol => self,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -232,7 +228,7 @@ impl<'db> ClassBase<'db> {
|
||||||
specialization: Option<Specialization<'db>>,
|
specialization: Option<Specialization<'db>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
if let Some(specialization) = specialization {
|
if let Some(specialization) = specialization {
|
||||||
self.apply_specialization(db, specialization)
|
self.apply_type_mapping(db, specialization.type_mapping())
|
||||||
} else {
|
} else {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
|
@ -130,12 +130,7 @@ impl<'db> GenericContext<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn default_specialization(self, db: &'db dyn Db) -> Specialization<'db> {
|
pub(crate) fn default_specialization(self, db: &'db dyn Db) -> Specialization<'db> {
|
||||||
let types = self
|
self.specialize_partial(db, &vec![None; self.variables(db).len()])
|
||||||
.variables(db)
|
|
||||||
.iter()
|
|
||||||
.map(|typevar| typevar.default_ty(db).unwrap_or(Type::unknown()))
|
|
||||||
.collect();
|
|
||||||
self.specialize(db, types)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn identity_specialization(self, db: &'db dyn Db) -> Specialization<'db> {
|
pub(crate) fn identity_specialization(self, db: &'db dyn Db) -> Specialization<'db> {
|
||||||
|
@ -157,7 +152,9 @@ impl<'db> GenericContext<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a specialization of this generic context. Panics if the length of `types` does not
|
/// Creates a specialization of this generic context. Panics if the length of `types` does not
|
||||||
/// match the number of typevars in the generic context.
|
/// match the number of typevars in the generic context. You must provide a specific type for
|
||||||
|
/// each typevar; no defaults are used. (Use [`specialize_partial`](Self::specialize_partial)
|
||||||
|
/// if you might not have types for every typevar.)
|
||||||
pub(crate) fn specialize(
|
pub(crate) fn specialize(
|
||||||
self,
|
self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
|
@ -166,6 +163,50 @@ impl<'db> GenericContext<'db> {
|
||||||
assert!(self.variables(db).len() == types.len());
|
assert!(self.variables(db).len() == types.len());
|
||||||
Specialization::new(db, self, types)
|
Specialization::new(db, self, types)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a specialization of this generic context. Panics if the length of `types` does not
|
||||||
|
/// match the number of typevars in the generic context. If any provided type is `None`, we
|
||||||
|
/// will use the corresponding typevar's default type.
|
||||||
|
pub(crate) fn specialize_partial(
|
||||||
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
types: &[Option<Type<'db>>],
|
||||||
|
) -> Specialization<'db> {
|
||||||
|
let variables = self.variables(db);
|
||||||
|
assert!(variables.len() == types.len());
|
||||||
|
|
||||||
|
// Typevars can have other typevars as their default values, e.g.
|
||||||
|
//
|
||||||
|
// ```py
|
||||||
|
// class C[T, U = T]: ...
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// If there is a mapping for `T`, we want to map `U` to that type, not to `T`. To handle
|
||||||
|
// this, we repeatedly apply the specialization to itself, until we reach a fixed point.
|
||||||
|
let mut expanded = vec![Type::unknown(); types.len()];
|
||||||
|
for (idx, (ty, typevar)) in types.iter().zip(variables).enumerate() {
|
||||||
|
if let Some(ty) = ty {
|
||||||
|
expanded[idx] = *ty;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(default) = typevar.default_ty(db) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Typevars are only allowed to refer to _earlier_ typevars in their defaults. (This is
|
||||||
|
// statically enforced for PEP-695 contexts, and is explicitly called out as a
|
||||||
|
// requirement for legacy contexts.)
|
||||||
|
let type_mapping = TypeMapping::Partial {
|
||||||
|
generic_context: self,
|
||||||
|
types: &expanded[0..idx],
|
||||||
|
};
|
||||||
|
let default = default.apply_type_mapping(db, type_mapping);
|
||||||
|
expanded[idx] = default;
|
||||||
|
}
|
||||||
|
|
||||||
|
Specialization::new(db, self, expanded.into_boxed_slice())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An assignment of a specific type to each type variable in a generic scope.
|
/// An assignment of a specific type to each type variable in a generic scope.
|
||||||
|
@ -180,6 +221,10 @@ pub struct Specialization<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> Specialization<'db> {
|
impl<'db> Specialization<'db> {
|
||||||
|
pub(crate) fn type_mapping(self) -> TypeMapping<'db, 'db> {
|
||||||
|
TypeMapping::Specialization(self)
|
||||||
|
}
|
||||||
|
|
||||||
/// Applies a specialization to this specialization. This is used, for instance, when a generic
|
/// Applies a specialization to this specialization. This is used, for instance, when a generic
|
||||||
/// class inherits from a generic alias:
|
/// class inherits from a generic alias:
|
||||||
///
|
///
|
||||||
|
@ -194,10 +239,18 @@ impl<'db> Specialization<'db> {
|
||||||
/// That lets us produce the generic alias `A[int]`, which is the corresponding entry in the
|
/// That lets us produce the generic alias `A[int]`, which is the corresponding entry in the
|
||||||
/// MRO of `B[int]`.
|
/// MRO of `B[int]`.
|
||||||
pub(crate) fn apply_specialization(self, db: &'db dyn Db, other: Specialization<'db>) -> Self {
|
pub(crate) fn apply_specialization(self, db: &'db dyn Db, other: Specialization<'db>) -> Self {
|
||||||
|
self.apply_type_mapping(db, other.type_mapping())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn apply_type_mapping<'a>(
|
||||||
|
self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
type_mapping: TypeMapping<'a, 'db>,
|
||||||
|
) -> Self {
|
||||||
let types: Box<[_]> = self
|
let types: Box<[_]> = self
|
||||||
.types(db)
|
.types(db)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|ty| ty.apply_specialization(db, other))
|
.map(|ty| ty.apply_type_mapping(db, type_mapping))
|
||||||
.collect();
|
.collect();
|
||||||
Specialization::new(db, self.generic_context(db), types)
|
Specialization::new(db, self.generic_context(db), types)
|
||||||
}
|
}
|
||||||
|
@ -244,16 +297,6 @@ impl<'db> Specialization<'db> {
|
||||||
Self::new(db, self.generic_context(db), types)
|
Self::new(db, self.generic_context(db), types)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the type that a typevar is specialized to, or None if the typevar isn't part of
|
|
||||||
/// this specialization.
|
|
||||||
pub(crate) fn get(self, db: &'db dyn Db, typevar: TypeVarInstance<'db>) -> Option<Type<'db>> {
|
|
||||||
let index = self
|
|
||||||
.generic_context(db)
|
|
||||||
.variables(db)
|
|
||||||
.get_index_of(&typevar)?;
|
|
||||||
Some(self.types(db)[index])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn is_subtype_of(self, db: &'db dyn Db, other: Specialization<'db>) -> bool {
|
pub(crate) fn is_subtype_of(self, db: &'db dyn Db, other: Specialization<'db>) -> bool {
|
||||||
let generic_context = self.generic_context(db);
|
let generic_context = self.generic_context(db);
|
||||||
if generic_context != other.generic_context(db) {
|
if generic_context != other.generic_context(db) {
|
||||||
|
@ -403,6 +446,57 @@ impl<'db> Specialization<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A mapping between type variables and types.
|
||||||
|
///
|
||||||
|
/// You will usually use [`Specialization`] instead of this type. This type is used when we need to
|
||||||
|
/// substitute types for type variables before we have fully constructed a [`Specialization`].
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
|
pub(crate) enum TypeMapping<'a, 'db> {
|
||||||
|
Specialization(Specialization<'db>),
|
||||||
|
Partial {
|
||||||
|
generic_context: GenericContext<'db>,
|
||||||
|
types: &'a [Type<'db>],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'db> TypeMapping<'_, 'db> {
|
||||||
|
fn generic_context(self, db: &'db dyn Db) -> GenericContext<'db> {
|
||||||
|
match self {
|
||||||
|
Self::Specialization(specialization) => specialization.generic_context(db),
|
||||||
|
Self::Partial {
|
||||||
|
generic_context, ..
|
||||||
|
} => generic_context,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the type that a typevar is mapped to, or None if the typevar isn't part of this
|
||||||
|
/// mapping.
|
||||||
|
pub(crate) fn get(self, db: &'db dyn Db, typevar: TypeVarInstance<'db>) -> Option<Type<'db>> {
|
||||||
|
let index = self
|
||||||
|
.generic_context(db)
|
||||||
|
.variables(db)
|
||||||
|
.get_index_of(&typevar)?;
|
||||||
|
match self {
|
||||||
|
Self::Specialization(specialization) => specialization.types(db).get(index).copied(),
|
||||||
|
Self::Partial { types, .. } => types.get(index).copied(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn into_specialization(self, db: &'db dyn Db) -> Specialization<'db> {
|
||||||
|
match self {
|
||||||
|
Self::Specialization(specialization) => specialization,
|
||||||
|
Self::Partial {
|
||||||
|
generic_context,
|
||||||
|
types,
|
||||||
|
} => {
|
||||||
|
let mut types = types.to_vec();
|
||||||
|
types.resize(generic_context.variables(db).len(), Type::unknown());
|
||||||
|
Specialization::new(db, generic_context, types.into_boxed_slice())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Performs type inference between parameter annotations and argument types, producing a
|
/// Performs type inference between parameter annotations and argument types, producing a
|
||||||
/// specialization of a generic function.
|
/// specialization of a generic function.
|
||||||
pub(crate) struct SpecializationBuilder<'db> {
|
pub(crate) struct SpecializationBuilder<'db> {
|
||||||
|
|
|
@ -6899,8 +6899,10 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
}
|
}
|
||||||
_ => CallArgumentTypes::positional([self.infer_type_expression(slice_node)]),
|
_ => CallArgumentTypes::positional([self.infer_type_expression(slice_node)]),
|
||||||
};
|
};
|
||||||
let signature = generic_context.signature(self.db());
|
let signatures = Signatures::single(CallableSignature::single(
|
||||||
let signatures = Signatures::single(CallableSignature::single(value_ty, signature.clone()));
|
value_ty,
|
||||||
|
generic_context.signature(self.db()),
|
||||||
|
));
|
||||||
let bindings = match Bindings::match_parameters(signatures, &call_argument_types)
|
let bindings = match Bindings::match_parameters(signatures, &call_argument_types)
|
||||||
.check_types(self.db(), &call_argument_types)
|
.check_types(self.db(), &call_argument_types)
|
||||||
{
|
{
|
||||||
|
@ -6918,10 +6920,8 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
.matching_overloads()
|
.matching_overloads()
|
||||||
.next()
|
.next()
|
||||||
.expect("valid bindings should have matching overload");
|
.expect("valid bindings should have matching overload");
|
||||||
let parameters = overload
|
let specialization =
|
||||||
.parameter_types_with_defaults(&signature)
|
generic_context.specialize_partial(self.db(), overload.parameter_types());
|
||||||
.expect("matching overload should not have missing arguments");
|
|
||||||
let specialization = generic_context.specialize(self.db(), parameters);
|
|
||||||
Type::from(GenericAlias::new(self.db(), generic_class, specialization))
|
Type::from(GenericAlias::new(self.db(), generic_class, specialization))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +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::types::generics::TypeMapping;
|
||||||
use crate::Db;
|
use crate::Db;
|
||||||
|
|
||||||
pub(super) use synthesized_protocol::SynthesizedProtocolType;
|
pub(super) use synthesized_protocol::SynthesizedProtocolType;
|
||||||
|
@ -113,13 +113,13 @@ impl<'db> NominalInstanceType<'db> {
|
||||||
SubclassOfType::from(db, self.class)
|
SubclassOfType::from(db, self.class)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn apply_specialization(
|
pub(super) fn apply_type_mapping<'a>(
|
||||||
self,
|
self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
specialization: Specialization<'db>,
|
type_mapping: TypeMapping<'a, 'db>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
class: self.class.apply_specialization(db, specialization),
|
class: self.class.apply_type_mapping(db, type_mapping),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ use smallvec::{smallvec, SmallVec};
|
||||||
|
|
||||||
use super::{definition_expression_type, DynamicType, Type};
|
use super::{definition_expression_type, DynamicType, Type};
|
||||||
use crate::semantic_index::definition::Definition;
|
use crate::semantic_index::definition::Definition;
|
||||||
use crate::types::generics::{GenericContext, Specialization};
|
use crate::types::generics::{GenericContext, Specialization, TypeMapping};
|
||||||
use crate::types::{todo_type, TypeVarInstance};
|
use crate::types::{todo_type, TypeVarInstance};
|
||||||
use crate::{Db, FxOrderSet};
|
use crate::{Db, FxOrderSet};
|
||||||
use ruff_python_ast::{self as ast, name::Name};
|
use ruff_python_ast::{self as ast, name::Name};
|
||||||
|
@ -313,14 +313,22 @@ impl<'db> Signature<'db> {
|
||||||
&self,
|
&self,
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
specialization: Specialization<'db>,
|
specialization: Specialization<'db>,
|
||||||
|
) -> Self {
|
||||||
|
self.apply_type_mapping(db, specialization.type_mapping())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn apply_type_mapping<'a>(
|
||||||
|
&self,
|
||||||
|
db: &'db dyn Db,
|
||||||
|
type_mapping: TypeMapping<'a, 'db>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
generic_context: self.generic_context,
|
generic_context: self.generic_context,
|
||||||
inherited_generic_context: self.inherited_generic_context,
|
inherited_generic_context: self.inherited_generic_context,
|
||||||
parameters: self.parameters.apply_specialization(db, specialization),
|
parameters: self.parameters.apply_type_mapping(db, type_mapping),
|
||||||
return_ty: self
|
return_ty: self
|
||||||
.return_ty
|
.return_ty
|
||||||
.map(|ty| ty.apply_specialization(db, specialization)),
|
.map(|ty| ty.apply_type_mapping(db, type_mapping)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1053,12 +1061,12 @@ impl<'db> Parameters<'db> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_specialization(&self, db: &'db dyn Db, specialization: Specialization<'db>) -> Self {
|
fn apply_type_mapping<'a>(&self, db: &'db dyn Db, type_mapping: TypeMapping<'a, 'db>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
value: self
|
value: self
|
||||||
.value
|
.value
|
||||||
.iter()
|
.iter()
|
||||||
.map(|param| param.apply_specialization(db, specialization))
|
.map(|param| param.apply_type_mapping(db, type_mapping))
|
||||||
.collect(),
|
.collect(),
|
||||||
is_gradual: self.is_gradual,
|
is_gradual: self.is_gradual,
|
||||||
}
|
}
|
||||||
|
@ -1225,12 +1233,12 @@ impl<'db> Parameter<'db> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_specialization(&self, db: &'db dyn Db, specialization: Specialization<'db>) -> Self {
|
fn apply_type_mapping<'a>(&self, db: &'db dyn Db, type_mapping: TypeMapping<'a, 'db>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
annotated_type: self
|
annotated_type: self
|
||||||
.annotated_type
|
.annotated_type
|
||||||
.map(|ty| ty.apply_specialization(db, specialization)),
|
.map(|ty| ty.apply_type_mapping(db, type_mapping)),
|
||||||
kind: self.kind.apply_specialization(db, specialization),
|
kind: self.kind.apply_type_mapping(db, type_mapping),
|
||||||
form: self.form,
|
form: self.form,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1422,24 +1430,24 @@ pub(crate) enum ParameterKind<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> ParameterKind<'db> {
|
impl<'db> ParameterKind<'db> {
|
||||||
fn apply_specialization(&self, db: &'db dyn Db, specialization: Specialization<'db>) -> Self {
|
fn apply_type_mapping<'a>(&self, db: &'db dyn Db, type_mapping: TypeMapping<'a, 'db>) -> Self {
|
||||||
match self {
|
match self {
|
||||||
Self::PositionalOnly { default_type, name } => Self::PositionalOnly {
|
Self::PositionalOnly { default_type, name } => Self::PositionalOnly {
|
||||||
default_type: default_type
|
default_type: default_type
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|ty| ty.apply_specialization(db, specialization)),
|
.map(|ty| ty.apply_type_mapping(db, type_mapping)),
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
},
|
},
|
||||||
Self::PositionalOrKeyword { default_type, name } => Self::PositionalOrKeyword {
|
Self::PositionalOrKeyword { default_type, name } => Self::PositionalOrKeyword {
|
||||||
default_type: default_type
|
default_type: default_type
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|ty| ty.apply_specialization(db, specialization)),
|
.map(|ty| ty.apply_type_mapping(db, type_mapping)),
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
},
|
},
|
||||||
Self::KeywordOnly { default_type, name } => Self::KeywordOnly {
|
Self::KeywordOnly { default_type, name } => Self::KeywordOnly {
|
||||||
default_type: default_type
|
default_type: default_type
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|ty| ty.apply_specialization(db, specialization)),
|
.map(|ty| ty.apply_type_mapping(db, type_mapping)),
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
},
|
},
|
||||||
Self::Variadic { .. } | Self::KeywordVariadic { .. } => self.clone(),
|
Self::Variadic { .. } | Self::KeywordVariadic { .. } => self.clone(),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue