From 9724dfd939ddf00f14ae97c47265c2568571c36d Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sat, 11 Nov 2023 15:34:02 -0800 Subject: [PATCH] Implement autofix for `unnecessary-lambda` (`PLW0108`) (#8621) Closes https://github.com/astral-sh/ruff/issues/8618. --- .../rules/pylint/rules/unnecessary_lambda.rs | 36 ++++++- ..._tests__PLW0108_unnecessary_lambda.py.snap | 99 +++++++++++++++++-- 2 files changed, 122 insertions(+), 13 deletions(-) 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 82e5c8decd46b..f7bb851e42cdd 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_lambda.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_lambda.rs @@ -1,5 +1,6 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; +use ruff_python_ast::helpers::contains_effect; use ruff_python_ast::visitor::Visitor; use ruff_python_ast::{self as ast, visitor, Expr, ExprLambda, Parameter, ParameterWithDefault}; use ruff_text_size::Ranged; @@ -24,14 +25,29 @@ use crate::checkers::ast::Checker; /// ```python /// df.apply(str) /// ``` +/// +/// ## Fix safety +/// This rule's fix is marked as unsafe in cases in which the lambda body itself +/// contains an effect. +/// +/// For example, replacing `lambda x, y: (func()(x, y))` with `func()` would +/// lead to a change in behavior, as `func()` would be evaluated eagerly when +/// defining the lambda, rather than when the lambda is called. +/// +/// When the lambda body contains no visible effects, the fix is considered +/// safe. #[violation] pub struct UnnecessaryLambda; -impl Violation for UnnecessaryLambda { +impl AlwaysFixableViolation for UnnecessaryLambda { #[derive_message_formats] fn message(&self) -> String { format!("Lambda may be unnecessary; consider inlining inner function") } + + fn fix_title(&self) -> String { + "Inline function call".to_string() + } } /// PLW0108 @@ -184,9 +200,19 @@ pub(crate) fn unnecessary_lambda(checker: &mut Checker, lambda: &ExprLambda) { } } - checker - .diagnostics - .push(Diagnostic::new(UnnecessaryLambda, lambda.range())); + let mut diagnostic = Diagnostic::new(UnnecessaryLambda, lambda.range()); + diagnostic.set_fix(Fix::applicable_edit( + Edit::range_replacement( + checker.locator().slice(func.as_ref()).to_string(), + lambda.range(), + ), + if contains_effect(func.as_ref(), |id| checker.semantic().is_builtin(id)) { + Applicability::Unsafe + } else { + Applicability::Safe + }, + )); + checker.diagnostics.push(diagnostic); } /// 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 c4acb1801dfeb..5247ac88bd92c 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 @@ -1,14 +1,22 @@ --- source: crates/ruff_linter/src/rules/pylint/mod.rs --- -unnecessary_lambda.py:1:5: PLW0108 Lambda may be unnecessary; consider inlining inner function +unnecessary_lambda.py:1:5: PLW0108 [*] Lambda may be unnecessary; consider inlining inner function | 1 | _ = lambda: print() # [unnecessary-lambda] | ^^^^^^^^^^^^^^^ PLW0108 2 | _ = lambda x, y: min(x, y) # [unnecessary-lambda] | + = help: Inline function call -unnecessary_lambda.py:2:5: PLW0108 Lambda may be unnecessary; consider inlining inner function +ℹ Safe fix +1 |-_ = lambda: print() # [unnecessary-lambda] + 1 |+_ = print # [unnecessary-lambda] +2 2 | _ = lambda x, y: min(x, y) # [unnecessary-lambda] +3 3 | +4 4 | _ = lambda *args: f(*args) # [unnecessary-lambda] + +unnecessary_lambda.py:2:5: PLW0108 [*] Lambda may be unnecessary; consider inlining inner function | 1 | _ = lambda: print() # [unnecessary-lambda] 2 | _ = lambda x, y: min(x, y) # [unnecessary-lambda] @@ -16,8 +24,17 @@ unnecessary_lambda.py:2:5: PLW0108 Lambda may be unnecessary; consider inlining 3 | 4 | _ = lambda *args: f(*args) # [unnecessary-lambda] | + = help: Inline function call + +ℹ Safe fix +1 1 | _ = lambda: print() # [unnecessary-lambda] +2 |-_ = lambda x, y: min(x, y) # [unnecessary-lambda] + 2 |+_ = min # [unnecessary-lambda] +3 3 | +4 4 | _ = lambda *args: f(*args) # [unnecessary-lambda] +5 5 | _ = lambda **kwargs: f(**kwargs) # [unnecessary-lambda] -unnecessary_lambda.py:4:5: PLW0108 Lambda may be unnecessary; consider inlining inner function +unnecessary_lambda.py:4:5: PLW0108 [*] Lambda may be unnecessary; consider inlining inner function | 2 | _ = lambda x, y: min(x, y) # [unnecessary-lambda] 3 | @@ -26,8 +43,19 @@ unnecessary_lambda.py:4:5: PLW0108 Lambda may be unnecessary; consider inlining 5 | _ = lambda **kwargs: f(**kwargs) # [unnecessary-lambda] 6 | _ = lambda *args, **kwargs: f(*args, **kwargs) # [unnecessary-lambda] | + = help: Inline function call -unnecessary_lambda.py:5:5: PLW0108 Lambda may be unnecessary; consider inlining inner function +ℹ Safe fix +1 1 | _ = lambda: print() # [unnecessary-lambda] +2 2 | _ = lambda x, y: min(x, y) # [unnecessary-lambda] +3 3 | +4 |-_ = lambda *args: f(*args) # [unnecessary-lambda] + 4 |+_ = f # [unnecessary-lambda] +5 5 | _ = lambda **kwargs: f(**kwargs) # [unnecessary-lambda] +6 6 | _ = lambda *args, **kwargs: f(*args, **kwargs) # [unnecessary-lambda] +7 7 | _ = lambda x, y, z, *args, **kwargs: f(x, y, z, *args, **kwargs) # [unnecessary-lambda] + +unnecessary_lambda.py:5:5: PLW0108 [*] Lambda may be unnecessary; consider inlining inner function | 4 | _ = lambda *args: f(*args) # [unnecessary-lambda] 5 | _ = lambda **kwargs: f(**kwargs) # [unnecessary-lambda] @@ -35,8 +63,19 @@ unnecessary_lambda.py:5:5: PLW0108 Lambda may be unnecessary; consider inlining 6 | _ = lambda *args, **kwargs: f(*args, **kwargs) # [unnecessary-lambda] 7 | _ = lambda x, y, z, *args, **kwargs: f(x, y, z, *args, **kwargs) # [unnecessary-lambda] | + = help: Inline function call + +ℹ Safe fix +2 2 | _ = lambda x, y: min(x, y) # [unnecessary-lambda] +3 3 | +4 4 | _ = lambda *args: f(*args) # [unnecessary-lambda] +5 |-_ = lambda **kwargs: f(**kwargs) # [unnecessary-lambda] + 5 |+_ = f # [unnecessary-lambda] +6 6 | _ = lambda *args, **kwargs: f(*args, **kwargs) # [unnecessary-lambda] +7 7 | _ = lambda x, y, z, *args, **kwargs: f(x, y, z, *args, **kwargs) # [unnecessary-lambda] +8 8 | -unnecessary_lambda.py:6:5: PLW0108 Lambda may be unnecessary; consider inlining inner function +unnecessary_lambda.py:6:5: PLW0108 [*] Lambda may be unnecessary; consider inlining inner function | 4 | _ = lambda *args: f(*args) # [unnecessary-lambda] 5 | _ = lambda **kwargs: f(**kwargs) # [unnecessary-lambda] @@ -44,8 +83,19 @@ unnecessary_lambda.py:6:5: PLW0108 Lambda may be unnecessary; consider inlining | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLW0108 7 | _ = lambda x, y, z, *args, **kwargs: f(x, y, z, *args, **kwargs) # [unnecessary-lambda] | + = help: Inline function call -unnecessary_lambda.py:7:5: PLW0108 Lambda may be unnecessary; consider inlining inner function +ℹ Safe fix +3 3 | +4 4 | _ = lambda *args: f(*args) # [unnecessary-lambda] +5 5 | _ = lambda **kwargs: f(**kwargs) # [unnecessary-lambda] +6 |-_ = lambda *args, **kwargs: f(*args, **kwargs) # [unnecessary-lambda] + 6 |+_ = f # [unnecessary-lambda] +7 7 | _ = lambda x, y, z, *args, **kwargs: f(x, y, z, *args, **kwargs) # [unnecessary-lambda] +8 8 | +9 9 | _ = lambda x: f(lambda x: x)(x) # [unnecessary-lambda] + +unnecessary_lambda.py:7:5: PLW0108 [*] Lambda may be unnecessary; consider inlining inner function | 5 | _ = lambda **kwargs: f(**kwargs) # [unnecessary-lambda] 6 | _ = lambda *args, **kwargs: f(*args, **kwargs) # [unnecessary-lambda] @@ -54,8 +104,19 @@ unnecessary_lambda.py:7:5: PLW0108 Lambda may be unnecessary; consider inlining 8 | 9 | _ = lambda x: f(lambda x: x)(x) # [unnecessary-lambda] | + = help: Inline function call + +ℹ Safe fix +4 4 | _ = lambda *args: f(*args) # [unnecessary-lambda] +5 5 | _ = lambda **kwargs: f(**kwargs) # [unnecessary-lambda] +6 6 | _ = lambda *args, **kwargs: f(*args, **kwargs) # [unnecessary-lambda] +7 |-_ = lambda x, y, z, *args, **kwargs: f(x, y, z, *args, **kwargs) # [unnecessary-lambda] + 7 |+_ = f # [unnecessary-lambda] +8 8 | +9 9 | _ = lambda x: f(lambda x: x)(x) # [unnecessary-lambda] +10 10 | _ = lambda x, y: f(lambda x, y: x + y)(x, y) # [unnecessary-lambda] -unnecessary_lambda.py:9:5: PLW0108 Lambda may be unnecessary; consider inlining inner function +unnecessary_lambda.py:9:5: PLW0108 [*] Lambda may be unnecessary; consider inlining inner function | 7 | _ = lambda x, y, z, *args, **kwargs: f(x, y, z, *args, **kwargs) # [unnecessary-lambda] 8 | @@ -63,8 +124,19 @@ unnecessary_lambda.py:9:5: PLW0108 Lambda may be unnecessary; consider inlining | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLW0108 10 | _ = lambda x, y: f(lambda x, y: x + y)(x, y) # [unnecessary-lambda] | + = help: Inline function call -unnecessary_lambda.py:10:5: PLW0108 Lambda may be unnecessary; consider inlining inner function +ℹ Unsafe fix +6 6 | _ = lambda *args, **kwargs: f(*args, **kwargs) # [unnecessary-lambda] +7 7 | _ = lambda x, y, z, *args, **kwargs: f(x, y, z, *args, **kwargs) # [unnecessary-lambda] +8 8 | +9 |-_ = lambda x: f(lambda x: x)(x) # [unnecessary-lambda] + 9 |+_ = f(lambda x: x) # [unnecessary-lambda] +10 10 | _ = lambda x, y: f(lambda x, y: x + y)(x, y) # [unnecessary-lambda] +11 11 | +12 12 | # default value in lambda parameters + +unnecessary_lambda.py:10:5: PLW0108 [*] Lambda may be unnecessary; consider inlining inner function | 9 | _ = lambda x: f(lambda x: x)(x) # [unnecessary-lambda] 10 | _ = lambda x, y: f(lambda x, y: x + y)(x, y) # [unnecessary-lambda] @@ -72,5 +144,16 @@ unnecessary_lambda.py:10:5: PLW0108 Lambda may be unnecessary; consider inlining 11 | 12 | # default value in lambda parameters | + = help: Inline function call + +ℹ Unsafe fix +7 7 | _ = lambda x, y, z, *args, **kwargs: f(x, y, z, *args, **kwargs) # [unnecessary-lambda] +8 8 | +9 9 | _ = lambda x: f(lambda x: x)(x) # [unnecessary-lambda] +10 |-_ = lambda x, y: f(lambda x, y: x + y)(x, y) # [unnecessary-lambda] + 10 |+_ = f(lambda x, y: x + y) # [unnecessary-lambda] +11 11 | +12 12 | # default value in lambda parameters +13 13 | _ = lambda x=42: print(x)