mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 13:24:57 +00:00
[ty] Add support for generic PEP695 type aliases (#20219)
## Summary Adds support for generic PEP695 type aliases, e.g., ```python type A[T] = T reveal_type(A[int]) # A[int] ``` Resolves https://github.com/astral-sh/ty/issues/677.
This commit is contained in:
parent
d55edb3d74
commit
aa5d665d52
8 changed files with 510 additions and 70 deletions
|
@ -1589,12 +1589,11 @@ def ab(a: int, *, c: int):
|
|||
"#,
|
||||
);
|
||||
|
||||
// TODO: This should render T@Alias once we create GenericContexts for type alias scopes.
|
||||
assert_snapshot!(test.hover(), @r"
|
||||
typing.TypeVar
|
||||
T@Alias
|
||||
---------------------------------------------
|
||||
```python
|
||||
typing.TypeVar
|
||||
T@Alias
|
||||
```
|
||||
---------------------------------------------
|
||||
info[hover]: Hovered content is
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
# Generic type aliases: PEP 695 syntax
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.13"
|
||||
```
|
||||
|
||||
## Defining a generic alias
|
||||
|
||||
At its simplest, to define a type alias using PEP 695 syntax, you add a list of `TypeVar`s,
|
||||
`ParamSpec`s or `TypeVarTuple`s after the alias name.
|
||||
|
||||
```py
|
||||
from ty_extensions import generic_context
|
||||
|
||||
type SingleTypevar[T] = ...
|
||||
type MultipleTypevars[T, S] = ...
|
||||
type SingleParamSpec[**P] = ...
|
||||
type TypeVarAndParamSpec[T, **P] = ...
|
||||
type SingleTypeVarTuple[*Ts] = ...
|
||||
type TypeVarAndTypeVarTuple[T, *Ts] = ...
|
||||
|
||||
# revealed: tuple[T@SingleTypevar]
|
||||
reveal_type(generic_context(SingleTypevar))
|
||||
# revealed: tuple[T@MultipleTypevars, S@MultipleTypevars]
|
||||
reveal_type(generic_context(MultipleTypevars))
|
||||
|
||||
# TODO: support `ParamSpec`/`TypeVarTuple` properly
|
||||
# (these should include the `ParamSpec`s and `TypeVarTuple`s in their generic contexts)
|
||||
reveal_type(generic_context(SingleParamSpec)) # revealed: tuple[()]
|
||||
reveal_type(generic_context(TypeVarAndParamSpec)) # revealed: tuple[T@TypeVarAndParamSpec]
|
||||
reveal_type(generic_context(SingleTypeVarTuple)) # revealed: tuple[()]
|
||||
reveal_type(generic_context(TypeVarAndTypeVarTuple)) # revealed: tuple[T@TypeVarAndTypeVarTuple]
|
||||
```
|
||||
|
||||
You cannot use the same typevar more than once.
|
||||
|
||||
```py
|
||||
# error: [invalid-syntax] "duplicate type parameter"
|
||||
type RepeatedTypevar[T, T] = ...
|
||||
```
|
||||
|
||||
## Specializing type aliases explicitly
|
||||
|
||||
The type parameter can be specified explicitly:
|
||||
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
type C[T] = T
|
||||
|
||||
def _(a: C[int], b: C[Literal[5]]):
|
||||
reveal_type(a) # revealed: int
|
||||
reveal_type(b) # revealed: Literal[5]
|
||||
```
|
||||
|
||||
The specialization must match the generic types:
|
||||
|
||||
```py
|
||||
# error: [too-many-positional-arguments] "Too many positional arguments: expected 1, got 2"
|
||||
reveal_type(C[int, int]) # revealed: Unknown
|
||||
```
|
||||
|
||||
And non-generic types cannot be specialized:
|
||||
|
||||
```py
|
||||
type B = ...
|
||||
|
||||
# error: [non-subscriptable] "Cannot subscript non-generic type alias"
|
||||
reveal_type(B[int]) # revealed: Unknown
|
||||
|
||||
# error: [non-subscriptable] "Cannot subscript non-generic type alias"
|
||||
def _(b: B[int]): ...
|
||||
```
|
||||
|
||||
If the type variable has an upper bound, the specialized type must satisfy that bound:
|
||||
|
||||
```py
|
||||
type Bounded[T: int] = ...
|
||||
type BoundedByUnion[T: int | str] = ...
|
||||
|
||||
class IntSubclass(int): ...
|
||||
|
||||
reveal_type(Bounded[int]) # revealed: Bounded[int]
|
||||
reveal_type(Bounded[IntSubclass]) # revealed: Bounded[IntSubclass]
|
||||
|
||||
# TODO: update this diagnostic to talk about type parameters and specializations
|
||||
# error: [invalid-argument-type] "Argument is incorrect: Expected `int`, found `str`"
|
||||
reveal_type(Bounded[str]) # revealed: Unknown
|
||||
|
||||
# TODO: update this diagnostic to talk about type parameters and specializations
|
||||
# error: [invalid-argument-type] "Argument is incorrect: Expected `int`, found `int | str`"
|
||||
reveal_type(Bounded[int | str]) # revealed: Unknown
|
||||
|
||||
reveal_type(BoundedByUnion[int]) # revealed: BoundedByUnion[int]
|
||||
reveal_type(BoundedByUnion[IntSubclass]) # revealed: BoundedByUnion[IntSubclass]
|
||||
reveal_type(BoundedByUnion[str]) # revealed: BoundedByUnion[str]
|
||||
reveal_type(BoundedByUnion[int | str]) # revealed: BoundedByUnion[int | str]
|
||||
```
|
||||
|
||||
If the type variable is constrained, the specialized type must satisfy those constraints:
|
||||
|
||||
```py
|
||||
type Constrained[T: (int, str)] = ...
|
||||
|
||||
reveal_type(Constrained[int]) # revealed: Constrained[int]
|
||||
|
||||
# TODO: error: [invalid-argument-type]
|
||||
# TODO: revealed: Constrained[Unknown]
|
||||
reveal_type(Constrained[IntSubclass]) # revealed: Constrained[IntSubclass]
|
||||
|
||||
reveal_type(Constrained[str]) # revealed: Constrained[str]
|
||||
|
||||
# TODO: error: [invalid-argument-type]
|
||||
# TODO: revealed: Unknown
|
||||
reveal_type(Constrained[int | str]) # revealed: Constrained[int | str]
|
||||
|
||||
# TODO: update this diagnostic to talk about type parameters and specializations
|
||||
# error: [invalid-argument-type] "Argument is incorrect: Expected `int | str`, found `object`"
|
||||
reveal_type(Constrained[object]) # revealed: Unknown
|
||||
```
|
||||
|
||||
If the type variable has a default, it can be omitted:
|
||||
|
||||
```py
|
||||
type WithDefault[T, U = int] = ...
|
||||
|
||||
reveal_type(WithDefault[str, str]) # revealed: WithDefault[str, str]
|
||||
reveal_type(WithDefault[str]) # revealed: WithDefault[str, int]
|
||||
```
|
||||
|
||||
If the type alias is not specialized explicitly, it is implicitly specialized to `Unknown`:
|
||||
|
||||
```py
|
||||
type G[T] = list[T]
|
||||
|
||||
def _(g: G):
|
||||
reveal_type(g) # revealed: list[Unknown]
|
||||
```
|
||||
|
||||
Unless a type default was provided:
|
||||
|
||||
```py
|
||||
type G[T = int] = list[T]
|
||||
|
||||
def _(g: G):
|
||||
reveal_type(g) # revealed: list[int]
|
||||
```
|
||||
|
||||
## Aliases are not callable
|
||||
|
||||
```py
|
||||
type A = int
|
||||
type B[T] = T
|
||||
|
||||
# error: [call-non-callable] "Object of type `TypeAliasType` is not callable"
|
||||
reveal_type(A()) # revealed: Unknown
|
||||
|
||||
# error: [call-non-callable] "Object of type `GenericAlias` is not callable"
|
||||
reveal_type(B[int]()) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Recursive Truthiness
|
||||
|
||||
Make sure we handle cycles correctly when computing the truthiness of a generic type alias:
|
||||
|
||||
```py
|
||||
type X[T: X] = T
|
||||
|
||||
def _(x: X):
|
||||
assert x
|
||||
```
|
|
@ -40,13 +40,6 @@ You cannot use the same typevar more than once.
|
|||
class RepeatedTypevar[T, T]: ...
|
||||
```
|
||||
|
||||
You can only use typevars (TODO: or param specs or typevar tuples) in the class's generic context.
|
||||
|
||||
```py
|
||||
# TODO: error
|
||||
class GenericOfType[int]: ...
|
||||
```
|
||||
|
||||
You can also define a generic class by inheriting from some _other_ generic class, and specializing
|
||||
it with typevars. With PEP 695 syntax, you must explicitly list all of the typevars that you use in
|
||||
your base classes.
|
||||
|
|
|
@ -188,7 +188,7 @@ T = TypeVar("T")
|
|||
IntAnd = TypeAliasType("IntAndT", tuple[int, T], type_params=(T,))
|
||||
|
||||
def f(x: IntAnd[str]) -> None:
|
||||
reveal_type(x) # revealed: @Todo(Generic PEP-695 type alias)
|
||||
reveal_type(x) # revealed: @Todo(Generic manual PEP-695 type alias)
|
||||
```
|
||||
|
||||
### Error cases
|
||||
|
|
|
@ -44,6 +44,7 @@ use crate::types::constraints::{
|
|||
use crate::types::context::{LintDiagnosticGuard, LintDiagnosticGuardBuilder};
|
||||
use crate::types::diagnostic::{INVALID_AWAIT, INVALID_TYPE_FORM, UNSUPPORTED_BOOL_CONVERSION};
|
||||
pub use crate::types::display::DisplaySettings;
|
||||
use crate::types::display::TupleSpecialization;
|
||||
use crate::types::enums::{enum_metadata, is_single_member_enum};
|
||||
use crate::types::function::{
|
||||
DataclassTransformerParams, FunctionSpans, FunctionType, KnownFunction,
|
||||
|
@ -196,6 +197,11 @@ pub(crate) struct IsEquivalent;
|
|||
pub(crate) type FindLegacyTypeVarsVisitor<'db> = CycleDetector<FindLegacyTypeVars, Type<'db>, ()>;
|
||||
pub(crate) struct FindLegacyTypeVars;
|
||||
|
||||
/// A [`CycleDetector`] that is used in `try_bool` methods.
|
||||
pub(crate) type TryBoolVisitor<'db> =
|
||||
CycleDetector<TryBool, Type<'db>, Result<Truthiness, BoolError<'db>>>;
|
||||
pub(crate) struct TryBool;
|
||||
|
||||
/// A [`TypeTransformer`] that is used in `normalized` methods.
|
||||
pub(crate) type NormalizedVisitor<'db> = TypeTransformer<'db, Normalized>;
|
||||
pub(crate) struct Normalized;
|
||||
|
@ -844,6 +850,13 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn into_type_alias(self) -> Option<TypeAliasType<'db>> {
|
||||
match self {
|
||||
Type::KnownInstance(KnownInstanceType::TypeAliasType(type_alias)) => Some(type_alias),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn into_dynamic(self) -> Option<DynamicType> {
|
||||
match self {
|
||||
Type::Dynamic(dynamic_type) => Some(dynamic_type),
|
||||
|
@ -3607,7 +3620,7 @@ impl<'db> Type<'db> {
|
|||
/// is truthy or falsy in a context where Python doesn't make an implicit `bool` call.
|
||||
/// Use [`try_bool`](Self::try_bool) for type checking or implicit `bool` calls.
|
||||
pub(crate) fn bool(&self, db: &'db dyn Db) -> Truthiness {
|
||||
self.try_bool_impl(db, true)
|
||||
self.try_bool_impl(db, true, &TryBoolVisitor::new(Ok(Truthiness::Ambiguous)))
|
||||
.unwrap_or_else(|err| err.fallback_truthiness())
|
||||
}
|
||||
|
||||
|
@ -3618,7 +3631,7 @@ impl<'db> Type<'db> {
|
|||
///
|
||||
/// Returns an error if the type doesn't implement `__bool__` correctly.
|
||||
pub(crate) fn try_bool(&self, db: &'db dyn Db) -> Result<Truthiness, BoolError<'db>> {
|
||||
self.try_bool_impl(db, false)
|
||||
self.try_bool_impl(db, false, &TryBoolVisitor::new(Ok(Truthiness::Ambiguous)))
|
||||
}
|
||||
|
||||
/// Resolves the boolean value of a type.
|
||||
|
@ -3637,6 +3650,7 @@ impl<'db> Type<'db> {
|
|||
&self,
|
||||
db: &'db dyn Db,
|
||||
allow_short_circuit: bool,
|
||||
visitor: &TryBoolVisitor<'db>,
|
||||
) -> Result<Truthiness, BoolError<'db>> {
|
||||
let type_to_truthiness = |ty| {
|
||||
if let Type::BooleanLiteral(bool_val) = ty {
|
||||
|
@ -3706,14 +3720,15 @@ impl<'db> Type<'db> {
|
|||
let mut has_errors = false;
|
||||
|
||||
for element in union.elements(db) {
|
||||
let element_truthiness = match element.try_bool_impl(db, allow_short_circuit) {
|
||||
Ok(truthiness) => truthiness,
|
||||
Err(err) => {
|
||||
has_errors = true;
|
||||
all_not_callable &= matches!(err, BoolError::NotCallable { .. });
|
||||
err.fallback_truthiness()
|
||||
}
|
||||
};
|
||||
let element_truthiness =
|
||||
match element.try_bool_impl(db, allow_short_circuit, visitor) {
|
||||
Ok(truthiness) => truthiness,
|
||||
Err(err) => {
|
||||
has_errors = true;
|
||||
all_not_callable &= matches!(err, BoolError::NotCallable { .. });
|
||||
err.fallback_truthiness()
|
||||
}
|
||||
};
|
||||
|
||||
truthiness.get_or_insert(element_truthiness);
|
||||
|
||||
|
@ -3768,17 +3783,19 @@ impl<'db> Type<'db> {
|
|||
|
||||
Type::AlwaysFalsy => Truthiness::AlwaysFalse,
|
||||
|
||||
Type::ClassLiteral(class) => class
|
||||
.metaclass_instance_type(db)
|
||||
.try_bool_impl(db, allow_short_circuit)?,
|
||||
Type::ClassLiteral(class) => {
|
||||
class
|
||||
.metaclass_instance_type(db)
|
||||
.try_bool_impl(db, allow_short_circuit, visitor)?
|
||||
}
|
||||
Type::GenericAlias(alias) => ClassType::from(*alias)
|
||||
.metaclass_instance_type(db)
|
||||
.try_bool_impl(db, allow_short_circuit)?,
|
||||
.try_bool_impl(db, allow_short_circuit, visitor)?,
|
||||
|
||||
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
|
||||
SubclassOfInner::Dynamic(_) => Truthiness::Ambiguous,
|
||||
SubclassOfInner::Class(class) => {
|
||||
Type::from(class).try_bool_impl(db, allow_short_circuit)?
|
||||
Type::from(class).try_bool_impl(db, allow_short_circuit, visitor)?
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -3786,7 +3803,7 @@ impl<'db> Type<'db> {
|
|||
match bound_typevar.typevar(db).bound_or_constraints(db) {
|
||||
None => Truthiness::Ambiguous,
|
||||
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
|
||||
bound.try_bool_impl(db, allow_short_circuit)?
|
||||
bound.try_bool_impl(db, allow_short_circuit, visitor)?
|
||||
}
|
||||
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => {
|
||||
try_union(constraints)?
|
||||
|
@ -3821,9 +3838,11 @@ impl<'db> Type<'db> {
|
|||
Type::BooleanLiteral(bool) => Truthiness::from(*bool),
|
||||
Type::StringLiteral(str) => Truthiness::from(!str.value(db).is_empty()),
|
||||
Type::BytesLiteral(bytes) => Truthiness::from(!bytes.value(db).is_empty()),
|
||||
Type::TypeAlias(alias) => alias
|
||||
.value_type(db)
|
||||
.try_bool_impl(db, allow_short_circuit)?,
|
||||
Type::TypeAlias(alias) => visitor.visit(*self, || {
|
||||
alias
|
||||
.value_type(db)
|
||||
.try_bool_impl(db, allow_short_circuit, visitor)
|
||||
})?,
|
||||
};
|
||||
|
||||
Ok(truthiness)
|
||||
|
@ -6846,10 +6865,13 @@ impl<'db> KnownInstanceType<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
const fn class(self) -> KnownClass {
|
||||
fn class(self, db: &'db dyn Db) -> KnownClass {
|
||||
match self {
|
||||
Self::SubscriptedProtocol(_) | Self::SubscriptedGeneric(_) => KnownClass::SpecialForm,
|
||||
Self::TypeVar(_) => KnownClass::TypeVar,
|
||||
Self::TypeAliasType(TypeAliasType::PEP695(alias)) if alias.is_specialized(db) => {
|
||||
KnownClass::GenericAlias
|
||||
}
|
||||
Self::TypeAliasType(_) => KnownClass::TypeAliasType,
|
||||
Self::Deprecated(_) => KnownClass::Deprecated,
|
||||
Self::Field(_) => KnownClass::Field,
|
||||
|
@ -6857,7 +6879,7 @@ impl<'db> KnownInstanceType<'db> {
|
|||
}
|
||||
|
||||
fn to_meta_type(self, db: &'db dyn Db) -> Type<'db> {
|
||||
self.class().to_class_literal(db)
|
||||
self.class(db).to_class_literal(db)
|
||||
}
|
||||
|
||||
/// Return the instance type which this type is a subtype of.
|
||||
|
@ -6866,12 +6888,12 @@ impl<'db> KnownInstanceType<'db> {
|
|||
/// `typing.TypeAliasType`, so `KnownInstanceType::TypeAliasType(_).instance_fallback(db)`
|
||||
/// returns `Type::NominalInstance(NominalInstanceType { class: <typing.TypeAliasType> })`.
|
||||
fn instance_fallback(self, db: &dyn Db) -> Type<'_> {
|
||||
self.class().to_instance(db)
|
||||
self.class(db).to_instance(db)
|
||||
}
|
||||
|
||||
/// Return `true` if this symbol is an instance of `class`.
|
||||
fn is_instance_of(self, db: &dyn Db, class: ClassType) -> bool {
|
||||
self.class().is_subclass_of(db, class)
|
||||
self.class(db).is_subclass_of(db, class)
|
||||
}
|
||||
|
||||
/// Return the repr of the symbol at runtime
|
||||
|
@ -6892,7 +6914,16 @@ impl<'db> KnownInstanceType<'db> {
|
|||
f.write_str("typing.Generic")?;
|
||||
generic_context.display(self.db).fmt(f)
|
||||
}
|
||||
KnownInstanceType::TypeAliasType(_) => f.write_str("typing.TypeAliasType"),
|
||||
KnownInstanceType::TypeAliasType(alias) => {
|
||||
if let Some(specialization) = alias.specialization(self.db) {
|
||||
f.write_str(alias.name(self.db))?;
|
||||
specialization
|
||||
.display_short(self.db, TupleSpecialization::No)
|
||||
.fmt(f)
|
||||
} else {
|
||||
f.write_str("typing.TypeAliasType")
|
||||
}
|
||||
}
|
||||
// This is a legacy `TypeVar` _outside_ of any generic class or function, so we render
|
||||
// it as an instance of `typing.TypeVar`. Inside of a generic class or function, we'll
|
||||
// have a `Type::TypeVar(_)`, which is rendered as the typevar's name.
|
||||
|
@ -7507,7 +7538,7 @@ impl<'db> TypeVarInstance<'db> {
|
|||
))
|
||||
}
|
||||
|
||||
#[salsa::tracked]
|
||||
#[salsa::tracked(cycle_fn=lazy_bound_cycle_recover, cycle_initial=lazy_bound_cycle_initial)]
|
||||
fn lazy_bound(self, db: &'db dyn Db) -> Option<TypeVarBoundOrConstraints<'db>> {
|
||||
let definition = self.definition(db)?;
|
||||
let module = parsed_module(db, definition.file(db)).load(db);
|
||||
|
@ -7539,6 +7570,23 @@ impl<'db> TypeVarInstance<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::ref_option)]
|
||||
fn lazy_bound_cycle_recover<'db>(
|
||||
_db: &'db dyn Db,
|
||||
_value: &Option<TypeVarBoundOrConstraints<'db>>,
|
||||
_count: u32,
|
||||
_self: TypeVarInstance<'db>,
|
||||
) -> salsa::CycleRecoveryAction<Option<TypeVarBoundOrConstraints<'db>>> {
|
||||
salsa::CycleRecoveryAction::Iterate
|
||||
}
|
||||
|
||||
fn lazy_bound_cycle_initial<'db>(
|
||||
_db: &'db dyn Db,
|
||||
_self: TypeVarInstance<'db>,
|
||||
) -> Option<TypeVarBoundOrConstraints<'db>> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Where a type variable is bound and usable.
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, salsa::Update, get_size2::GetSize)]
|
||||
pub enum BindingContext<'db> {
|
||||
|
@ -9365,6 +9413,8 @@ pub struct PEP695TypeAliasType<'db> {
|
|||
pub name: ast::name::Name,
|
||||
|
||||
rhs_scope: ScopeId<'db>,
|
||||
|
||||
specialization: Option<Specialization<'db>>,
|
||||
}
|
||||
|
||||
// The Salsa heap is tracked separately.
|
||||
|
@ -9392,7 +9442,65 @@ impl<'db> PEP695TypeAliasType<'db> {
|
|||
let module = parsed_module(db, scope.file(db)).load(db);
|
||||
let type_alias_stmt_node = scope.node(db).expect_type_alias();
|
||||
let definition = self.definition(db);
|
||||
definition_expression_type(db, definition, &type_alias_stmt_node.node(&module).value)
|
||||
let value_type =
|
||||
definition_expression_type(db, definition, &type_alias_stmt_node.node(&module).value);
|
||||
|
||||
if let Some(generic_context) = self.generic_context(db) {
|
||||
let specialization = self
|
||||
.specialization(db)
|
||||
.unwrap_or_else(|| generic_context.default_specialization(db, None));
|
||||
|
||||
value_type.apply_specialization(db, specialization)
|
||||
} else {
|
||||
value_type
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn apply_specialization(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
f: impl FnOnce(GenericContext<'db>) -> Specialization<'db>,
|
||||
) -> PEP695TypeAliasType<'db> {
|
||||
match self.generic_context(db) {
|
||||
None => self,
|
||||
|
||||
Some(generic_context) => {
|
||||
// Note that at runtime, a specialized type alias is an instance of `typing.GenericAlias`.
|
||||
// However, the `GenericAlias` type in ty is heavily special cased to refer to specialized
|
||||
// class literals, so we instead represent specialized type aliases as instances of
|
||||
// `typing.TypeAliasType` internally, and pass the specialization through to the value type,
|
||||
// except when resolving to an instance of the type alias, or its display representation.
|
||||
let specialization = f(generic_context);
|
||||
PEP695TypeAliasType::new(
|
||||
db,
|
||||
self.name(db),
|
||||
self.rhs_scope(db),
|
||||
Some(specialization),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_specialized(self, db: &'db dyn Db) -> bool {
|
||||
self.specialization(db).is_some()
|
||||
}
|
||||
|
||||
#[salsa::tracked(cycle_fn=generic_context_cycle_recover, cycle_initial=generic_context_cycle_initial, heap_size=ruff_memory_usage::heap_size)]
|
||||
pub(crate) fn generic_context(self, db: &'db dyn Db) -> Option<GenericContext<'db>> {
|
||||
let scope = self.rhs_scope(db);
|
||||
let file = scope.file(db);
|
||||
let parsed = parsed_module(db, file).load(db);
|
||||
let type_alias_stmt_node = scope.node(db).expect_type_alias();
|
||||
|
||||
type_alias_stmt_node
|
||||
.node(&parsed)
|
||||
.type_params
|
||||
.as_ref()
|
||||
.map(|type_params| {
|
||||
let index = semantic_index(db, scope.file(db));
|
||||
let definition = index.expect_single_definition(type_alias_stmt_node);
|
||||
GenericContext::from_type_params(db, index, definition, type_params)
|
||||
})
|
||||
}
|
||||
|
||||
fn normalized_impl(self, _db: &'db dyn Db, _visitor: &NormalizedVisitor<'db>) -> Self {
|
||||
|
@ -9400,6 +9508,23 @@ impl<'db> PEP695TypeAliasType<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::ref_option, clippy::trivially_copy_pass_by_ref)]
|
||||
fn generic_context_cycle_recover<'db>(
|
||||
_db: &'db dyn Db,
|
||||
_value: &Option<GenericContext<'db>>,
|
||||
_count: u32,
|
||||
_self: PEP695TypeAliasType<'db>,
|
||||
) -> salsa::CycleRecoveryAction<Option<GenericContext<'db>>> {
|
||||
salsa::CycleRecoveryAction::Iterate
|
||||
}
|
||||
|
||||
fn generic_context_cycle_initial<'db>(
|
||||
_db: &'db dyn Db,
|
||||
_self: PEP695TypeAliasType<'db>,
|
||||
) -> Option<GenericContext<'db>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn value_type_cycle_recover<'db>(
|
||||
_db: &'db dyn Db,
|
||||
_value: &Type<'db>,
|
||||
|
@ -9506,6 +9631,41 @@ impl<'db> TypeAliasType<'db> {
|
|||
TypeAliasType::ManualPEP695(type_alias) => type_alias.value(db),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn into_pep_695_type_alias(self) -> Option<PEP695TypeAliasType<'db>> {
|
||||
match self {
|
||||
TypeAliasType::PEP695(type_alias) => Some(type_alias),
|
||||
TypeAliasType::ManualPEP695(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn generic_context(self, db: &'db dyn Db) -> Option<GenericContext<'db>> {
|
||||
// TODO: Add support for generic non-PEP695 type aliases.
|
||||
match self {
|
||||
TypeAliasType::PEP695(type_alias) => type_alias.generic_context(db),
|
||||
TypeAliasType::ManualPEP695(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn specialization(self, db: &'db dyn Db) -> Option<Specialization<'db>> {
|
||||
match self {
|
||||
TypeAliasType::PEP695(type_alias) => type_alias.specialization(db),
|
||||
TypeAliasType::ManualPEP695(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn apply_specialization(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
f: impl FnOnce(GenericContext<'db>) -> Specialization<'db>,
|
||||
) -> Self {
|
||||
match self {
|
||||
TypeAliasType::PEP695(type_alias) => {
|
||||
TypeAliasType::PEP695(type_alias.apply_specialization(db, f))
|
||||
}
|
||||
TypeAliasType::ManualPEP695(_) => self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Either the explicit `metaclass=` keyword of the class, or the inferred metaclass of one of its base classes.
|
||||
|
|
|
@ -32,8 +32,8 @@ use crate::types::signatures::{Parameter, ParameterForm, Parameters};
|
|||
use crate::types::tuple::{Tuple, TupleLength, TupleType};
|
||||
use crate::types::{
|
||||
BoundMethodType, ClassLiteral, DataclassParams, FieldInstance, KnownClass, KnownInstanceType,
|
||||
MethodWrapperKind, PropertyInstanceType, SpecialFormType, TypeMapping, UnionType,
|
||||
WrapperDescriptorKind, enums, ide_support, todo_type,
|
||||
MethodWrapperKind, PropertyInstanceType, SpecialFormType, TypeAliasType, TypeMapping,
|
||||
UnionType, WrapperDescriptorKind, enums, ide_support, todo_type,
|
||||
};
|
||||
use ruff_db::diagnostic::{Annotation, Diagnostic, SubDiagnostic, SubDiagnosticSeverity};
|
||||
use ruff_python_ast::{self as ast, PythonVersion};
|
||||
|
@ -662,6 +662,13 @@ impl<'db> Bindings<'db> {
|
|||
function_generic_context(bound_method.function(db))
|
||||
}
|
||||
|
||||
Type::KnownInstance(KnownInstanceType::TypeAliasType(
|
||||
TypeAliasType::PEP695(alias),
|
||||
)) => alias
|
||||
.generic_context(db)
|
||||
.map(|generic_context| generic_context.as_tuple(db))
|
||||
.unwrap_or_else(|| Type::none(db)),
|
||||
|
||||
_ => Type::none(db),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -48,6 +48,13 @@ fn enclosing_generic_contexts<'db>(
|
|||
.last_definition_signature(db)
|
||||
.generic_context
|
||||
}
|
||||
NodeWithScopeKind::TypeAlias(type_alias) => {
|
||||
let definition = index.expect_single_definition(type_alias.node(module));
|
||||
binding_type(db, definition)
|
||||
.into_type_alias()?
|
||||
.into_pep_695_type_alias()?
|
||||
.generic_context(db)
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -99,16 +99,16 @@ use crate::types::diagnostic::{
|
|||
INVALID_ARGUMENT_TYPE, INVALID_ASSIGNMENT, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE,
|
||||
INVALID_DECLARATION, INVALID_GENERIC_CLASS, INVALID_KEY, INVALID_NAMED_TUPLE,
|
||||
INVALID_PARAMETER_DEFAULT, INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL,
|
||||
INVALID_TYPE_VARIABLE_CONSTRAINTS, IncompatibleBases, POSSIBLY_UNBOUND_IMPLICIT_CALL,
|
||||
POSSIBLY_UNBOUND_IMPORT, TypeCheckDiagnostics, UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE,
|
||||
UNRESOLVED_GLOBAL, UNRESOLVED_IMPORT, UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR,
|
||||
report_bad_dunder_set_call, report_cannot_pop_required_field_on_typed_dict,
|
||||
report_implicit_return_type, report_instance_layout_conflict,
|
||||
report_invalid_argument_number_to_special_form, report_invalid_arguments_to_annotated,
|
||||
report_invalid_arguments_to_callable, report_invalid_assignment,
|
||||
report_invalid_attribute_assignment, report_invalid_generator_function_return_type,
|
||||
report_invalid_key_on_typed_dict, report_invalid_return_type,
|
||||
report_namedtuple_field_without_default_after_field_with_default,
|
||||
INVALID_TYPE_VARIABLE_CONSTRAINTS, IncompatibleBases, NON_SUBSCRIPTABLE,
|
||||
POSSIBLY_UNBOUND_IMPLICIT_CALL, POSSIBLY_UNBOUND_IMPORT, TypeCheckDiagnostics,
|
||||
UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_GLOBAL, UNRESOLVED_IMPORT,
|
||||
UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR, report_bad_dunder_set_call,
|
||||
report_cannot_pop_required_field_on_typed_dict, report_implicit_return_type,
|
||||
report_instance_layout_conflict, report_invalid_argument_number_to_special_form,
|
||||
report_invalid_arguments_to_annotated, report_invalid_arguments_to_callable,
|
||||
report_invalid_assignment, report_invalid_attribute_assignment,
|
||||
report_invalid_generator_function_return_type, report_invalid_key_on_typed_dict,
|
||||
report_invalid_return_type, report_namedtuple_field_without_default_after_field_with_default,
|
||||
report_possibly_unbound_attribute,
|
||||
};
|
||||
use crate::types::enums::is_enum_class;
|
||||
|
@ -3212,6 +3212,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
self.db(),
|
||||
&type_alias.name.as_name_expr().unwrap().id,
|
||||
rhs_scope,
|
||||
None,
|
||||
)),
|
||||
));
|
||||
|
||||
|
@ -8818,6 +8819,16 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
if let Type::SpecialForm(SpecialFormType::Tuple) = value_ty {
|
||||
return tuple_generic_alias(self.db(), self.infer_tuple_type_expression(slice));
|
||||
}
|
||||
if let Type::KnownInstance(KnownInstanceType::TypeAliasType(type_alias)) = value_ty {
|
||||
if let Some(generic_context) = type_alias.generic_context(self.db()) {
|
||||
return self.infer_explicit_type_alias_specialization(
|
||||
subscript,
|
||||
value_ty,
|
||||
type_alias,
|
||||
generic_context,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let slice_ty = self.infer_expression(slice);
|
||||
let result_ty = self.infer_subscript_expression_types(subscript, value_ty, slice_ty, *ctx);
|
||||
|
@ -8830,6 +8841,52 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
value_ty: Type<'db>,
|
||||
generic_class: ClassLiteral<'db>,
|
||||
generic_context: GenericContext<'db>,
|
||||
) -> Type<'db> {
|
||||
let db = self.db();
|
||||
let specialize = |types: &[Option<Type<'db>>]| {
|
||||
Type::from(generic_class.apply_specialization(db, |_| {
|
||||
generic_context.specialize_partial(db, types.iter().copied())
|
||||
}))
|
||||
};
|
||||
|
||||
self.infer_explicit_callable_specialization(
|
||||
subscript,
|
||||
value_ty,
|
||||
generic_context,
|
||||
specialize,
|
||||
)
|
||||
}
|
||||
|
||||
fn infer_explicit_type_alias_specialization(
|
||||
&mut self,
|
||||
subscript: &ast::ExprSubscript,
|
||||
value_ty: Type<'db>,
|
||||
generic_type_alias: TypeAliasType<'db>,
|
||||
generic_context: GenericContext<'db>,
|
||||
) -> Type<'db> {
|
||||
let db = self.db();
|
||||
let specialize = |types: &[Option<Type<'db>>]| {
|
||||
let type_alias = generic_type_alias.apply_specialization(db, |_| {
|
||||
generic_context.specialize_partial(db, types.iter().copied())
|
||||
});
|
||||
|
||||
Type::KnownInstance(KnownInstanceType::TypeAliasType(type_alias))
|
||||
};
|
||||
|
||||
self.infer_explicit_callable_specialization(
|
||||
subscript,
|
||||
value_ty,
|
||||
generic_context,
|
||||
specialize,
|
||||
)
|
||||
}
|
||||
|
||||
fn infer_explicit_callable_specialization(
|
||||
&mut self,
|
||||
subscript: &ast::ExprSubscript,
|
||||
value_ty: Type<'db>,
|
||||
generic_context: GenericContext<'db>,
|
||||
specialize: impl FnOnce(&[Option<Type<'db>>]) -> Type<'db>,
|
||||
) -> Type<'db> {
|
||||
let slice_node = subscript.slice.as_ref();
|
||||
let call_argument_types = match slice_node {
|
||||
|
@ -8864,10 +8921,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
.matching_overloads()
|
||||
.next()
|
||||
.expect("valid bindings should have matching overload");
|
||||
Type::from(generic_class.apply_specialization(self.db(), |_| {
|
||||
generic_context
|
||||
.specialize_partial(self.db(), overload.parameter_types().iter().copied())
|
||||
}))
|
||||
|
||||
specialize(overload.parameter_types())
|
||||
}
|
||||
|
||||
fn infer_subscript_expression_types(
|
||||
|
@ -9044,6 +9099,18 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
Some(todo_type!("doubly-specialized typing.Protocol"))
|
||||
}
|
||||
|
||||
(
|
||||
Type::KnownInstance(KnownInstanceType::TypeAliasType(TypeAliasType::PEP695(alias))),
|
||||
_,
|
||||
) if alias.generic_context(db).is_none() => {
|
||||
if let Some(builder) = self.context.report_lint(&NON_SUBSCRIPTABLE, subscript) {
|
||||
builder
|
||||
.into_diagnostic(format_args!("Cannot subscript non-generic type alias"));
|
||||
}
|
||||
|
||||
Some(Type::unknown())
|
||||
}
|
||||
|
||||
(Type::SpecialForm(SpecialFormType::Generic), typevars) => Some(
|
||||
self.legacy_generic_class_context(value_node, typevars, LegacyGenericBase::Generic)
|
||||
.map(|context| {
|
||||
|
@ -9066,7 +9133,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
}
|
||||
|
||||
(Type::KnownInstance(known_instance), _)
|
||||
if known_instance.class().is_special_form() =>
|
||||
if known_instance.class(db).is_special_form() =>
|
||||
{
|
||||
Some(todo_type!("Inference of subscript on special form"))
|
||||
}
|
||||
|
@ -10534,9 +10601,43 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
|||
self.infer_type_expression(&subscript.slice);
|
||||
todo_type!("TypeVar annotations")
|
||||
}
|
||||
KnownInstanceType::TypeAliasType(_) => {
|
||||
self.infer_type_expression(&subscript.slice);
|
||||
todo_type!("Generic PEP-695 type alias")
|
||||
KnownInstanceType::TypeAliasType(type_alias @ TypeAliasType::PEP695(_)) => {
|
||||
match type_alias.generic_context(self.db()) {
|
||||
Some(generic_context) => {
|
||||
let specialized_type_alias = self
|
||||
.infer_explicit_type_alias_specialization(
|
||||
subscript,
|
||||
value_ty,
|
||||
type_alias,
|
||||
generic_context,
|
||||
);
|
||||
|
||||
specialized_type_alias
|
||||
.in_type_expression(
|
||||
self.db(),
|
||||
self.scope(),
|
||||
self.typevar_binding_context,
|
||||
)
|
||||
.unwrap_or(Type::unknown())
|
||||
}
|
||||
None => {
|
||||
self.infer_type_expression(slice);
|
||||
|
||||
if let Some(builder) =
|
||||
self.context.report_lint(&NON_SUBSCRIPTABLE, subscript)
|
||||
{
|
||||
builder.into_diagnostic(format_args!(
|
||||
"Cannot subscript non-generic type alias"
|
||||
));
|
||||
}
|
||||
|
||||
Type::unknown()
|
||||
}
|
||||
}
|
||||
}
|
||||
KnownInstanceType::TypeAliasType(TypeAliasType::ManualPEP695(_)) => {
|
||||
self.infer_type_expression(slice);
|
||||
todo_type!("Generic manual PEP-695 type alias")
|
||||
}
|
||||
},
|
||||
Type::Dynamic(DynamicType::Todo(_)) => {
|
||||
|
@ -10552,6 +10653,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
|||
class,
|
||||
generic_context,
|
||||
);
|
||||
|
||||
specialized_class
|
||||
.in_type_expression(
|
||||
self.db(),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue