Make unnecessary-lambda an always-unsafe fix (#10668)

## Summary

See the linked issue and comment for more.

Closes https://github.com/astral-sh/ruff/issues/10663.
This commit is contained in:
Charlie Marsh 2024-03-29 21:05:05 -04:00 committed by GitHub
parent 7c2e9f71ea
commit fc54f53662
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 19 additions and 18 deletions

View file

@ -1,6 +1,5 @@
use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix}; use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::contains_effect;
use ruff_python_ast::visitor::Visitor; use ruff_python_ast::visitor::Visitor;
use ruff_python_ast::{self as ast, visitor, Expr, ExprLambda, Parameter, ParameterWithDefault}; use ruff_python_ast::{self as ast, visitor, Expr, ExprLambda, Parameter, ParameterWithDefault};
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
@ -27,15 +26,23 @@ use crate::checkers::ast::Checker;
/// ``` /// ```
/// ///
/// ## Fix safety /// ## Fix safety
/// This rule's fix is marked as unsafe in cases in which the lambda body itself /// This rule's fix is marked as unsafe for two primary reasons.
/// contains an effect. ///
/// First, the lambda body itself could contain an effect.
/// ///
/// For example, replacing `lambda x, y: (func()(x, y))` with `func()` would /// 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 /// lead to a change in behavior, as `func()` would be evaluated eagerly when
/// defining the lambda, rather than when the lambda is called. /// defining the lambda, rather than when the lambda is called.
/// ///
/// When the lambda body contains no visible effects, the fix is considered /// However, even when the lambda body itself is pure, the lambda may
/// safe. /// change the argument names, which can lead to a change in behavior when
/// callers pass arguments by name.
///
/// For example, replacing `foo = lambda x, y: func(x, y)` with `foo = func`,
/// where `func` is defined as `def func(a, b): return a + b`, would be a
/// breaking change for callers that execute the lambda by passing arguments by
/// name, as in: `foo(x=1, y=2)`. Since `func` does not define the arguments
/// `x` and `y`, unlike the lambda, the call would raise a `TypeError`.
#[violation] #[violation]
pub struct UnnecessaryLambda; pub struct UnnecessaryLambda;
@ -206,11 +213,7 @@ pub(crate) fn unnecessary_lambda(checker: &mut Checker, lambda: &ExprLambda) {
checker.locator().slice(func.as_ref()).to_string(), checker.locator().slice(func.as_ref()).to_string(),
lambda.range(), lambda.range(),
), ),
if contains_effect(func.as_ref(), |id| checker.semantic().is_builtin(id)) { Applicability::Unsafe,
Applicability::Unsafe
} else {
Applicability::Safe
},
)); ));
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }

View file

@ -9,7 +9,7 @@ unnecessary_lambda.py:1:5: PLW0108 [*] Lambda may be unnecessary; consider inlin
| |
= help: Inline function call = help: Inline function call
Safe fix Unsafe fix
1 |-_ = lambda: print() # [unnecessary-lambda] 1 |-_ = lambda: print() # [unnecessary-lambda]
1 |+_ = print # [unnecessary-lambda] 1 |+_ = print # [unnecessary-lambda]
2 2 | _ = lambda x, y: min(x, y) # [unnecessary-lambda] 2 2 | _ = lambda x, y: min(x, y) # [unnecessary-lambda]
@ -26,7 +26,7 @@ unnecessary_lambda.py:2:5: PLW0108 [*] Lambda may be unnecessary; consider inlin
| |
= help: Inline function call = help: Inline function call
Safe fix Unsafe fix
1 1 | _ = lambda: print() # [unnecessary-lambda] 1 1 | _ = lambda: print() # [unnecessary-lambda]
2 |-_ = lambda x, y: min(x, y) # [unnecessary-lambda] 2 |-_ = lambda x, y: min(x, y) # [unnecessary-lambda]
2 |+_ = min # [unnecessary-lambda] 2 |+_ = min # [unnecessary-lambda]
@ -45,7 +45,7 @@ unnecessary_lambda.py:4:5: PLW0108 [*] Lambda may be unnecessary; consider inlin
| |
= help: Inline function call = help: Inline function call
Safe fix Unsafe fix
1 1 | _ = lambda: print() # [unnecessary-lambda] 1 1 | _ = lambda: print() # [unnecessary-lambda]
2 2 | _ = lambda x, y: min(x, y) # [unnecessary-lambda] 2 2 | _ = lambda x, y: min(x, y) # [unnecessary-lambda]
3 3 | 3 3 |
@ -65,7 +65,7 @@ unnecessary_lambda.py:5:5: PLW0108 [*] Lambda may be unnecessary; consider inlin
| |
= help: Inline function call = help: Inline function call
Safe fix Unsafe fix
2 2 | _ = lambda x, y: min(x, y) # [unnecessary-lambda] 2 2 | _ = lambda x, y: min(x, y) # [unnecessary-lambda]
3 3 | 3 3 |
4 4 | _ = lambda *args: f(*args) # [unnecessary-lambda] 4 4 | _ = lambda *args: f(*args) # [unnecessary-lambda]
@ -85,7 +85,7 @@ unnecessary_lambda.py:6:5: PLW0108 [*] Lambda may be unnecessary; consider inlin
| |
= help: Inline function call = help: Inline function call
Safe fix Unsafe fix
3 3 | 3 3 |
4 4 | _ = lambda *args: f(*args) # [unnecessary-lambda] 4 4 | _ = lambda *args: f(*args) # [unnecessary-lambda]
5 5 | _ = lambda **kwargs: f(**kwargs) # [unnecessary-lambda] 5 5 | _ = lambda **kwargs: f(**kwargs) # [unnecessary-lambda]
@ -106,7 +106,7 @@ unnecessary_lambda.py:7:5: PLW0108 [*] Lambda may be unnecessary; consider inlin
| |
= help: Inline function call = help: Inline function call
Safe fix Unsafe fix
4 4 | _ = lambda *args: f(*args) # [unnecessary-lambda] 4 4 | _ = lambda *args: f(*args) # [unnecessary-lambda]
5 5 | _ = lambda **kwargs: f(**kwargs) # [unnecessary-lambda] 5 5 | _ = lambda **kwargs: f(**kwargs) # [unnecessary-lambda]
6 6 | _ = lambda *args, **kwargs: f(*args, **kwargs) # [unnecessary-lambda] 6 6 | _ = lambda *args, **kwargs: f(*args, **kwargs) # [unnecessary-lambda]
@ -155,5 +155,3 @@ unnecessary_lambda.py:10:5: PLW0108 [*] Lambda may be unnecessary; consider inli
11 11 | 11 11 |
12 12 | # default value in lambda parameters 12 12 | # default value in lambda parameters
13 13 | _ = lambda x=42: print(x) 13 13 | _ = lambda x=42: print(x)