mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-09 21:28:21 +00:00
[ty] Promote literals when inferring class specializations from constructors (#18102)
This implements the stopgap approach described in https://github.com/astral-sh/ty/issues/336#issuecomment-2880532213 for handling literal types in generic class specializations. With this approach, we will promote any literal to its instance type, but _only_ when inferring a generic class specialization from a constructor call: ```py class C[T]: def __init__(self, x: T) -> None: ... reveal_type(C("string")) # revealed: C[str] ``` If you specialize the class explicitly, we still use whatever type you provide, even if it's a literal: ```py from typing import Literal reveal_type(C[Literal[5]](5)) # revealed: C[Literal[5]] ``` And this doesn't apply at all to generic functions: ```py def f[T](x: T) -> T: return x reveal_type(f(5)) # revealed: Literal[5] ``` --- As part of making this happen, we also generalize the `TypeMapping` machinery. This provides a way to apply a function to type, returning a new type. Complicating matters is that for function literals, we have to apply the mapping lazily, since the function's signature is not created until (and if) someone calls its `signature` method. That means we have to stash away the mappings that we want to apply to the signatures parameter/return annotations once we do create it. This requires some minor `Cow` shenanigans to continue working for partial specializations.
This commit is contained in:
parent
fb589730ef
commit
ce43dbab58
12 changed files with 215 additions and 176 deletions
|
@ -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)
|
||||
```
|
||||
|
||||
|
|
|
@ -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)
|
||||
```
|
||||
|
||||
|
|
|
@ -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<Type<'db>> {
|
||||
// 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<GenericContext<'db>>,
|
||||
|
||||
/// 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<Specialization<'db>>,
|
||||
/// 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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<Specialization<'db>>,
|
||||
) -> Self {
|
||||
if let Some(specialization) = specialization {
|
||||
self.apply_type_mapping(db, specialization.type_mapping())
|
||||
self.apply_type_mapping(db, &TypeMapping::Specialization(specialization))
|
||||
} else {
|
||||
self
|
||||
}
|
||||
|
|
|
@ -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<Type<'db>> {
|
||||
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<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 get(&self, db: &'db dyn Db, typevar: TypeVarInstance<'db>) -> Option<Type<'db>> {
|
||||
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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<Specialization<'db>>,
|
||||
) -> 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
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue