diff --git a/crates/ty_python_semantic/resources/corpus/cyclic_pep613_typevar.py b/crates/ty_python_semantic/resources/corpus/cyclic_pep613_typevar.py new file mode 100644 index 0000000000..5730c9f30b --- /dev/null +++ b/crates/ty_python_semantic/resources/corpus/cyclic_pep613_typevar.py @@ -0,0 +1,7 @@ +from typing import TypeAlias, TypeVar + +T = TypeVar("T", bound="A[0]") +A: TypeAlias = T +def _(x: A): + if x: + pass diff --git a/crates/ty_python_semantic/resources/corpus/cyclic_pep695_typevars_invalid_bound2.py b/crates/ty_python_semantic/resources/corpus/cyclic_pep695_typevars_invalid_bound2.py new file mode 100644 index 0000000000..5b3ab4e12a --- /dev/null +++ b/crates/ty_python_semantic/resources/corpus/cyclic_pep695_typevars_invalid_bound2.py @@ -0,0 +1,3 @@ +def _[T: T[0]](x: T): + if x: + pass diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/variables.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/variables.md index 5917e340ab..13547e81be 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/variables.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/variables.md @@ -104,6 +104,34 @@ S = TypeVar("S", **{"bound": int}) reveal_type(S) # revealed: TypeVar ``` +### No explicit specialization + +A type variable itself cannot be explicitly specialized; the result of the specialization is +`Unknown`. However, generic PEP 613 type aliases that point to type variables can be explicitly +specialized. + +```py +from typing import TypeVar, TypeAlias + +T = TypeVar("T") +ImplicitPositive = T +Positive: TypeAlias = T + +def _( + # error: [invalid-type-form] "A type variable itself cannot be specialized" + a: T[int], + # error: [invalid-type-form] "A type variable itself cannot be specialized" + b: T[T], + # error: [invalid-type-form] "A type variable itself cannot be specialized" + c: ImplicitPositive[int], + d: Positive[int], +): + reveal_type(a) # revealed: Unknown + reveal_type(b) # revealed: Unknown + reveal_type(c) # revealed: Unknown + reveal_type(d) # revealed: int +``` + ### Type variables with a default Note that the `__default__` property is only available in Python ≥3.13. diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/variables.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/variables.md index eaa4b8923e..8338227538 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/variables.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/variables.md @@ -98,6 +98,26 @@ def f[T: (int,)](): pass ``` +### No explicit specialization + +A type variable itself cannot be explicitly specialized; the result of the specialization is +`Unknown`. However, generic type aliases that point to type variables can be explicitly specialized. + +```py +type Positive[T] = T + +def _[T]( + # error: [invalid-type-form] "A type variable itself cannot be specialized" + a: T[int], + # error: [invalid-type-form] "A type variable itself cannot be specialized" + b: T[T], + c: Positive[int], +): + reveal_type(a) # revealed: Unknown + reveal_type(b) # revealed: Unknown + reveal_type(c) # revealed: int +``` + ## Invalid uses Note that many of the invalid uses of legacy typevars do not apply to PEP 695 typevars, since the diff --git a/crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md b/crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md index ca0c4f6d8b..977991611c 100644 --- a/crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md +++ b/crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md @@ -414,6 +414,7 @@ def _( list_or_tuple_legacy: ListOrTupleLegacy[int], my_callable: MyCallable[[str, bytes], int], annotated_int: AnnotatedType[int], + # error: [invalid-type-form] "A type variable itself cannot be specialized" transparent_alias: TransparentAlias[int], optional_int: MyOptional[int], ): @@ -427,7 +428,7 @@ def _( reveal_type(list_or_tuple_legacy) # revealed: list[int] | tuple[int, ...] reveal_type(my_callable) # revealed: (str, bytes, /) -> int reveal_type(annotated_int) # revealed: int - reveal_type(transparent_alias) # revealed: int + reveal_type(transparent_alias) # revealed: Unknown reveal_type(optional_int) # revealed: int | None ``` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 81916db6da..e9311547d7 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -7994,7 +7994,7 @@ impl<'db> Type<'db> { ) { let matching_typevar = |bound_typevar: &BoundTypeVarInstance<'db>| { match bound_typevar.typevar(db).kind(db) { - TypeVarKind::Legacy | TypeVarKind::TypingSelf + TypeVarKind::Legacy | TypeVarKind::Pep613Alias | TypeVarKind::TypingSelf if binding_context.is_none_or(|binding_context| { bound_typevar.binding_context(db) == BindingContext::Definition(binding_context) @@ -9472,6 +9472,8 @@ pub enum TypeVarKind { ParamSpec, /// `def foo[**P]() -> None: ...` Pep695ParamSpec, + /// `Alias: typing.TypeAlias = T` + Pep613Alias, } impl TypeVarKind { diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index 510ec1f467..1b78a02c32 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -5866,6 +5866,24 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { }; if is_pep_613_type_alias { + let inferred_ty = + if let Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) = inferred_ty { + let identity = TypeVarIdentity::new( + self.db(), + typevar.identity(self.db()).name(self.db()), + typevar.identity(self.db()).definition(self.db()), + TypeVarKind::Pep613Alias, + ); + Type::KnownInstance(KnownInstanceType::TypeVar(TypeVarInstance::new( + self.db(), + identity, + typevar._bound_or_constraints(self.db()), + typevar.explicit_variance(self.db()), + typevar._default(self.db()), + ))) + } else { + inferred_ty + }; self.add_declaration_with_binding( target.into(), definition, diff --git a/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs b/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs index c8df4df756..d4c5701f1d 100644 --- a/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs +++ b/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs @@ -16,8 +16,8 @@ use crate::types::tuple::{TupleSpecBuilder, TupleType}; use crate::types::{ BindingContext, CallableType, DynamicType, GenericContext, IntersectionBuilder, KnownClass, KnownInstanceType, LintDiagnosticGuard, Parameter, Parameters, SpecialFormType, SubclassOfType, - Type, TypeAliasType, TypeContext, TypeIsType, TypeMapping, UnionBuilder, UnionType, - any_over_type, todo_type, + Type, TypeAliasType, TypeContext, TypeIsType, TypeMapping, TypeVarKind, UnionBuilder, + UnionType, any_over_type, todo_type, }; /// Type expressions @@ -995,8 +995,26 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } Type::unknown() } - KnownInstanceType::TypeVar(_) => { - self.infer_explicit_type_alias_specialization(subscript, value_ty, false) + KnownInstanceType::TypeVar(typevar) => { + // The type variable designated as a generic type alias by `typing.TypeAlias` can be explicitly specialized. + // ```py + // from typing import TypeVar, TypeAlias + // T = TypeVar('T') + // Annotated: TypeAlias = T + // _: Annotated[int] = 1 # valid + // ``` + if typevar.identity(self.db()).kind(self.db()) == TypeVarKind::Pep613Alias { + self.infer_explicit_type_alias_specialization(subscript, value_ty, false) + } else { + if let Some(builder) = + self.context.report_lint(&INVALID_TYPE_FORM, subscript) + { + builder.into_diagnostic(format_args!( + "A type variable itself cannot be specialized", + )); + } + Type::unknown() + } } KnownInstanceType::UnionType(_)