Skip to content

Commit 7737ca4

Browse files
committed
Skip PEP 695 fixes for TypeVars with starred constraints (UP040, UP046, UP047, RUF053)
1 parent 0587598 commit 7737ca4

9 files changed

Lines changed: 60 additions & 2 deletions

File tree

‎crates/ruff_linter/resources/test/fixtures/pyupgrade/UP040.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,8 @@ class Foo:
132132
# Test case for TypeVar with default - should be converted when preview mode is enabled
133133
T_default = TypeVar("T_default", default=int)
134134
DefaultList: TypeAlias = list[T_default]
135+
136+
# TypeVar with starred constraints has no PEP 695 equivalent
137+
constraints = (int, str)
138+
T_starred = TypeVar("T_starred", *constraints)
139+
StarredList: TypeAlias = list[T_starred]

‎crates/ruff_linter/resources/test/fixtures/pyupgrade/UP046_0.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,12 @@ class DefaultOnlyTypeVar(Generic[W]): # -> [W = int]
142142
class Outer:
143143
class Inner(Generic[T]):
144144
var: T
145+
146+
147+
# TypeVar with starred constraints has no PEP 695 equivalent
148+
constraints = (int, str)
149+
U = TypeVar("U", *constraints)
150+
151+
152+
class StarredConstraints(Generic[U]):
153+
var: U

‎crates/ruff_linter/resources/test/fixtures/pyupgrade/UP047_0.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,11 @@ def multi_param(t: list[T], c: Callable[[T], None]) -> T:
6363
def outer():
6464
def inner(t: T) -> T:
6565
return t
66+
67+
68+
constraints = (int, str)
69+
U = TypeVar("U", *constraints)
70+
71+
72+
def starred_constraints(var: U) -> U:
73+
return var

‎crates/ruff_linter/resources/test/fixtures/ruff/RUF053.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,12 @@ class C[T: [a]](Generic[_A]): ...
101101
# class C[T: (*a,)](Generic[_A]): ... # TODO: Uncomment this
102102

103103

104+
# TypeVar with starred constraints has no PEP 695 equivalent
105+
constraints = (str, bytes)
106+
_H = TypeVar('_H', *constraints)
107+
class C[T](Generic[_H]): ...
108+
109+
104110
### No errors
105111

106112
class C(Generic[_A]): ...

‎crates/ruff_linter/src/rules/pyupgrade/rules/pep695/mod.rs‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,16 @@ impl TypeVar<'_> {
9393
source,
9494
}
9595
}
96+
97+
/// Returns `true` for a starred constraint like `TypeVar("T", *constraints)`, which has no
98+
/// equivalent in PEP 695 syntax.
99+
pub(crate) fn has_starred_constraint(&self) -> bool {
100+
matches!(
101+
&self.restriction,
102+
Some(TypeVarRestriction::Constraint(constraints))
103+
if constraints.iter().any(|constraint| constraint.is_starred_expr())
104+
)
105+
}
96106
}
97107

98108
impl Display for DisplayTypeVar<'_> {
@@ -399,7 +409,7 @@ fn check_type_vars<'a>(vars: Vec<TypeVar<'a>>, checker: &Checker) -> Option<Vec<
399409
return None;
400410
}
401411

402-
if non_default_follows_default(&vars) {
412+
if non_default_follows_default(&vars) || vars.iter().any(TypeVar::has_starred_constraint) {
403413
return None;
404414
}
405415

‎crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_type_alias.rs‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,9 @@ fn create_diagnostic(
268268
return;
269269
}
270270

271-
if non_default_follows_default(type_vars) {
271+
if non_default_follows_default(type_vars)
272+
|| type_vars.iter().any(TypeVar::has_starred_constraint)
273+
{
272274
return;
273275
}
274276

‎crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP040.py__preview_diff.snap‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,14 @@ UP040 [*] Type alias `DefaultList` uses `TypeAlias` annotation instead of the `t
5656
133 | T_default = TypeVar("T_default", default=int)
5757
134 | DefaultList: TypeAlias = list[T_default]
5858
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
59+
135 |
60+
136 | # TypeVar with starred constraints has no PEP 695 equivalent
61+
|
5962
help: Use the `type` keyword
6063
|
6164
133 | T_default = TypeVar("T_default", default=int)
6265
- DefaultList: TypeAlias = list[T_default]
6366
134 + type DefaultList[T_default = int] = list[T_default]
67+
135 |
6468
|
6569
note: This is an unsafe fix and may change runtime behavior

‎crates/ruff_linter/src/rules/ruff/rules/class_with_mixed_type_vars.rs‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ fn generic_arguments_to_type_vars<'a>(
231231
///
232232
/// * If `type_var` is a `TypeVar`:
233233
/// * It must not be unpacked
234+
/// * It must not have a starred constraint
234235
/// * If `type_var` is a `TypeVarTuple`:
235236
/// * It must be unpacked
236237
/// * It must not have any restrictions
@@ -248,5 +249,9 @@ fn type_var_is_valid(type_var: &TypeVar, unpacked: bool) -> bool {
248249
return false;
249250
}
250251

252+
if type_var.has_starred_constraint() {
253+
return false;
254+
}
255+
251256
true
252257
}

‎crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__class-with-mixed-type-vars_RUF053.py.snap‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,3 +449,12 @@ help: Remove `Generic` base class
449449
97 |
450450
|
451451
note: This is an unsafe fix and may change runtime behavior
452+
453+
RUF053 Class with type parameter list inherits from `Generic`
454+
--> RUF053.py:107:12
455+
|
456+
105 | constraints = (str, bytes)
457+
106 | _H = TypeVar('_H', *constraints)
458+
107 | class C[T](Generic[_H]): ...
459+
| ^^^^^^^^^^^
460+
help: Remove `Generic` base class

0 commit comments

Comments
 (0)