From c06c3f9505cdc975b8b79734cd73215458b7712f Mon Sep 17 00:00:00 2001 From: Dan Parizher <105245560+danparizher@users.noreply.github.com> Date: Wed, 15 Oct 2025 10:51:55 -0400 Subject: [PATCH] [`pyupgrade`] Fix false negative for `TypeVar` with default argument in `non-pep695-generic-class` (`UP046`) (#20660) ## Summary Fixes #20656 --------- Co-authored-by: Brent Westbrook --- .../test/fixtures/pyupgrade/UP040.py | 10 +- .../test/fixtures/pyupgrade/UP046_0.py | 10 +- .../test/fixtures/pyupgrade/UP047.py | 6 +- crates/ruff_linter/src/preview.rs | 5 + crates/ruff_linter/src/rules/pyupgrade/mod.rs | 24 +++- .../src/rules/pyupgrade/rules/pep695/mod.rs | 127 +++++++++--------- .../rules/pep695/non_pep695_generic_class.rs | 2 +- .../pep695/non_pep695_generic_function.rs | 2 +- .../rules/pep695/non_pep695_type_alias.rs | 7 +- ...er__rules__pyupgrade__tests__UP040.py.snap | 26 +++- ...pgrade__tests__UP040.py__preview_diff.snap | 49 +++++++ ...grade__tests__UP040.pyi__preview_diff.snap | 10 ++ ...rade__tests__UP046_0.py__preview_diff.snap | 48 +++++++ ...rade__tests__UP046_1.py__preview_diff.snap | 10 ++ ...pgrade__tests__UP047.py__preview_diff.snap | 29 ++++ 15 files changed, 289 insertions(+), 76 deletions(-) create mode 100644 crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP040.py__preview_diff.snap create mode 100644 crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP040.pyi__preview_diff.snap create mode 100644 crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP046_0.py__preview_diff.snap create mode 100644 crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP046_1.py__preview_diff.snap create mode 100644 crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP047.py__preview_diff.snap diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP040.py b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP040.py index f6797167e1..1d7ed4faab 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP040.py +++ b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP040.py @@ -43,7 +43,7 @@ class Foo: T = typing.TypeVar(*args) x: typing.TypeAlias = list[T] -# `default` should be skipped for now, added in Python 3.13 +# `default` was added in Python 3.13 T = typing.TypeVar("T", default=Any) x: typing.TypeAlias = list[T] @@ -90,9 +90,9 @@ PositiveList = TypeAliasType( "PositiveList2", list[Annotated[T, Gt(0)]], type_params=(T,) ) -# `default` should be skipped for now, added in Python 3.13 +# `default` was added in Python 3.13 T = typing.TypeVar("T", default=Any) -AnyList = TypeAliasType("AnyList", list[T], typep_params=(T,)) +AnyList = TypeAliasType("AnyList", list[T], type_params=(T,)) # unsafe fix if comments within the fix T = TypeVar("T") @@ -128,3 +128,7 @@ T: TypeAlias = ( # comment0 str # comment6 # comment7 ) # comment8 + +# Test case for TypeVar with default - should be converted when preview mode is enabled +T_default = TypeVar("T_default", default=int) +DefaultList: TypeAlias = list[T_default] diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP046_0.py b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP046_0.py index 1d3bcc58be..7983ed4756 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP046_0.py +++ b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP046_0.py @@ -122,7 +122,7 @@ class MixedGenerics[U]: return (u, t) -# TODO(brent) default requires 3.13 +# default requires 3.13 V = TypeVar("V", default=Any, bound=str) @@ -130,6 +130,14 @@ class DefaultTypeVar(Generic[V]): # -> [V: str = Any] var: V +# Test case for TypeVar with default but no bound +W = TypeVar("W", default=int) + + +class DefaultOnlyTypeVar(Generic[W]): # -> [W = int] + var: W + + # nested classes and functions are skipped class Outer: class Inner(Generic[T]): diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP047.py b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP047.py index 48b5d431de..e22d79e52e 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP047.py +++ b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP047.py @@ -44,9 +44,7 @@ def any_str_param(s: AnyStr) -> AnyStr: return s -# these cases are not handled - -# TODO(brent) default requires 3.13 +# default requires 3.13 V = TypeVar("V", default=Any, bound=str) @@ -54,6 +52,8 @@ def default_var(v: V) -> V: return v +# these cases are not handled + def outer(): def inner(t: T) -> T: return t diff --git a/crates/ruff_linter/src/preview.rs b/crates/ruff_linter/src/preview.rs index 87895090e5..dfe95b94bb 100644 --- a/crates/ruff_linter/src/preview.rs +++ b/crates/ruff_linter/src/preview.rs @@ -242,6 +242,11 @@ pub(crate) const fn is_refined_submodule_import_match_enabled(settings: &LinterS settings.preview.is_enabled() } +// https://github.com/astral-sh/ruff/pull/20660 +pub(crate) const fn is_type_var_default_enabled(settings: &LinterSettings) -> bool { + settings.preview.is_enabled() +} + // github.com/astral-sh/ruff/issues/20004 pub(crate) const fn is_b006_check_guaranteed_mutable_expr_enabled( settings: &LinterSettings, diff --git a/crates/ruff_linter/src/rules/pyupgrade/mod.rs b/crates/ruff_linter/src/rules/pyupgrade/mod.rs index d882cd5cca..044c90b3a2 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/mod.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/mod.rs @@ -19,7 +19,7 @@ mod tests { use crate::rules::{isort, pyupgrade}; use crate::settings::types::PreviewMode; use crate::test::{test_path, test_snippet}; - use crate::{assert_diagnostics, settings}; + use crate::{assert_diagnostics, assert_diagnostics_diff, settings}; #[test_case(Rule::ConvertNamedTupleFunctionalToClass, Path::new("UP014.py"))] #[test_case(Rule::ConvertTypedDictFunctionalToClass, Path::new("UP013.py"))] @@ -140,6 +140,28 @@ mod tests { Ok(()) } + #[test_case(Rule::NonPEP695TypeAlias, Path::new("UP040.py"))] + #[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"))] + fn type_var_default_preview(rule_code: Rule, path: &Path) -> Result<()> { + let snapshot = format!("{}__preview_diff", path.to_string_lossy()); + assert_diagnostics_diff!( + snapshot, + Path::new("pyupgrade").join(path).as_path(), + &settings::LinterSettings { + preview: PreviewMode::Disabled, + ..settings::LinterSettings::for_rule(rule_code) + }, + &settings::LinterSettings { + preview: PreviewMode::Enabled, + ..settings::LinterSettings::for_rule(rule_code) + }, + ); + Ok(()) + } + #[test_case(Rule::QuotedAnnotation, Path::new("UP037_0.py"))] #[test_case(Rule::QuotedAnnotation, Path::new("UP037_1.py"))] #[test_case(Rule::QuotedAnnotation, Path::new("UP037_2.pyi"))] 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 41ae88289f..c633c5b4ee 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/mod.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/mod.rs @@ -14,13 +14,14 @@ use ruff_python_ast::{ use ruff_python_semantic::SemanticModel; use ruff_text_size::{Ranged, TextRange}; +use crate::checkers::ast::Checker; +use crate::preview::is_type_var_default_enabled; + pub(crate) use non_pep695_generic_class::*; pub(crate) use non_pep695_generic_function::*; pub(crate) use non_pep695_type_alias::*; pub(crate) use private_type_parameter::*; -use crate::checkers::ast::Checker; - mod non_pep695_generic_class; mod non_pep695_generic_function; mod non_pep695_type_alias; @@ -122,6 +123,10 @@ impl Display for DisplayTypeVar<'_> { } } } + if let Some(default) = self.type_var.default { + f.write_str(" = ")?; + f.write_str(&self.source[default.range()])?; + } Ok(()) } @@ -133,66 +138,63 @@ impl<'a> From<&'a TypeVar<'a>> for TypeParam { name, restriction, kind, - default: _, // TODO(brent) see below + default, }: &'a TypeVar<'a>, ) -> Self { + let default = default.map(|expr| Box::new(expr.clone())); match kind { - TypeParamKind::TypeVar => { - TypeParam::TypeVar(TypeParamTypeVar { - range: TextRange::default(), - node_index: ruff_python_ast::AtomicNodeIndex::NONE, - name: Identifier::new(*name, TextRange::default()), - bound: match restriction { - Some(TypeVarRestriction::Bound(bound)) => Some(Box::new((*bound).clone())), - Some(TypeVarRestriction::Constraint(constraints)) => { - Some(Box::new(Expr::Tuple(ast::ExprTuple { - range: TextRange::default(), - node_index: ruff_python_ast::AtomicNodeIndex::NONE, - elts: constraints.iter().map(|expr| (*expr).clone()).collect(), - ctx: ast::ExprContext::Load, - parenthesized: true, - }))) - } - Some(TypeVarRestriction::AnyStr) => { - Some(Box::new(Expr::Tuple(ast::ExprTuple { - range: TextRange::default(), - node_index: ruff_python_ast::AtomicNodeIndex::NONE, - elts: vec![ - Expr::Name(ExprName { - range: TextRange::default(), - node_index: ruff_python_ast::AtomicNodeIndex::NONE, - id: Name::from("str"), - ctx: ast::ExprContext::Load, - }), - Expr::Name(ExprName { - range: TextRange::default(), - node_index: ruff_python_ast::AtomicNodeIndex::NONE, - id: Name::from("bytes"), - ctx: ast::ExprContext::Load, - }), - ], - ctx: ast::ExprContext::Load, - parenthesized: true, - }))) - } - None => None, - }, - // We don't handle defaults here yet. Should perhaps be a different rule since - // defaults are only valid in 3.13+. - default: None, - }) - } + TypeParamKind::TypeVar => TypeParam::TypeVar(TypeParamTypeVar { + range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::NONE, + name: Identifier::new(*name, TextRange::default()), + bound: match restriction { + Some(TypeVarRestriction::Bound(bound)) => Some(Box::new((*bound).clone())), + Some(TypeVarRestriction::Constraint(constraints)) => { + Some(Box::new(Expr::Tuple(ast::ExprTuple { + range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::NONE, + elts: constraints.iter().map(|expr| (*expr).clone()).collect(), + ctx: ast::ExprContext::Load, + parenthesized: true, + }))) + } + Some(TypeVarRestriction::AnyStr) => { + Some(Box::new(Expr::Tuple(ast::ExprTuple { + range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::NONE, + elts: vec![ + Expr::Name(ExprName { + range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::NONE, + id: Name::from("str"), + ctx: ast::ExprContext::Load, + }), + Expr::Name(ExprName { + range: TextRange::default(), + node_index: ruff_python_ast::AtomicNodeIndex::NONE, + id: Name::from("bytes"), + ctx: ast::ExprContext::Load, + }), + ], + ctx: ast::ExprContext::Load, + parenthesized: true, + }))) + } + None => None, + }, + default, + }), TypeParamKind::TypeVarTuple => TypeParam::TypeVarTuple(TypeParamTypeVarTuple { range: TextRange::default(), node_index: ruff_python_ast::AtomicNodeIndex::NONE, name: Identifier::new(*name, TextRange::default()), - default: None, + default, }), TypeParamKind::ParamSpec => TypeParam::ParamSpec(TypeParamParamSpec { range: TextRange::default(), node_index: ruff_python_ast::AtomicNodeIndex::NONE, name: Identifier::new(*name, TextRange::default()), - default: None, + default, }), } } @@ -318,8 +320,8 @@ pub(crate) fn expr_name_to_type_var<'a>( .first() .is_some_and(Expr::is_string_literal_expr) { - // TODO(brent) `default` was added in PEP 696 and Python 3.13 but can't be used in - // generic type parameters before that + // `default` was added in PEP 696 and Python 3.13. We now support converting + // TypeVars with defaults to PEP 695 type parameters. // // ```python // T = TypeVar("T", default=Any, bound=str) @@ -367,21 +369,22 @@ fn in_nested_context(checker: &Checker) -> bool { } /// Deduplicate `vars`, returning `None` if `vars` is empty or any duplicates are found. -fn check_type_vars(vars: Vec>) -> Option>> { +/// Also returns `None` if any `TypeVar` has a default value and preview mode is not enabled. +fn check_type_vars<'a>(vars: Vec>, checker: &Checker) -> Option>> { if vars.is_empty() { return None; } + // If any type variables have defaults and preview mode is not enabled, skip the rule + if vars.iter().any(|tv| tv.default.is_some()) + && !is_type_var_default_enabled(checker.settings()) + { + return None; + } + // If any type variables were not unique, just bail out here. this is a runtime error and we - // can't predict what the user wanted. also bail out if any Python 3.13+ default values are - // found on the type parameters - (vars - .iter() - .unique_by(|tvar| tvar.name) - .filter(|tvar| tvar.default.is_none()) - .count() - == vars.len()) - .then_some(vars) + // can't predict what the user wanted. + (vars.iter().unique_by(|tvar| tvar.name).count() == vars.len()).then_some(vars) } /// Search `class_bases` for a `typing.Generic` base class. Returns the `Generic` expression (if diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_class.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_class.rs index 00e76a5360..0d9f8ad2d6 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_class.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_class.rs @@ -186,7 +186,7 @@ pub(crate) fn non_pep695_generic_class(checker: &Checker, class_def: &StmtClassD // // just because we can't confirm that `SomethingElse` is a `TypeVar` if !visitor.any_skipped { - let Some(type_vars) = check_type_vars(visitor.vars) else { + let Some(type_vars) = check_type_vars(visitor.vars, checker) else { diagnostic.defuse(); return; }; diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_function.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_function.rs index 645fbcad61..93bd368f45 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_function.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_generic_function.rs @@ -154,7 +154,7 @@ pub(crate) fn non_pep695_generic_function(checker: &Checker, function_def: &Stmt } } - let Some(type_vars) = check_type_vars(type_vars) else { + let Some(type_vars) = check_type_vars(type_vars, checker) else { return; }; diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_type_alias.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_type_alias.rs index e14c5c8632..54390c998c 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_type_alias.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/pep695/non_pep695_type_alias.rs @@ -8,6 +8,7 @@ use ruff_python_ast::{Expr, ExprCall, ExprName, Keyword, StmtAnnAssign, StmtAssi use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; +use crate::preview::is_type_var_default_enabled; use crate::{Applicability, Edit, Fix, FixAvailability, Violation}; use ruff_python_ast::PythonVersion; @@ -232,8 +233,10 @@ pub(crate) fn non_pep695_type_alias(checker: &Checker, stmt: &StmtAnnAssign) { .unique_by(|tvar| tvar.name) .collect::>(); - // TODO(brent) handle `default` arg for Python 3.13+ - if vars.iter().any(|tv| tv.default.is_some()) { + // Skip if any TypeVar has defaults and preview mode is not enabled + if vars.iter().any(|tv| tv.default.is_some()) + && !is_type_var_default_enabled(checker.settings()) + { return; } diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP040.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP040.py.snap index 2ea0df676d..ac492e70d3 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP040.py.snap +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP040.py.snap @@ -217,7 +217,7 @@ UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keywo 44 | x: typing.TypeAlias = list[T] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 45 | -46 | # `default` should be skipped for now, added in Python 3.13 +46 | # `default` was added in Python 3.13 | help: Use the `type` keyword 41 | @@ -226,7 +226,7 @@ help: Use the `type` keyword - x: typing.TypeAlias = list[T] 44 + type x = list[T] 45 | -46 | # `default` should be skipped for now, added in Python 3.13 +46 | # `default` was added in Python 3.13 47 | T = typing.TypeVar("T", default=Any) note: This is an unsafe fix and may change runtime behavior @@ -355,6 +355,26 @@ help: Use the `type` keyword 87 | # OK: Other name 88 | T = TypeVar("T", bound=SupportGt) +UP040 [*] Type alias `AnyList` uses `TypeAliasType` assignment instead of the `type` keyword + --> UP040.py:95:1 + | +93 | # `default` was added in Python 3.13 +94 | T = typing.TypeVar("T", default=Any) +95 | AnyList = TypeAliasType("AnyList", list[T], type_params=(T,)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +96 | +97 | # unsafe fix if comments within the fix + | +help: Use the `type` keyword +92 | +93 | # `default` was added in Python 3.13 +94 | T = typing.TypeVar("T", default=Any) + - AnyList = TypeAliasType("AnyList", list[T], type_params=(T,)) +95 + type AnyList[T = Any] = list[T] +96 | +97 | # unsafe fix if comments within the fix +98 | T = TypeVar("T") + UP040 [*] Type alias `PositiveList` uses `TypeAliasType` assignment instead of the `type` keyword --> UP040.py:99:1 | @@ -469,6 +489,8 @@ UP040 [*] Type alias `T` uses `TypeAlias` annotation instead of the `type` keywo 129 | | # comment7 130 | | ) # comment8 | |_^ +131 | +132 | # Test case for TypeVar with default - should be converted when preview mode is enabled | help: Use the `type` keyword 119 | | str diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP040.py__preview_diff.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP040.py__preview_diff.snap new file mode 100644 index 0000000000..475e758873 --- /dev/null +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP040.py__preview_diff.snap @@ -0,0 +1,49 @@ +--- +source: crates/ruff_linter/src/rules/pyupgrade/mod.rs +--- +--- Linter settings --- +-linter.preview = disabled ++linter.preview = enabled + +--- Summary --- +Removed: 0 +Added: 2 + +--- Added --- +UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword + --> UP040.py:48:1 + | +46 | # `default` was added in Python 3.13 +47 | T = typing.TypeVar("T", default=Any) +48 | x: typing.TypeAlias = list[T] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +49 | +50 | # OK + | +help: Use the `type` keyword +45 | +46 | # `default` was added in Python 3.13 +47 | T = typing.TypeVar("T", default=Any) + - x: typing.TypeAlias = list[T] +48 + type x[T = Any] = list[T] +49 | +50 | # OK +51 | x: TypeAlias +note: This is an unsafe fix and may change runtime behavior + + +UP040 [*] Type alias `DefaultList` uses `TypeAlias` annotation instead of the `type` keyword + --> UP040.py:134:1 + | +132 | # Test case for TypeVar with default - should be converted when preview mode is enabled +133 | T_default = TypeVar("T_default", default=int) +134 | DefaultList: TypeAlias = list[T_default] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: Use the `type` keyword +131 | +132 | # Test case for TypeVar with default - should be converted when preview mode is enabled +133 | T_default = TypeVar("T_default", default=int) + - DefaultList: TypeAlias = list[T_default] +134 + type DefaultList[T_default = int] = list[T_default] +note: This is an unsafe fix and may change runtime behavior diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP040.pyi__preview_diff.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP040.pyi__preview_diff.snap new file mode 100644 index 0000000000..7e33841de0 --- /dev/null +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP040.pyi__preview_diff.snap @@ -0,0 +1,10 @@ +--- +source: crates/ruff_linter/src/rules/pyupgrade/mod.rs +--- +--- Linter settings --- +-linter.preview = disabled ++linter.preview = enabled + +--- Summary --- +Removed: 0 +Added: 0 diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP046_0.py__preview_diff.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP046_0.py__preview_diff.snap new file mode 100644 index 0000000000..3763460d1d --- /dev/null +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP046_0.py__preview_diff.snap @@ -0,0 +1,48 @@ +--- +source: crates/ruff_linter/src/rules/pyupgrade/mod.rs +--- +--- Linter settings --- +-linter.preview = disabled ++linter.preview = enabled + +--- Summary --- +Removed: 0 +Added: 2 + +--- Added --- +UP046 [*] Generic class `DefaultTypeVar` uses `Generic` subclass instead of type parameters + --> UP046_0.py:129:22 + | +129 | class DefaultTypeVar(Generic[V]): # -> [V: str = Any] + | ^^^^^^^^^^ +130 | var: V + | +help: Use type parameters +126 | V = TypeVar("V", default=Any, bound=str) +127 | +128 | + - class DefaultTypeVar(Generic[V]): # -> [V: str = Any] +129 + class DefaultTypeVar[V: str = Any]: # -> [V: str = Any] +130 | var: V +131 | +132 | +note: This is an unsafe fix and may change runtime behavior + + +UP046 [*] Generic class `DefaultOnlyTypeVar` uses `Generic` subclass instead of type parameters + --> UP046_0.py:137:26 + | +137 | class DefaultOnlyTypeVar(Generic[W]): # -> [W = int] + | ^^^^^^^^^^ +138 | var: W + | +help: Use type parameters +134 | W = TypeVar("W", default=int) +135 | +136 | + - class DefaultOnlyTypeVar(Generic[W]): # -> [W = int] +137 + class DefaultOnlyTypeVar[W = int]: # -> [W = int] +138 | var: W +139 | +140 | +note: This is an unsafe fix and may change runtime behavior diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP046_1.py__preview_diff.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP046_1.py__preview_diff.snap new file mode 100644 index 0000000000..7e33841de0 --- /dev/null +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP046_1.py__preview_diff.snap @@ -0,0 +1,10 @@ +--- +source: crates/ruff_linter/src/rules/pyupgrade/mod.rs +--- +--- Linter settings --- +-linter.preview = disabled ++linter.preview = enabled + +--- Summary --- +Removed: 0 +Added: 0 diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP047.py__preview_diff.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP047.py__preview_diff.snap new file mode 100644 index 0000000000..2a11cd2e59 --- /dev/null +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP047.py__preview_diff.snap @@ -0,0 +1,29 @@ +--- +source: crates/ruff_linter/src/rules/pyupgrade/mod.rs +--- +--- Linter settings --- +-linter.preview = disabled ++linter.preview = enabled + +--- Summary --- +Removed: 0 +Added: 1 + +--- Added --- +UP047 [*] Generic function `default_var` should use type parameters + --> UP047.py:51:5 + | +51 | def default_var(v: V) -> V: + | ^^^^^^^^^^^^^^^^^ +52 | return v + | +help: Use type parameters +48 | V = TypeVar("V", default=Any, bound=str) +49 | +50 | + - def default_var(v: V) -> V: +51 + def default_var[V: str = Any](v: V) -> V: +52 | return v +53 | +54 | +note: This is an unsafe fix and may change runtime behavior