diff --git a/crates/ruff_linter/resources/test/fixtures/pylint/unnecessary_lambda.py b/crates/ruff_linter/resources/test/fixtures/pylint/unnecessary_lambda.py index 8f7dc913967f3f..dbdd9110c25993 100644 --- a/crates/ruff_linter/resources/test/fixtures/pylint/unnecessary_lambda.py +++ b/crates/ruff_linter/resources/test/fixtures/pylint/unnecessary_lambda.py @@ -57,3 +57,7 @@ # lambda uses an additional keyword _ = lambda *args: f(*args, y=1) _ = lambda *args: f(*args, y=x) + +# https://github.com/astral-sh/ruff/issues/18675 +_ = lambda x: (string := str)(x) +_ = lambda x: ((x := 1) and str)(x) diff --git a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_lambda.rs b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_lambda.rs index d1be080b4f823e..783063ccda57b0 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_lambda.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_lambda.rs @@ -4,7 +4,7 @@ use ruff_python_ast::{self as ast, Expr, ExprLambda, Parameter, ParameterWithDef use ruff_text_size::Ranged; use crate::checkers::ast::Checker; -use crate::{AlwaysFixableViolation, Applicability, Edit, Fix}; +use crate::{Edit, Fix, FixAvailability, Violation}; /// ## What it does /// Checks for `lambda` definitions that consist of a single function call @@ -46,14 +46,16 @@ use crate::{AlwaysFixableViolation, Applicability, Edit, Fix}; #[derive(ViolationMetadata)] pub(crate) struct UnnecessaryLambda; -impl AlwaysFixableViolation for UnnecessaryLambda { +impl Violation for UnnecessaryLambda { + const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes; + #[derive_message_formats] fn message(&self) -> String { "Lambda may be unnecessary; consider inlining inner function".to_string() } - fn fix_title(&self) -> String { - "Inline function call".to_string() + fn fix_title(&self) -> Option { + Some("Inline function call".to_string()) } } @@ -199,7 +201,7 @@ pub(crate) fn unnecessary_lambda(checker: &Checker, lambda: &ExprLambda) { finder.names }; - for name in names { + for name in &names { if let Some(binding_id) = checker.semantic().resolve_name(name) { let binding = checker.semantic().binding(binding_id); if checker.semantic().is_current_scope(binding.scope) { @@ -209,13 +211,33 @@ pub(crate) fn unnecessary_lambda(checker: &Checker, lambda: &ExprLambda) { } let mut diagnostic = checker.report_diagnostic(UnnecessaryLambda, lambda.range()); - diagnostic.set_fix(Fix::applicable_edit( - Edit::range_replacement( - checker.locator().slice(func.as_ref()).to_string(), - lambda.range(), - ), - Applicability::Unsafe, - )); + // Suppress the fix if the assignment expression target shadows one of the lambda's parameters. + // This is necessary to avoid introducing a change in the behavior of the program. + for name in names { + if let Some(binding_id) = checker.semantic().lookup_symbol(name.id()) { + let binding = checker.semantic().binding(binding_id); + if checker + .semantic() + .current_scope() + .shadowed_binding(binding_id) + .is_some() + && binding + .expression(checker.semantic()) + .is_some_and(Expr::is_named_expr) + { + return; + } + } + } + + diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement( + if func.is_named_expr() { + format!("({})", checker.locator().slice(func.as_ref())) + } else { + checker.locator().slice(func.as_ref()).to_string() + }, + lambda.range(), + ))); } /// Identify all `Expr::Name` nodes in an AST. diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0108_unnecessary_lambda.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0108_unnecessary_lambda.py.snap index e4e25f27e649d2..73dd687d2fe5f1 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0108_unnecessary_lambda.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLW0108_unnecessary_lambda.py.snap @@ -155,3 +155,29 @@ unnecessary_lambda.py:10:5: PLW0108 [*] Lambda may be unnecessary; consider inli 11 11 | 12 12 | # default value in lambda parameters 13 13 | _ = lambda x=42: print(x) + +unnecessary_lambda.py:62:5: PLW0108 [*] Lambda may be unnecessary; consider inlining inner function + | +61 | # https://github.com/astral-sh/ruff/issues/18675 +62 | _ = lambda x: (string := str)(x) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLW0108 +63 | _ = lambda x: ((x := 1) and str)(x) + | + = help: Inline function call + +ℹ Unsafe fix +59 59 | _ = lambda *args: f(*args, y=x) +60 60 | +61 61 | # https://github.com/astral-sh/ruff/issues/18675 +62 |-_ = lambda x: (string := str)(x) + 62 |+_ = (string := str) +63 63 | _ = lambda x: ((x := 1) and str)(x) + +unnecessary_lambda.py:63:5: PLW0108 Lambda may be unnecessary; consider inlining inner function + | +61 | # https://github.com/astral-sh/ruff/issues/18675 +62 | _ = lambda x: (string := str)(x) +63 | _ = lambda x: ((x := 1) and str)(x) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLW0108 + | + = help: Inline function call