[pylint] Fix PLW0108 autofix introducing a syntax error when the lambda's body contains an assignment expression (#18678)

<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title? (Please prefix
with `[ty]` for ty pull
  requests.)
- Does this pull request include references to any relevant issues?
-->

## Summary

This PR also supresses the fix if the assignment expression target
shadows one of the lambda's parameters.

Fixes #18675

<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

Add regression tests.
<!-- How was it tested? -->
This commit is contained in:
Victor Hugo Gomes 2025-06-26 17:56:17 -03:00 committed by GitHub
parent 32c54189cb
commit a1579d82d0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 64 additions and 12 deletions

View file

@ -57,3 +57,7 @@ _ = lambda x: z(lambda y: x + y)(x)
# 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)

View file

@ -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<String> {
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.

View file

@ -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