Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from typing import TypeAlias, TypeVar

T = TypeVar("T", bound="A[0]")
A: TypeAlias = T
def _(x: A):
if x:
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
def _[T: T[0]](x: T):
if x:
pass
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This usage seems to be used in bokeh, so we should allow it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For reference, pyright and pyrefly support both ImplicitPositive and Positive forms. Mypy supports only the explicit PEP 613 version.

With our current implementation of implicit type aliases, it would be quite difficult for us to distinguish ImplicitPositive from direct use of T. This may change in the future, but for now it seems fine to match mypy's support.


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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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],
):
Expand All @@ -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
```

Expand Down
4 changes: 3 additions & 1 deletion crates/ty_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8011,7 +8011,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)
Expand Down Expand Up @@ -9488,6 +9488,8 @@ pub enum TypeVarKind {
ParamSpec,
/// `def foo[**P]() -> None: ...`
Pep695ParamSpec,
/// `Alias: typing.TypeAlias = T`
Pep613Alias,
}

impl TypeVarKind {
Expand Down
18 changes: 18 additions & 0 deletions crates/ty_python_semantic/src/types/infer/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5865,6 +5865,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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(_)
Expand Down
Loading