mirror of https://github.com/astral-sh/ruff
[ty] disallow explicit specialization of type variables themselves (#21938)
## Summary This PR makes explicit specialization of a type variable itself an error, and the result of the specialization is `Unknown`. The change also fixes https://github.com/astral-sh/ty/issues/1794. ## Test Plan mdtests updated new corpus test --------- Co-authored-by: Carl Meyer <carl@astral.sh>
This commit is contained in:
parent
5a2aba237b
commit
e19c050386
|
|
@ -0,0 +1,7 @@
|
|||
from typing import TypeAlias, TypeVar
|
||||
|
||||
T = TypeVar("T", bound="A[0]")
|
||||
A: TypeAlias = T
|
||||
def _(x: A):
|
||||
if x:
|
||||
pass
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
def _[T: T[0]](x: T):
|
||||
if x:
|
||||
pass
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(_) => {
|
||||
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(_)
|
||||
|
|
|
|||
Loading…
Reference in New Issue