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
13 changes: 13 additions & 0 deletions crates/ruff_linter/resources/test/fixtures/pyupgrade/UP046_2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""This is placed in a separate fixture as `TypeVar` needs to be imported
from `typing_extensions` to support default arguments in Python version < 3.13.
We verify that UP046 doesn't apply in this case.
"""

from typing import Generic
from typing_extensions import TypeVar

T = TypeVar("T", default=str)


class DefaultTypeVar(Generic[T]):
var: T
12 changes: 12 additions & 0 deletions crates/ruff_linter/resources/test/fixtures/pyupgrade/UP047_1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""This is placed in a separate fixture as `TypeVar` needs to be imported
from `typing_extensions` to support default arguments in Python version < 3.13.
We verify that UP047 doesn't apply in this case.
"""

from typing_extensions import TypeVar

T = TypeVar("T", default=int)


def default_var(var: T) -> T:
return var
20 changes: 18 additions & 2 deletions crates/ruff_linter/src/rules/pyupgrade/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ mod tests {
#[test_case(Rule::NonPEP695TypeAlias, Path::new("UP040.pyi"))]
#[test_case(Rule::NonPEP695GenericClass, Path::new("UP046_0.py"))]
#[test_case(Rule::NonPEP695GenericClass, Path::new("UP046_1.py"))]
#[test_case(Rule::NonPEP695GenericFunction, Path::new("UP047.py"))]
#[test_case(Rule::NonPEP695GenericFunction, Path::new("UP047_0.py"))]
#[test_case(Rule::PrivateTypeParameter, Path::new("UP049_0.py"))]
#[test_case(Rule::PrivateTypeParameter, Path::new("UP049_1.py"))]
#[test_case(Rule::UselessClassMetaclassType, Path::new("UP050.py"))]
Expand All @@ -125,6 +125,22 @@ mod tests {
Ok(())
}

#[test_case(Rule::NonPEP695GenericClass, Path::new("UP046_2.py"))]
#[test_case(Rule::NonPEP695GenericFunction, Path::new("UP047_1.py"))]
fn rules_not_applied_default_typevar_backported(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = path.to_string_lossy().to_string();
let diagnostics = test_path(
Path::new("pyupgrade").join(path).as_path(),
&settings::LinterSettings {
preview: PreviewMode::Enabled,
unresolved_target_version: PythonVersion::PY312.into(),
..settings::LinterSettings::for_rule(rule_code)
},
)?;
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}

#[test_case(Rule::SuperCallWithParameters, Path::new("UP008.py"))]
#[test_case(Rule::TypingTextStrAlias, Path::new("UP019.py"))]
fn rules_preview(rule_code: Rule, path: &Path) -> Result<()> {
Expand All @@ -144,7 +160,7 @@ mod tests {
#[test_case(Rule::NonPEP695TypeAlias, Path::new("UP040.pyi"))]
#[test_case(Rule::NonPEP695GenericClass, Path::new("UP046_0.py"))]
#[test_case(Rule::NonPEP695GenericClass, Path::new("UP046_1.py"))]
#[test_case(Rule::NonPEP695GenericFunction, Path::new("UP047.py"))]
#[test_case(Rule::NonPEP695GenericFunction, Path::new("UP047_0.py"))]
fn type_var_default_preview(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}__preview_diff", path.to_string_lossy());
assert_diagnostics_diff!(
Expand Down
14 changes: 9 additions & 5 deletions crates/ruff_linter/src/rules/pyupgrade/rules/pep695/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use std::fmt::Display;

use itertools::Itertools;
use ruff_python_ast::{
self as ast, Arguments, Expr, ExprCall, ExprName, ExprSubscript, Identifier, Stmt, StmtAssign,
TypeParam, TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple,
self as ast, Arguments, Expr, ExprCall, ExprName, ExprSubscript, Identifier, PythonVersion,
Stmt, StmtAssign, TypeParam, TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple,
name::Name,
visitor::{self, Visitor},
};
Expand Down Expand Up @@ -369,15 +369,19 @@ fn in_nested_context(checker: &Checker) -> bool {
}

/// Deduplicate `vars`, returning `None` if `vars` is empty or any duplicates are found.
/// Also returns `None` if any `TypeVar` has a default value and preview mode is not enabled.
/// Also returns `None` if any `TypeVar` has a default value and the target Python version
/// is below 3.13 or preview mode is not enabled. Note that `typing_extensions` backports
/// the default argument, but the rule should be skipped in that case.
fn check_type_vars<'a>(vars: Vec<TypeVar<'a>>, checker: &Checker) -> Option<Vec<TypeVar<'a>>> {
if vars.is_empty() {
return None;
}

// If any type variables have defaults and preview mode is not enabled, skip the rule
// If any type variables have defaults, skip the rule unless
// running with preview mode enabled and targeting Python 3.13+.
if vars.iter().any(|tv| tv.default.is_some())
&& !is_type_var_default_enabled(checker.settings())
&& (checker.target_version() < PythonVersion::PY313
|| !is_type_var_default_enabled(checker.settings()))
{
return None;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
---

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
---
UP047 [*] Generic function `f` should use type parameters
--> UP047.py:12:5
--> UP047_0.py:12:5
|
12 | def f(t: T) -> T:
| ^^^^^^^
Expand All @@ -20,7 +20,7 @@ help: Use type parameters
note: This is an unsafe fix and may change runtime behavior

UP047 [*] Generic function `g` should use type parameters
--> UP047.py:16:5
--> UP047_0.py:16:5
|
16 | def g(ts: tuple[*Ts]) -> tuple[*Ts]:
| ^^^^^^^^^^^^^^^^^
Expand All @@ -38,7 +38,7 @@ help: Use type parameters
note: This is an unsafe fix and may change runtime behavior

UP047 [*] Generic function `h` should use type parameters
--> UP047.py:20:5
--> UP047_0.py:20:5
|
20 | def h(
| _____^
Expand All @@ -62,7 +62,7 @@ help: Use type parameters
note: This is an unsafe fix and may change runtime behavior

UP047 [*] Generic function `i` should use type parameters
--> UP047.py:29:5
--> UP047_0.py:29:5
|
29 | def i(s: S) -> S:
| ^^^^^^^
Expand All @@ -80,7 +80,7 @@ help: Use type parameters
note: This is an unsafe fix and may change runtime behavior

UP047 [*] Generic function `broken_fix` should use type parameters
--> UP047.py:39:5
--> UP047_0.py:39:5
|
37 | # TypeVars with the new-style generic syntax and will be rejected by type
38 | # checkers
Expand All @@ -100,7 +100,7 @@ help: Use type parameters
note: This is an unsafe fix and may change runtime behavior

UP047 [*] Generic function `any_str_param` should use type parameters
--> UP047.py:43:5
--> UP047_0.py:43:5
|
43 | def any_str_param(s: AnyStr) -> AnyStr:
| ^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Added: 1

--- Added ---
UP047 [*] Generic function `default_var` should use type parameters
--> UP047.py:51:5
--> UP047_0.py:51:5
|
51 | def default_var(v: V) -> V:
| ^^^^^^^^^^^^^^^^^
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
---