[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:
Ibraheem Ahmed 2025-09-08 16:26:21 -04:00 committed by GitHub
parent d55edb3d74
commit aa5d665d52
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 510 additions and 70 deletions

View file

@ -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

View file

@ -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
```

View file

@ -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.

View file

@ -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

View file

@ -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.

View file

@ -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),
});
}

View file

@ -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,
})
}

View file

@ -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(),