Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -813,3 +813,29 @@ def test_string_temporal_compare_between(con, op, left, right): ...
lambda # dangling header comment
: (x := 1)
)


# Regression tests for https://github.com/astral-sh/ruff/issues/23851
foo = lambda x: 'hello this is a really long string that then get concatenated ' 'with another string that prints {x!r}'

foo = lambda x: 'hello this is a really long string that then get concatenated ' 'with another string that prints {x!r}'.format(x=x)

foo = lambda x: 'hello this is a really long string that then get concatenated ' 'with another string that prints {x!r}'[x]

foo = lambda x: ('hello this is a really long string that then get concatenated ' and 'with another string that prints {x!r}').foo()

foo = lambda: (result := some_really_long_callable_expression)(extra_argument_one, extra_argument_two)

foo = lambda: (result := some_really_long_subscriptable_expression)[extra_long_index_expression]

foo = lambda: (await some_coroutine)(extra_argument_one, extra_argument_two, extra_argument_three)

foo = lambda: call()(extra_argument_one, extra_argument_two, extra_argument_threeeeeeeee)

foo = lambda: call()(extra_argument_one, extra_argument_twooooooooooooooooooo, extra_argument_threeeeeeeee)

foo = lambda: call(argument_one,)(extra_argument_one, extra_argument_twooooooooooooooooooo, extra_argument_threeeeeeeee)

foo = lambda: call(argument_one, argument_two, argument_threeeeeeeeeeeeeeeeeeeeeeeeeeee)(extra_argument_one, extra_argument_twooooooooooooooooooo, extra_argument_threeeeeeeee)

foo = lambda: callllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll(argument_one, argument_two, argument_threeeeeeeeeeeeeeeeeeeeeeeeeeee)(extra_argument_one, extra_argument_twooooooooooooooooooo, extra_argument_threeeeeeeee)
66 changes: 33 additions & 33 deletions crates/ruff_python_formatter/src/expression/expr_lambda.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ use ruff_text_size::Ranged;

use crate::builders::parenthesize_if_expands;
use crate::comments::{SourceComment, dangling_comments, leading_comments, trailing_comments};
use crate::expression::has_own_parentheses;
use crate::expression::parentheses::{
NeedsParentheses, OptionalParentheses, Parentheses, is_expression_parenthesized,
};
use crate::expression::{CallChainLayout, has_own_parentheses};
use crate::other::parameters::ParametersParentheses;
use crate::prelude::*;

Expand Down Expand Up @@ -259,6 +259,7 @@ struct FormatBody<'a> {
}

impl Format<PyFormatContext<'_>> for FormatBody<'_> {
#[expect(clippy::if_same_then_else)]
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
let FormatBody {
dangling_header_comments,
Expand Down Expand Up @@ -358,6 +359,24 @@ impl Format<PyFormatContext<'_>> for FormatBody<'_> {
else if body_comments.has_leading() || body_comments.has_trailing_own_line() {
body.format().with_options(Parentheses::Always).fmt(f)
}
// Include parentheses for cases that always require them, such as named expressions:
//
// ```py
// lambda x: (y := x + 1)
// ```
else if matches!(needs_parentheses, OptionalParentheses::Always) {
body.format().with_options(Parentheses::Always).fmt(f)
}
// Use `parenthesize_if_expands` for cases that require parentheses when broken over
// multiple lines, including some calls and subscripts:
//
// ```py
// lambda x: "implicitly" "concatenated {x}".format(x)
// lambda x: "implicitly" "concatenated {x}"[x]
// ```
else if matches!(needs_parentheses, OptionalParentheses::Multiline) {
parenthesize_if_expands(&body.format().with_options(Parentheses::Never)).fmt(f)
}
// Calls and subscripts require special formatting because they have their own
// parentheses, but they can also have an arbitrary amount of text before the
// opening parenthesis. We want to avoid cases where we keep a long callable on the
Expand Down Expand Up @@ -387,31 +406,20 @@ impl Format<PyFormatContext<'_>> for FormatBody<'_> {
// )
// ```
else if matches!(body, Expr::Call(_) | Expr::Subscript(_)) {
let unparenthesized = body.format().with_options(Parentheses::Never);
if CallChainLayout::from_expression(
body.into(),
comments.ranges(),
f.context().source(),
)
.is_fluent()
{
parenthesize_if_expands(&unparenthesized).fmt(f)
} else {
let unparenthesized = unparenthesized.memoized();
if unparenthesized.inspect(f)?.will_break() {
expand_parent().fmt(f)?;
}

best_fitting![
// body all flat
unparenthesized,
// body expanded
group(&unparenthesized).should_expand(true),
// parenthesized
format_args![token("("), block_indent(&unparenthesized), token(")")]
]
.fmt(f)
let unparenthesized = body.format().with_options(Parentheses::Never).memoized();
if unparenthesized.inspect(f)?.will_break() {
expand_parent().fmt(f)?;
}

best_fitting![
// body all flat
unparenthesized,
// body expanded
group(&unparenthesized).should_expand(true),
// parenthesized
format_args![token("("), block_indent(&unparenthesized), token(")")]
]
.fmt(f)
}
// For other cases with their own parentheses, such as lists, sets, dicts, tuples,
// etc., we can just format the body directly. Their own formatting results in the
Expand All @@ -433,14 +441,6 @@ impl Format<PyFormatContext<'_>> for FormatBody<'_> {
else if has_own_parentheses(body, f.context()).is_some() {
body.format().fmt(f)
}
// Include parentheses for cases that always require them, such as named expressions:
//
// ```py
// lambda x: (y := x + 1)
// ```
else if matches!(needs_parentheses, OptionalParentheses::Always) {
body.format().with_options(Parentheses::Always).fmt(f)
}
// Finally, for expressions without their own parentheses, use
// `parenthesize_if_expands` to add parentheses around the body, only if it expands
// across multiple lines. The `Parentheses::Never` here also removes unnecessary
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,32 @@ lambda x: (
lambda # dangling header comment
: (x := 1)
)


# Regression tests for https://github.com/astral-sh/ruff/issues/23851
foo = lambda x: 'hello this is a really long string that then get concatenated ' 'with another string that prints {x!r}'

foo = lambda x: 'hello this is a really long string that then get concatenated ' 'with another string that prints {x!r}'.format(x=x)

foo = lambda x: 'hello this is a really long string that then get concatenated ' 'with another string that prints {x!r}'[x]

foo = lambda x: ('hello this is a really long string that then get concatenated ' and 'with another string that prints {x!r}').foo()

foo = lambda: (result := some_really_long_callable_expression)(extra_argument_one, extra_argument_two)

foo = lambda: (result := some_really_long_subscriptable_expression)[extra_long_index_expression]

foo = lambda: (await some_coroutine)(extra_argument_one, extra_argument_two, extra_argument_three)

foo = lambda: call()(extra_argument_one, extra_argument_two, extra_argument_threeeeeeeee)

foo = lambda: call()(extra_argument_one, extra_argument_twooooooooooooooooooo, extra_argument_threeeeeeeee)

foo = lambda: call(argument_one,)(extra_argument_one, extra_argument_twooooooooooooooooooo, extra_argument_threeeeeeeee)

foo = lambda: call(argument_one, argument_two, argument_threeeeeeeeeeeeeeeeeeeeeeeeeeee)(extra_argument_one, extra_argument_twooooooooooooooooooo, extra_argument_threeeeeeeee)

foo = lambda: callllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll(argument_one, argument_two, argument_threeeeeeeeeeeeeeeeeeeeeeeeeeee)(extra_argument_one, extra_argument_twooooooooooooooooooo, extra_argument_threeeeeeeee)
```

## Output
Expand Down Expand Up @@ -1680,6 +1706,68 @@ lambda x: (x := 1)
x := 1
)
)


# Regression tests for https://github.com/astral-sh/ruff/issues/23851
foo = lambda x: (
"hello this is a really long string that then get concatenated "
"with another string that prints {x!r}"
)

foo = lambda x: (
"hello this is a really long string that then get concatenated "
"with another string that prints {x!r}".format(x=x)
)

foo = lambda x: (
"hello this is a really long string that then get concatenated "
"with another string that prints {x!r}"[x]
)

foo = lambda x: (
"hello this is a really long string that then get concatenated "
and "with another string that prints {x!r}"
).foo()

foo = lambda: (result := some_really_long_callable_expression)(
extra_argument_one, extra_argument_two
)

foo = lambda: (result := some_really_long_subscriptable_expression)[
extra_long_index_expression
]

foo = lambda: (await some_coroutine)(
extra_argument_one, extra_argument_two, extra_argument_three
)

foo = lambda: call()(
extra_argument_one, extra_argument_two, extra_argument_threeeeeeeee
)

foo = lambda: call()(
extra_argument_one,
extra_argument_twooooooooooooooooooo,
extra_argument_threeeeeeeee,
)

foo = lambda: call(
argument_one,
)(extra_argument_one, extra_argument_twooooooooooooooooooo, extra_argument_threeeeeeeee)

foo = lambda: call(
argument_one, argument_two, argument_threeeeeeeeeeeeeeeeeeeeeeeeeeee
)(extra_argument_one, extra_argument_twooooooooooooooooooo, extra_argument_threeeeeeeee)

foo = lambda: (
callllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll(
argument_one, argument_two, argument_threeeeeeeeeeeeeeeeeeeeeeeeeeee
)(
extra_argument_one,
extra_argument_twooooooooooooooooooo,
extra_argument_threeeeeeeee,
)
)
```


Expand Down
Loading