diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md index 88718bd914..66f7574243 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md @@ -90,7 +90,7 @@ reveal_type(generic_context(ExplicitInheritedGenericPartiallySpecializedExtraTyp The type parameter can be specified explicitly: ```py -from typing import Generic, TypeVar +from typing import Generic, Literal, TypeVar T = TypeVar("T") @@ -98,6 +98,7 @@ class C(Generic[T]): x: T reveal_type(C[int]()) # revealed: C[int] +reveal_type(C[Literal[5]]()) # revealed: C[Literal[5]] ``` The specialization must match the generic types: @@ -229,9 +230,9 @@ class C(Generic[T]): def __new__(cls, x: T) -> "C[T]": return object.__new__(cls) -reveal_type(C(1)) # revealed: C[Literal[1]] +reveal_type(C(1)) # revealed: C[int] -# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`" +# error: [invalid-assignment] "Object of type `C[str]` is not assignable to `C[int]`" wrong_innards: C[int] = C("five") ``` @@ -245,9 +246,9 @@ T = TypeVar("T") class C(Generic[T]): def __init__(self, x: T) -> None: ... -reveal_type(C(1)) # revealed: C[Literal[1]] +reveal_type(C(1)) # revealed: C[int] -# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`" +# error: [invalid-assignment] "Object of type `C[str]` is not assignable to `C[int]`" wrong_innards: C[int] = C("five") ``` @@ -264,9 +265,9 @@ class C(Generic[T]): def __init__(self, x: T) -> None: ... -reveal_type(C(1)) # revealed: C[Literal[1]] +reveal_type(C(1)) # revealed: C[int] -# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`" +# error: [invalid-assignment] "Object of type `C[str]` is not assignable to `C[int]`" wrong_innards: C[int] = C("five") ``` @@ -283,9 +284,9 @@ class C(Generic[T]): def __init__(self, x: T) -> None: ... -reveal_type(C(1)) # revealed: C[Literal[1]] +reveal_type(C(1)) # revealed: C[int] -# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`" +# error: [invalid-assignment] "Object of type `C[str]` is not assignable to `C[int]`" wrong_innards: C[int] = C("five") class D(Generic[T]): @@ -294,9 +295,9 @@ class D(Generic[T]): def __init__(self, *args, **kwargs) -> None: ... -reveal_type(D(1)) # revealed: D[Literal[1]] +reveal_type(D(1)) # revealed: D[int] -# error: [invalid-assignment] "Object of type `D[Literal["five"]]` is not assignable to `D[int]`" +# error: [invalid-assignment] "Object of type `D[str]` is not assignable to `D[int]`" wrong_innards: D[int] = D("five") ``` @@ -319,7 +320,7 @@ class C(Generic[T, U]): class D(C[V, int]): def __init__(self, x: V) -> None: ... -reveal_type(D(1)) # revealed: D[Literal[1]] +reveal_type(D(1)) # revealed: D[int] ``` ### `__init__` is itself generic @@ -333,11 +334,11 @@ T = TypeVar("T") class C(Generic[T]): def __init__(self, x: T, y: S) -> None: ... -reveal_type(C(1, 1)) # revealed: C[Literal[1]] -reveal_type(C(1, "string")) # revealed: C[Literal[1]] -reveal_type(C(1, True)) # revealed: C[Literal[1]] +reveal_type(C(1, 1)) # revealed: C[int] +reveal_type(C(1, "string")) # revealed: C[int] +reveal_type(C(1, True)) # revealed: C[int] -# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`" +# error: [invalid-assignment] "Object of type `C[str]` is not assignable to `C[int]`" wrong_innards: C[int] = C("five", 1) ``` diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md index 2bf153b969..c205d46617 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md @@ -74,10 +74,13 @@ class BothGenericSyntaxes[U](Generic[T]): ... The type parameter can be specified explicitly: ```py +from typing import Literal + class C[T]: x: T reveal_type(C[int]()) # revealed: C[int] +reveal_type(C[Literal[5]]()) # revealed: C[Literal[5]] ``` The specialization must match the generic types: @@ -190,9 +193,9 @@ class C[T]: def __new__(cls, x: T) -> "C[T]": return object.__new__(cls) -reveal_type(C(1)) # revealed: C[Literal[1]] +reveal_type(C(1)) # revealed: C[int] -# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`" +# error: [invalid-assignment] "Object of type `C[str]` is not assignable to `C[int]`" wrong_innards: C[int] = C("five") ``` @@ -202,9 +205,9 @@ wrong_innards: C[int] = C("five") class C[T]: def __init__(self, x: T) -> None: ... -reveal_type(C(1)) # revealed: C[Literal[1]] +reveal_type(C(1)) # revealed: C[int] -# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`" +# error: [invalid-assignment] "Object of type `C[str]` is not assignable to `C[int]`" wrong_innards: C[int] = C("five") ``` @@ -217,9 +220,9 @@ class C[T]: def __init__(self, x: T) -> None: ... -reveal_type(C(1)) # revealed: C[Literal[1]] +reveal_type(C(1)) # revealed: C[int] -# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`" +# error: [invalid-assignment] "Object of type `C[str]` is not assignable to `C[int]`" wrong_innards: C[int] = C("five") ``` @@ -232,9 +235,9 @@ class C[T]: def __init__(self, x: T) -> None: ... -reveal_type(C(1)) # revealed: C[Literal[1]] +reveal_type(C(1)) # revealed: C[int] -# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`" +# error: [invalid-assignment] "Object of type `C[str]` is not assignable to `C[int]`" wrong_innards: C[int] = C("five") class D[T]: @@ -243,9 +246,9 @@ class D[T]: def __init__(self, *args, **kwargs) -> None: ... -reveal_type(D(1)) # revealed: D[Literal[1]] +reveal_type(D(1)) # revealed: D[int] -# error: [invalid-assignment] "Object of type `D[Literal["five"]]` is not assignable to `D[int]`" +# error: [invalid-assignment] "Object of type `D[str]` is not assignable to `D[int]`" wrong_innards: D[int] = D("five") ``` @@ -262,7 +265,7 @@ class C[T, U]: class D[V](C[V, int]): def __init__(self, x: V) -> None: ... -reveal_type(D(1)) # revealed: D[Literal[1]] +reveal_type(D(1)) # revealed: D[int] ``` ### `__init__` is itself generic @@ -271,11 +274,11 @@ reveal_type(D(1)) # revealed: D[Literal[1]] class C[T]: def __init__[S](self, x: T, y: S) -> None: ... -reveal_type(C(1, 1)) # revealed: C[Literal[1]] -reveal_type(C(1, "string")) # revealed: C[Literal[1]] -reveal_type(C(1, True)) # revealed: C[Literal[1]] +reveal_type(C(1, 1)) # revealed: C[int] +reveal_type(C(1, "string")) # revealed: C[int] +reveal_type(C(1, True)) # revealed: C[int] -# error: [invalid-assignment] "Object of type `C[Literal["five"]]` is not assignable to `C[int]`" +# error: [invalid-assignment] "Object of type `C[str]` is not assignable to `C[int]`" wrong_innards: C[int] = C("five", 1) ``` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 8c8ad9f461..7c64993ae8 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -45,7 +45,7 @@ use crate::types::call::{Bindings, CallArgumentTypes, CallableBinding}; pub(crate) use crate::types::class_base::ClassBase; use crate::types::context::{LintDiagnosticGuard, LintDiagnosticGuardBuilder}; use crate::types::diagnostic::{INVALID_TYPE_FORM, UNSUPPORTED_BOOL_CONVERSION}; -use crate::types::generics::{GenericContext, Specialization, TypeMapping}; +use crate::types::generics::{GenericContext, PartialSpecialization, Specialization}; use crate::types::infer::infer_unpack_types; use crate::types::mro::{Mro, MroError, MroIterator}; pub(crate) use crate::types::narrow::infer_narrowing_constraint; @@ -342,7 +342,7 @@ pub struct PropertyInstanceType<'db> { } impl<'db> PropertyInstanceType<'db> { - fn apply_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: TypeMapping<'a, 'db>) -> Self { + fn apply_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self { let getter = self .getter(db) .map(|ty| ty.apply_type_mapping(db, type_mapping)); @@ -963,6 +963,23 @@ impl<'db> Type<'db> { if yes { self.negate(db) } else { *self } } + /// Returns the fallback instance type that a literal is an instance of, or `None` if the type + /// is not a literal. + pub fn literal_fallback_instance(self, db: &'db dyn Db) -> Option> { + // There are other literal types that could conceivable be included here: class literals + // falling back to `type[X]`, for instance. For now, there is not much rigorous thought put + // into what's included vs not; this is just an empirical choice that makes our ecosystem + // report look better until we have proper bidirectional type inference. + match self { + Type::StringLiteral(_) | Type::LiteralString => Some(KnownClass::Str.to_instance(db)), + Type::BooleanLiteral(_) => Some(KnownClass::Bool.to_instance(db)), + Type::IntLiteral(_) => Some(KnownClass::Int.to_instance(db)), + Type::BytesLiteral(_) => Some(KnownClass::Bytes.to_instance(db)), + Type::ModuleLiteral(_) => Some(KnownClass::ModuleType.to_instance(db)), + _ => None, + } + } + /// Return a "normalized" version of `self` that ensures that equivalent types have the same Salsa ID. /// /// A normalized type: @@ -1210,19 +1227,16 @@ impl<'db> Type<'db> { // Except for the special `LiteralString` case above, // most `Literal` types delegate to their instance fallbacks // unless `self` is exactly equivalent to `target` (handled above) - (Type::StringLiteral(_) | Type::LiteralString, _) => { - KnownClass::Str.to_instance(db).is_subtype_of(db, target) - } - (Type::BooleanLiteral(_), _) => { - KnownClass::Bool.to_instance(db).is_subtype_of(db, target) - } - (Type::IntLiteral(_), _) => KnownClass::Int.to_instance(db).is_subtype_of(db, target), - (Type::BytesLiteral(_), _) => { - KnownClass::Bytes.to_instance(db).is_subtype_of(db, target) - } - (Type::ModuleLiteral(_), _) => KnownClass::ModuleType - .to_instance(db) - .is_subtype_of(db, target), + ( + Type::StringLiteral(_) + | Type::LiteralString + | Type::BooleanLiteral(_) + | Type::IntLiteral(_) + | Type::BytesLiteral(_) + | Type::ModuleLiteral(_), + _, + ) => (self.literal_fallback_instance(db)) + .is_some_and(|instance| instance.is_subtype_of(db, target)), (Type::FunctionLiteral(self_function_literal), Type::Callable(_)) => { self_function_literal @@ -5124,24 +5138,32 @@ impl<'db> Type<'db> { db: &'db dyn Db, specialization: Specialization<'db>, ) -> Type<'db> { - self.apply_type_mapping(db, specialization.type_mapping()) + self.apply_type_mapping(db, &TypeMapping::Specialization(specialization)) } fn apply_type_mapping<'a>( self, db: &'db dyn Db, - type_mapping: TypeMapping<'a, 'db>, + type_mapping: &TypeMapping<'a, 'db>, ) -> Type<'db> { match self { - Type::TypeVar(typevar) => type_mapping.get(db, typevar).unwrap_or(self), + Type::TypeVar(typevar) => match type_mapping { + TypeMapping::Specialization(specialization) => { + specialization.get(db, typevar).unwrap_or(self) + } + TypeMapping::PartialSpecialization(partial) => { + partial.get(db, typevar).unwrap_or(self) + } + TypeMapping::PromoteLiterals => self, + } Type::FunctionLiteral(function) => { - Type::FunctionLiteral(function.apply_type_mapping(db, type_mapping)) + Type::FunctionLiteral(function.with_type_mapping(db, type_mapping)) } Type::BoundMethod(method) => Type::BoundMethod(BoundMethodType::new( db, - method.function(db).apply_type_mapping(db, type_mapping), + method.function(db).with_type_mapping(db, type_mapping), method.self_instance(db).apply_type_mapping(db, type_mapping), )), @@ -5155,13 +5177,13 @@ impl<'db> Type<'db> { Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderGet(function)) => { Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderGet( - function.apply_type_mapping(db, type_mapping), + function.with_type_mapping(db, type_mapping), )) } Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderCall(function)) => { Type::MethodWrapper(MethodWrapperKind::FunctionTypeDunderCall( - function.apply_type_mapping(db, type_mapping), + function.with_type_mapping(db, type_mapping), )) } @@ -5215,6 +5237,18 @@ impl<'db> Type<'db> { .map(|ty| ty.apply_type_mapping(db, type_mapping)), ), + Type::ModuleLiteral(_) + | Type::IntLiteral(_) + | Type::BooleanLiteral(_) + | Type::LiteralString + | Type::StringLiteral(_) + | Type::BytesLiteral(_) => match type_mapping { + TypeMapping::Specialization(_) | + TypeMapping::PartialSpecialization(_) => self, + TypeMapping::PromoteLiterals => self.literal_fallback_instance(db) + .expect("literal type should have fallback instance type"), + } + Type::Dynamic(_) | Type::Never | Type::AlwaysTruthy @@ -5223,16 +5257,10 @@ impl<'db> Type<'db> { | Type::MethodWrapper(MethodWrapperKind::StrStartswith(_)) | Type::DataclassDecorator(_) | Type::DataclassTransformer(_) - | Type::ModuleLiteral(_) // A non-generic class never needs to be specialized. A generic class is specialized // explicitly (via a subscript expression) or implicitly (via a call), and not because // some other generic context's specialization is applied to it. | Type::ClassLiteral(_) - | Type::IntLiteral(_) - | Type::BooleanLiteral(_) - | Type::LiteralString - | Type::StringLiteral(_) - | Type::BytesLiteral(_) | Type::BoundSuper(_) | Type::KnownInstance(_) => self, } @@ -5516,6 +5544,37 @@ impl<'db> From<&Type<'db>> for Type<'db> { } } +/// A mapping that can be applied to a type, producing another type. This is applied inductively to +/// the components of complex types. +/// +/// This is represented as an enum (with some variants using `Cow`), and not an `FnMut` trait, +/// since we sometimes have to apply type mappings lazily (e.g., to the signature of a function +/// literal). +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub enum TypeMapping<'a, 'db> { + /// Applies a specialization to the type + Specialization(Specialization<'db>), + /// Applies a partial specialization to the type + PartialSpecialization(PartialSpecialization<'a, 'db>), + /// Promotes any literal types to their corresponding instance types (e.g. `Literal["string"]` + /// to `str`) + PromoteLiterals, +} + +impl<'db> TypeMapping<'_, 'db> { + fn to_owned(&self) -> TypeMapping<'db, 'db> { + match self { + TypeMapping::Specialization(specialization) => { + TypeMapping::Specialization(*specialization) + } + TypeMapping::PartialSpecialization(partial) => { + TypeMapping::PartialSpecialization(partial.to_owned()) + } + TypeMapping::PromoteLiterals => TypeMapping::PromoteLiterals, + } + } +} + #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] pub enum DynamicType { // An explicitly annotated `typing.Any` @@ -6685,10 +6744,8 @@ pub struct FunctionType<'db> { /// to its own generic context. inherited_generic_context: Option>, - /// A specialization that should be applied to the function's parameter and return types, - /// either because the function is itself generic, or because it appears in the body of a - /// generic class. - specialization: Option>, + /// Type mappings that should be applied to the function's parameter and return types. + type_mappings: Box<[TypeMapping<'db, 'db>]>, } #[salsa::tracked] @@ -6777,29 +6834,33 @@ impl<'db> FunctionType<'db> { #[salsa::tracked(returns(ref), cycle_fn=signature_cycle_recover, cycle_initial=signature_cycle_initial)] pub(crate) fn signature(self, db: &'db dyn Db) -> FunctionSignature<'db> { let inherited_generic_context = self.inherited_generic_context(db); - let specialization = self.specialization(db); + let type_mappings = self.type_mappings(db); if let Some(overloaded) = self.to_overloaded(db) { FunctionSignature { overloads: CallableSignature::from_overloads( Type::FunctionLiteral(self), overloaded.overloads.iter().copied().map(|overload| { - overload - .internal_signature(db, inherited_generic_context) - .apply_optional_specialization(db, specialization) + type_mappings.iter().fold( + overload.internal_signature(db, inherited_generic_context), + |ty, mapping| ty.apply_type_mapping(db, mapping), + ) }), ), implementation: overloaded.implementation.map(|implementation| { - implementation - .internal_signature(db, inherited_generic_context) - .apply_optional_specialization(db, specialization) + type_mappings.iter().fold( + implementation.internal_signature(db, inherited_generic_context), + |ty, mapping| ty.apply_type_mapping(db, mapping), + ) }), } } else { FunctionSignature { overloads: CallableSignature::single( Type::FunctionLiteral(self), - self.internal_signature(db, inherited_generic_context) - .apply_optional_specialization(db, specialization), + type_mappings.iter().fold( + self.internal_signature(db, inherited_generic_context), + |ty, mapping| ty.apply_type_mapping(db, mapping), + ), ), implementation: None, } @@ -6854,7 +6915,7 @@ impl<'db> FunctionType<'db> { self.decorators(db), Some(params), self.inherited_generic_context(db), - self.specialization(db), + self.type_mappings(db), ) } @@ -6873,15 +6934,17 @@ impl<'db> FunctionType<'db> { self.decorators(db), self.dataclass_transformer_params(db), Some(inherited_generic_context), - self.specialization(db), + self.type_mappings(db), ) } - fn apply_specialization(self, db: &'db dyn Db, specialization: Specialization<'db>) -> Self { - let specialization = match self.specialization(db) { - Some(existing) => existing.apply_specialization(db, specialization), - None => specialization, - }; + fn with_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self { + let type_mappings: Box<[_]> = self + .type_mappings(db) + .iter() + .cloned() + .chain(std::iter::once(type_mapping.to_owned())) + .collect(); Self::new( db, self.name(db).clone(), @@ -6890,14 +6953,10 @@ impl<'db> FunctionType<'db> { self.decorators(db), self.dataclass_transformer_params(db), self.inherited_generic_context(db), - Some(specialization), + type_mappings, ) } - 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( self, db: &'db dyn Db, @@ -7408,7 +7467,7 @@ impl<'db> CallableType<'db> { ) } - fn apply_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: TypeMapping<'a, 'db>) -> Self { + fn apply_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self { CallableType::from_overloads( db, self.signatures(db) diff --git a/crates/ty_python_semantic/src/types/call/bind.rs b/crates/ty_python_semantic/src/types/call/bind.rs index 7394436fcd..be6641e510 100644 --- a/crates/ty_python_semantic/src/types/call/bind.rs +++ b/crates/ty_python_semantic/src/types/call/bind.rs @@ -22,7 +22,7 @@ use crate::types::signatures::{Parameter, ParameterForm}; use crate::types::{ BoundMethodType, DataclassParams, DataclassTransformerParams, FunctionDecorators, FunctionType, KnownClass, KnownFunction, KnownInstanceType, MethodWrapperKind, PropertyInstanceType, - TupleType, UnionType, WrapperDescriptorKind, todo_type, + TupleType, TypeMapping, UnionType, WrapperDescriptorKind, todo_type, }; use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, SubDiagnostic}; use ruff_python_ast as ast; @@ -1406,9 +1406,14 @@ impl<'db> Binding<'db> { } } self.specialization = signature.generic_context.map(|gc| builder.build(gc)); - self.inherited_specialization = signature - .inherited_generic_context - .map(|gc| builder.build(gc)); + self.inherited_specialization = signature.inherited_generic_context.map(|gc| { + // The inherited generic context is used when inferring the specialization of a + // generic class from a constructor call. In this case (only), we promote any + // typevars that are inferred as a literal to the corresponding instance type. + builder + .build(gc) + .apply_type_mapping(db, &TypeMapping::PromoteLiterals) + }); } num_synthetic_args = 0; diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 1bb38130d8..544c0e4437 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -8,11 +8,11 @@ use super::{ }; use crate::semantic_index::DeclarationWithConstraint; use crate::semantic_index::definition::Definition; -use crate::types::generics::{GenericContext, Specialization, TypeMapping}; +use crate::types::generics::{GenericContext, Specialization}; use crate::types::signatures::{Parameter, Parameters}; use crate::types::{ CallableType, DataclassParams, DataclassTransformerParams, KnownInstanceType, Signature, - TypeVarInstance, + TypeMapping, TypeVarInstance, }; use crate::{ Db, FxOrderSet, KnownModule, Program, @@ -175,7 +175,7 @@ impl<'db> GenericAlias<'db> { pub(super) fn apply_type_mapping<'a>( self, db: &'db dyn Db, - type_mapping: TypeMapping<'a, 'db>, + type_mapping: &TypeMapping<'a, 'db>, ) -> Self { Self::new( db, @@ -278,7 +278,7 @@ impl<'db> ClassType<'db> { pub(super) fn apply_type_mapping<'a>( self, db: &'db dyn Db, - type_mapping: TypeMapping<'a, 'db>, + type_mapping: &TypeMapping<'a, 'db>, ) -> Self { match self { Self::NonGeneric(_) => self, diff --git a/crates/ty_python_semantic/src/types/class_base.rs b/crates/ty_python_semantic/src/types/class_base.rs index 66add36f2c..d8ad6b444c 100644 --- a/crates/ty_python_semantic/src/types/class_base.rs +++ b/crates/ty_python_semantic/src/types/class_base.rs @@ -1,7 +1,8 @@ use crate::Db; -use crate::types::generics::{GenericContext, Specialization, TypeMapping}; +use crate::types::generics::{GenericContext, Specialization}; use crate::types::{ - ClassType, DynamicType, KnownClass, KnownInstanceType, MroError, MroIterator, Type, todo_type, + ClassType, DynamicType, KnownClass, KnownInstanceType, MroError, MroIterator, Type, + TypeMapping, todo_type, }; /// Enumeration of the possible kinds of types we allow in class bases. @@ -254,7 +255,7 @@ impl<'db> ClassBase<'db> { } } - fn apply_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: TypeMapping<'a, 'db>) -> Self { + fn apply_type_mapping<'a>(self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self { match self { Self::Class(class) => Self::Class(class.apply_type_mapping(db, type_mapping)), Self::Dynamic(_) | Self::Generic(_) | Self::Protocol(_) => self, @@ -267,7 +268,7 @@ impl<'db> ClassBase<'db> { specialization: Option>, ) -> Self { if let Some(specialization) = specialization { - self.apply_type_mapping(db, specialization.type_mapping()) + self.apply_type_mapping(db, &TypeMapping::Specialization(specialization)) } else { self } diff --git a/crates/ty_python_semantic/src/types/generics.rs b/crates/ty_python_semantic/src/types/generics.rs index 93c3b5bbd0..aa7e8c3c3d 100644 --- a/crates/ty_python_semantic/src/types/generics.rs +++ b/crates/ty_python_semantic/src/types/generics.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use ruff_python_ast as ast; use rustc_hash::FxHashMap; @@ -7,8 +9,8 @@ use crate::types::class_base::ClassBase; use crate::types::instance::{NominalInstanceType, Protocol, ProtocolInstanceType}; use crate::types::signatures::{Parameter, Parameters, Signature}; use crate::types::{ - KnownInstanceType, Type, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarVariance, - UnionType, declaration_type, todo_type, + KnownInstanceType, Type, TypeMapping, TypeVarBoundOrConstraints, TypeVarInstance, + TypeVarVariance, UnionType, declaration_type, todo_type, }; use crate::{Db, FxOrderSet}; @@ -219,11 +221,12 @@ impl<'db> GenericContext<'db> { // 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 { + let partial = PartialSpecialization { generic_context: self, - types: &expanded[0..idx], + types: Cow::Borrowed(&expanded[0..idx]), }; - let default = default.apply_type_mapping(db, type_mapping); + let default = + default.apply_type_mapping(db, &TypeMapping::PartialSpecialization(partial)); expanded[idx] = default; } @@ -295,8 +298,14 @@ pub struct Specialization<'db> { } impl<'db> Specialization<'db> { - pub(crate) fn type_mapping(self) -> TypeMapping<'db, 'db> { - TypeMapping::Specialization(self) + /// 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> { + let index = self + .generic_context(db) + .variables(db) + .get_index_of(&typevar)?; + self.types(db).get(index).copied() } /// Applies a specialization to this specialization. This is used, for instance, when a generic @@ -313,13 +322,13 @@ impl<'db> Specialization<'db> { /// That lets us produce the generic alias `A[int]`, which is the corresponding entry in the /// MRO of `B[int]`. pub(crate) fn apply_specialization(self, db: &'db dyn Db, other: Specialization<'db>) -> Self { - self.apply_type_mapping(db, other.type_mapping()) + self.apply_type_mapping(db, &TypeMapping::Specialization(other)) } pub(crate) fn apply_type_mapping<'a>( self, db: &'db dyn Db, - type_mapping: TypeMapping<'a, 'db>, + type_mapping: &TypeMapping<'a, 'db>, ) -> Self { let types: Box<[_]> = self .types(db) @@ -527,49 +536,24 @@ impl<'db> Specialization<'db> { /// /// 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>], - }, +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct PartialSpecialization<'a, 'db> { + generic_context: GenericContext<'db>, + types: Cow<'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, - } - } - +impl<'db> PartialSpecialization<'_, 'db> { /// 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> { - 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 get(&self, db: &'db dyn Db, typevar: TypeVarInstance<'db>) -> Option> { + let index = self.generic_context.variables(db).get_index_of(&typevar)?; + self.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()) - } + pub(crate) fn to_owned(&self) -> PartialSpecialization<'db, 'db> { + PartialSpecialization { + generic_context: self.generic_context, + types: Cow::from(self.types.clone().into_owned()), } } } diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 7537e689de..7359649a3f 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -2010,7 +2010,7 @@ impl<'db> TypeInferenceBuilder<'db> { .to_scope_id(self.db(), self.file()); let inherited_generic_context = None; - let specialization = None; + let type_mappings = Box::from([]); let mut inferred_ty = Type::FunctionLiteral(FunctionType::new( self.db(), @@ -2020,7 +2020,7 @@ impl<'db> TypeInferenceBuilder<'db> { function_decorators, dataclass_transformer_params, inherited_generic_context, - specialization, + type_mappings, )); for (decorator_ty, decorator_node) in decorator_types_and_nodes.iter().rev() { diff --git a/crates/ty_python_semantic/src/types/instance.rs b/crates/ty_python_semantic/src/types/instance.rs index 68f21d6e9a..d1ad4d47f9 100644 --- a/crates/ty_python_semantic/src/types/instance.rs +++ b/crates/ty_python_semantic/src/types/instance.rs @@ -5,8 +5,7 @@ use std::marker::PhantomData; use super::protocol_class::ProtocolInterface; use super::{ClassType, KnownClass, SubclassOfType, Type}; use crate::symbol::{Symbol, SymbolAndQualifiers}; -use crate::types::generics::TypeMapping; -use crate::types::{ClassLiteral, TypeVarInstance}; +use crate::types::{ClassLiteral, TypeMapping, TypeVarInstance}; use crate::{Db, FxOrderSet}; pub(super) use synthesized_protocol::SynthesizedProtocolType; @@ -139,7 +138,7 @@ impl<'db> NominalInstanceType<'db> { pub(super) fn apply_type_mapping<'a>( self, db: &'db dyn Db, - type_mapping: TypeMapping<'a, 'db>, + type_mapping: &TypeMapping<'a, 'db>, ) -> Self { Self::from_class(self.class.apply_type_mapping(db, type_mapping)) } @@ -312,7 +311,7 @@ impl<'db> ProtocolInstanceType<'db> { pub(super) fn apply_type_mapping<'a>( self, db: &'db dyn Db, - type_mapping: TypeMapping<'a, 'db>, + type_mapping: &TypeMapping<'a, 'db>, ) -> Self { match self.inner { Protocol::FromClass(class) => { @@ -364,9 +363,8 @@ impl<'db> Protocol<'db> { } mod synthesized_protocol { - use crate::types::TypeVarInstance; - use crate::types::generics::TypeMapping; use crate::types::protocol_class::ProtocolInterface; + use crate::types::{TypeMapping, TypeVarInstance}; use crate::{Db, FxOrderSet}; /// A "synthesized" protocol type that is dissociated from a class definition in source code. @@ -389,7 +387,7 @@ mod synthesized_protocol { pub(super) fn apply_type_mapping<'a>( self, db: &'db dyn Db, - type_mapping: TypeMapping<'a, 'db>, + type_mapping: &TypeMapping<'a, 'db>, ) -> Self { Self(self.0.specialized_and_normalized(db, type_mapping)) } diff --git a/crates/ty_python_semantic/src/types/protocol_class.rs b/crates/ty_python_semantic/src/types/protocol_class.rs index 6cf9864852..43d7697b61 100644 --- a/crates/ty_python_semantic/src/types/protocol_class.rs +++ b/crates/ty_python_semantic/src/types/protocol_class.rs @@ -176,7 +176,7 @@ impl<'db> ProtocolInterface<'db> { pub(super) fn specialized_and_normalized<'a>( self, db: &'db dyn Db, - type_mapping: TypeMapping<'a, 'db>, + type_mapping: &TypeMapping<'a, 'db>, ) -> Self { match self { Self::Members(members) => Self::Members(ProtocolInterfaceMembers::new( @@ -226,7 +226,7 @@ impl<'db> ProtocolMemberData<'db> { } } - fn apply_type_mapping<'a>(&self, db: &'db dyn Db, type_mapping: TypeMapping<'a, 'db>) -> Self { + fn apply_type_mapping<'a>(&self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self { Self { ty: self.ty.apply_type_mapping(db, type_mapping), qualifiers: self.qualifiers, diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index 41552b85e6..35d86f5b1e 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -17,8 +17,8 @@ use smallvec::{SmallVec, smallvec}; use super::{DynamicType, Type, definition_expression_type}; use crate::semantic_index::definition::Definition; -use crate::types::generics::{GenericContext, Specialization, TypeMapping}; -use crate::types::{ClassLiteral, TypeVarInstance, todo_type}; +use crate::types::generics::GenericContext; +use crate::types::{ClassLiteral, TypeMapping, TypeVarInstance, todo_type}; use crate::{Db, FxOrderSet}; use ruff_python_ast::{self as ast, name::Name}; @@ -313,22 +313,10 @@ impl<'db> Signature<'db> { } } - pub(crate) fn apply_optional_specialization( - self, - db: &'db dyn Db, - specialization: Option>, - ) -> Self { - if let Some(specialization) = specialization { - self.apply_type_mapping(db, specialization.type_mapping()) - } else { - self - } - } - pub(crate) fn apply_type_mapping<'a>( &self, db: &'db dyn Db, - type_mapping: TypeMapping<'a, 'db>, + type_mapping: &TypeMapping<'a, 'db>, ) -> Self { Self { generic_context: self.generic_context, @@ -1091,7 +1079,7 @@ impl<'db> Parameters<'db> { ) } - fn apply_type_mapping<'a>(&self, db: &'db dyn Db, type_mapping: TypeMapping<'a, 'db>) -> Self { + fn apply_type_mapping<'a>(&self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self { Self { value: self .value @@ -1263,7 +1251,7 @@ impl<'db> Parameter<'db> { self } - fn apply_type_mapping<'a>(&self, db: &'db dyn Db, type_mapping: TypeMapping<'a, 'db>) -> Self { + fn apply_type_mapping<'a>(&self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self { Self { annotated_type: self .annotated_type @@ -1468,7 +1456,7 @@ pub(crate) enum ParameterKind<'db> { } impl<'db> ParameterKind<'db> { - fn apply_type_mapping<'a>(&self, db: &'db dyn Db, type_mapping: TypeMapping<'a, 'db>) -> Self { + fn apply_type_mapping<'a>(&self, db: &'db dyn Db, type_mapping: &TypeMapping<'a, 'db>) -> Self { match self { Self::PositionalOnly { default_type, name } => Self::PositionalOnly { default_type: default_type diff --git a/crates/ty_python_semantic/src/types/subclass_of.rs b/crates/ty_python_semantic/src/types/subclass_of.rs index 127540abca..af54b4504a 100644 --- a/crates/ty_python_semantic/src/types/subclass_of.rs +++ b/crates/ty_python_semantic/src/types/subclass_of.rs @@ -1,9 +1,9 @@ use crate::symbol::SymbolAndQualifiers; -use crate::types::generics::TypeMapping; +use crate::types::{ + ClassType, DynamicType, KnownClass, MemberLookupPolicy, Type, TypeMapping, TypeVarInstance, +}; use crate::{Db, FxOrderSet}; -use super::{ClassType, DynamicType, KnownClass, MemberLookupPolicy, Type, TypeVarInstance}; - /// A type that represents `type[C]`, i.e. the class object `C` and class objects that are subclasses of `C`. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)] pub struct SubclassOfType<'db> { @@ -71,7 +71,7 @@ impl<'db> SubclassOfType<'db> { pub(super) fn apply_type_mapping<'a>( self, db: &'db dyn Db, - type_mapping: TypeMapping<'a, 'db>, + type_mapping: &TypeMapping<'a, 'db>, ) -> Self { match self.subclass_of { SubclassOfInner::Class(class) => Self {