diff --git a/crates/ruff_linter/resources/mdtest/pyupgrade/pep-695.md b/crates/ruff_linter/resources/mdtest/pyupgrade/pep-695.md index 6eece28c0e..f749599173 100644 --- a/crates/ruff_linter/resources/mdtest/pyupgrade/pep-695.md +++ b/crates/ruff_linter/resources/mdtest/pyupgrade/pep-695.md @@ -165,3 +165,54 @@ help: Use type parameters | note: This is an unsafe fix and may change runtime behavior ``` + +## Bounded `TypeVarTuple` + +Python 3.15 adds a `bound` argument to `TypeVarTuple`, but PEP 695 has no syntax to express it. These +rules emit no diagnostic for type aliases, classes, or functions that use a bounded `TypeVarTuple`. + +```toml +target-version = "py315" + +[lint] +select = ["UP040", "UP046", "UP047"] +``` + +```py +from typing import Generic, TypeAlias, TypeAliasType, TypeVarTuple + +Ts = TypeVarTuple("Ts", bound=int) + +BoundedAlias: TypeAlias = tuple[*Ts] # no diagnostic +BoundedAliasType = TypeAliasType("BoundedAliasType", tuple[*Ts], type_params=(Ts,)) # no diagnostic + +class Bounded(Generic[*Ts]): ... # no diagnostic + +def bounded(ts: tuple[*Ts]): ... # no diagnostic +``` + +## Bounded `ParamSpec` + +`ParamSpec` accepts a `bound` argument starting in Python 3.10, but PEP 695 has no syntax to express it. +These rules emit no diagnostic for type aliases, classes, or functions that use a bounded `ParamSpec`. + +```toml +target-version = "py312" + +[lint] +select = ["UP040", "UP046", "UP047"] +``` + +```py +from collections.abc import Callable +from typing import Generic, ParamSpec, TypeAlias, TypeAliasType + +P = ParamSpec("P", bound=int) + +BoundedAlias: TypeAlias = Callable[P, int] # no diagnostic +BoundedAliasType = TypeAliasType("BoundedAliasType", Callable[P, int], type_params=(P,)) # no diagnostic + +class Bounded(Generic[P]): ... # no diagnostic + +def bounded(callback: Callable[P, int]): ... # no diagnostic +``` diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/mod.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/mod.rs index 109ccd762d..2155add529 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/mod.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/mod.rs @@ -87,6 +87,14 @@ pub(crate) struct DisplayTypeVar<'a> { } impl TypeVar<'_> { + /// PEP 695 has no syntax for bounds or constraints on `TypeVarTuple` or `ParamSpec`. + fn has_unsupported_restriction(&self) -> bool { + matches!( + self.kind, + TypeParamKind::TypeVarTuple | TypeParamKind::ParamSpec + ) && self.restriction.is_some() + } + fn display<'a>(&'a self, source: &'a str) -> DisplayTypeVar<'a> { DisplayTypeVar { type_var: self, @@ -390,6 +398,10 @@ fn check_type_vars<'a>(vars: Vec>, checker: &Checker) -> Option