[pyupgrade] Don't offer fix when TypeVar has unpacked keyword arguments (UP040, UP046, UP047) - #27202
Conversation
pyupgrade] Don't offer fix when TypeVar has unpacked keyword arguments (UP040, UP046, UP047)
|
| code | total | + violation | - violation | + fix | - fix |
|---|---|---|---|---|---|
| RUF100 | 1 | 0 | 1 | 0 | 0 |
Linter (preview)
ℹ️ ecosystem check detected linter changes. (+0 -1 violations, +0 -0 fixes in 1 projects; 56 projects unchanged)
mlflow/mlflow (+0 -1 violations, +0 -0 fixes)
ruff check --no-cache --exit-zero --no-fix --output-format concise --preview
- mlflow/telemetry/track.py:31:32: unused-noqa [*] Unused `noqa` directive (unused: `RET504`)
Changes by rule (1 rules affected)
| code | total | + violation | - violation | + fix | - fix |
|---|---|---|---|---|---|
| unused-noqa | 1 | 0 | 1 | 0 | 0 |
ntBre
left a comment
There was a problem hiding this comment.
Thanks! I think there are a couple of additional fixes missing. I also had a request for the tests, and CI is flagging a minor formatting issue.
| if arguments.keywords.iter().any(|kw| kw.arg.is_none()) { | ||
| return None; | ||
| } | ||
|
|
There was a problem hiding this comment.
I don't think this is the right place to check this because it returns None for a single TypeVar, when really we want to avoid a fix if any TypeVar has unpacked keyword arguments. For example, this branch still fixes a case like this:
❯ target/debug/ruff check --no-cache --isolated --target-version py314 --preview --select UP047 --unsafe-fixes --diff - <<'PY'
from typing import Any, TypeVar
T = TypeVar("T", **{"default": Any})
U = TypeVar("U")
def f(first: T, second: U) -> tuple[T, U]:
return first, second
PY
@@ -3,5 +3,5 @@
T = TypeVar("T", **{"default": Any})
U = TypeVar("U")
-def f(first: T, second: U) -> tuple[T, U]:
+def f[U](first: T, second: U) -> tuple[T, U]:
return first, second
Would fix 1 error.| .iter() | ||
| .map(|expr| { | ||
| expr.as_name_expr() | ||
| .and_then(|name| expr_name_to_type_var(checker.semantic(), name)) |
There was a problem hiding this comment.
This only covers the type_alias_type part of UP040, the annotated type alias form of the rule still fires:
❯ target/debug/ruff check --no-cache --isolated --target-version py314 --preview --select UP040 --unsafe-fixes --diff - <<'PY'
from typing import Any, TypeAlias, TypeVar
T = TypeVar("T", **{"default": Any})
U = TypeVar("U")
Alias: TypeAlias = list[T]
MixedAlias: TypeAlias = tuple[T, U]
PY
@@ -3,5 +3,5 @@
T = TypeVar("T", **{"default": Any})
U = TypeVar("U")
-Alias: TypeAlias = list[T]
-MixedAlias: TypeAlias = tuple[T, U]
+type Alias = list[T]
+type MixedAlias[U] = tuple[T, U]|
Updated in the second and third commits to address the feedback. The check now uses a dedicated type_var_has_unpacked_kwargs helper called inside TypeVarReferenceVisitor, which correctly handles the multi-TypeVar case, if any TypeVar in the function/class/alias has unpacked kwargs, the entire fix is suppressed rather than partially applied. Also fixed the annotated TypeAlias form of UP040 which wasn't covered in the first pass. Third commit replaces the fixture additions with an mdtest section in non-pep695-type-alias.md. Let me know if there's anything else to address. |
|
Could you address the merge conflicts so I can run CI? |
…, avoid false positives in UP046/UP047
b3dad50 to
b0dedac
Compare
Fixes #27008
The fixes for UP040, UP046, and UP047 were silently dropping unpacked
keyword arguments when inlining a TypeVar into PEP 695 syntax. For example:
T = TypeVar("T", **{"default": Any})
would be incorrectly converted, losing the default entirely and changing
runtime behavior from True to False.
The fix: in
expr_name_to_type_var, returnNonewhen the TypeVar callcontains any unpacked keyword arguments (
kw.arg.is_none()). This causesUP040 to skip the conversion entirely, UP046 to emit a diagnostic without
a fix, and UP047 to skip entirely — consistent with their existing behavior
for unresolvable TypeVars.
Also changed the
unwrap_orfallback innon_pep695_type_alias_typetoand_thenso that unresolvable TypeVars intype_paramscause the ruleto bail out rather than proceeding with incomplete information.
Test Plan:
Added test cases with unpacked kwargs TypeVars to UP040.py, UP046_0.py,
and UP047_0.py fixtures. Verified using the exact reproduction case from
the issue that UP040 no longer flags the conversion, UP046 emits a
diagnostic without a fix, and UP047 produces no diagnostic.
cargo test -p ruff_linter pep695 — 14 passed, 0 failed.