mirror of https://github.com/astral-sh/ruff
Keep lambda parameters on one line and parenthesize the body if it expands (#21385)
## Summary This PR makes two changes to our formatting of `lambda` expressions: 1. We now parenthesize the body expression if it expands 2. We now try to keep the parameters on a single line The latter of these fixes #8179: Black formatting and this PR's formatting: ```py def a(): return b( c, d, e, f=lambda self, *args, **kwargs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa( *args, **kwargs ), ) ``` Stable Ruff formatting ```py def a(): return b( c, d, e, f=lambda self, *args, **kwargs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs), ) ``` We don't parenthesize the body expression here because the call to `aaaa...` has its own parentheses, but adding a binary operator shows the new parenthesization: ```diff @@ -3,7 +3,7 @@ c, d, e, - f=lambda self, *args, **kwargs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa( - *args, **kwargs - ) + 1, + f=lambda self, *args, **kwargs: ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs) + 1 + ), ) ``` This is actually a new divergence from Black, which formats this input like this: ```py def a(): return b( c, d, e, f=lambda self, *args, **kwargs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa( *args, **kwargs ) + 1, ) ``` But I think this is an improvement, unlike the case from #8179. One other, smaller benefit is that because we now add parentheses to lambda bodies, we also remove redundant parentheses: ```diff @pytest.mark.parametrize( "f", [ - lambda x: (x.expanding(min_periods=5).cov(x, pairwise=True)), - lambda x: (x.expanding(min_periods=5).corr(x, pairwise=True)), + lambda x: x.expanding(min_periods=5).cov(x, pairwise=True), + lambda x: x.expanding(min_periods=5).corr(x, pairwise=True), ], ) def test_moment_functions_zero_length_pairwise(f): ``` ## Test Plan New tests taken from #8465 and probably a few more I should grab from the ecosystem results. --------- Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
parent
d5546508cf
commit
0ebdebddd8
|
|
@ -125,6 +125,13 @@ lambda a, /, c: a
|
||||||
*x: x
|
*x: x
|
||||||
)
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
lambda
|
||||||
|
# comment
|
||||||
|
*x,
|
||||||
|
**y: x
|
||||||
|
)
|
||||||
|
|
||||||
(
|
(
|
||||||
lambda
|
lambda
|
||||||
# comment 1
|
# comment 1
|
||||||
|
|
@ -196,6 +203,17 @@ lambda: ( # comment
|
||||||
x
|
x
|
||||||
)
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
lambda # 1
|
||||||
|
# 2
|
||||||
|
x, # 3
|
||||||
|
# 4
|
||||||
|
y
|
||||||
|
: # 5
|
||||||
|
# 6
|
||||||
|
x
|
||||||
|
)
|
||||||
|
|
||||||
(
|
(
|
||||||
lambda
|
lambda
|
||||||
x,
|
x,
|
||||||
|
|
@ -204,6 +222,71 @@ lambda: ( # comment
|
||||||
z
|
z
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Leading
|
||||||
|
lambda x: (
|
||||||
|
lambda y: lambda z: x
|
||||||
|
+ y
|
||||||
|
+ y
|
||||||
|
+ y
|
||||||
|
+ y
|
||||||
|
+ y
|
||||||
|
+ y
|
||||||
|
+ y
|
||||||
|
+ y
|
||||||
|
+ y
|
||||||
|
+ y
|
||||||
|
+ y
|
||||||
|
+ y
|
||||||
|
+ y
|
||||||
|
+ y
|
||||||
|
+ y
|
||||||
|
+ y
|
||||||
|
+ y
|
||||||
|
+ y
|
||||||
|
+ y
|
||||||
|
+ y
|
||||||
|
+ y
|
||||||
|
+ z # Trailing
|
||||||
|
) # Trailing
|
||||||
|
|
||||||
|
|
||||||
|
# Leading
|
||||||
|
lambda x: lambda y: lambda z: [
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
y,
|
||||||
|
y,
|
||||||
|
y,
|
||||||
|
y,
|
||||||
|
y,
|
||||||
|
y,
|
||||||
|
y,
|
||||||
|
y,
|
||||||
|
y,
|
||||||
|
y,
|
||||||
|
y,
|
||||||
|
y,
|
||||||
|
y,
|
||||||
|
y,
|
||||||
|
y,
|
||||||
|
y,
|
||||||
|
y,
|
||||||
|
y,
|
||||||
|
y,
|
||||||
|
y,
|
||||||
|
y,
|
||||||
|
y,
|
||||||
|
y,
|
||||||
|
y,
|
||||||
|
y,
|
||||||
|
y,
|
||||||
|
y,
|
||||||
|
y,
|
||||||
|
z
|
||||||
|
] # Trailing
|
||||||
|
# Trailing
|
||||||
|
|
||||||
lambda self, araa, kkkwargs=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs), e=1, f=2, g=2: d
|
lambda self, araa, kkkwargs=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs), e=1, f=2, g=2: d
|
||||||
|
|
||||||
# Regression tests for https://github.com/astral-sh/ruff/issues/8179
|
# Regression tests for https://github.com/astral-sh/ruff/issues/8179
|
||||||
|
|
@ -228,6 +311,441 @@ def a():
|
||||||
g = 10
|
g = 10
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def a():
|
||||||
|
return b(
|
||||||
|
c,
|
||||||
|
d,
|
||||||
|
e,
|
||||||
|
f=lambda self, *args, **kwargs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(
|
||||||
|
*args, **kwargs
|
||||||
|
) + 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Additional ecosystem cases from https://github.com/astral-sh/ruff/pull/21385
|
||||||
|
class C:
|
||||||
|
def foo():
|
||||||
|
mock_service.return_value.bucket.side_effect = lambda name: (
|
||||||
|
source_bucket
|
||||||
|
if name == source_bucket_name
|
||||||
|
else storage.Bucket(mock_service, destination_bucket_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
class C:
|
||||||
|
function_dict: Dict[Text, Callable[[CRFToken], Any]] = {
|
||||||
|
CRFEntityExtractorOptions.POS2: lambda crf_token: crf_token.pos_tag[:2]
|
||||||
|
if crf_token.pos_tag is not None
|
||||||
|
else None,
|
||||||
|
}
|
||||||
|
|
||||||
|
name = re.sub(r"[^\x21\x23-\x5b\x5d-\x7e]...............", lambda m: f"\\{m.group(0)}", p["name"])
|
||||||
|
|
||||||
|
def foo():
|
||||||
|
if True:
|
||||||
|
if True:
|
||||||
|
return (
|
||||||
|
lambda x: np.exp(cs(np.log(x.to(u.MeV).value))) * u.MeV * u.cm**2 / u.g
|
||||||
|
)
|
||||||
|
|
||||||
|
class C:
|
||||||
|
_is_recognized_dtype: Callable[[DtypeObj], bool] = lambda x: lib.is_np_dtype(
|
||||||
|
x, "M"
|
||||||
|
) or isinstance(x, DatetimeTZDtype)
|
||||||
|
|
||||||
|
class C:
|
||||||
|
def foo():
|
||||||
|
if True:
|
||||||
|
transaction_count = self._query_txs_for_range(
|
||||||
|
get_count_fn=lambda from_ts, to_ts, _chain_id=chain_id: db_evmtx.count_transactions_in_range(
|
||||||
|
chain_id=_chain_id,
|
||||||
|
from_ts=from_ts,
|
||||||
|
to_ts=to_ts,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
transaction_count = self._query_txs_for_range(
|
||||||
|
get_count_fn=lambda from_ts, to_ts, _chain_id=chain_id: db_evmtx.count_transactions_in_range[_chain_id, from_ts, to_ts],
|
||||||
|
)
|
||||||
|
|
||||||
|
def ddb():
|
||||||
|
sql = (
|
||||||
|
lambda var, table, n=N: f"""
|
||||||
|
CREATE TABLE {table} AS
|
||||||
|
SELECT ROW_NUMBER() OVER () AS id, {var}
|
||||||
|
FROM (
|
||||||
|
SELECT {var}
|
||||||
|
FROM RANGE({n}) _ ({var})
|
||||||
|
ORDER BY RANDOM()
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
long_assignment_target.with_attribute.and_a_slice[with_an_index] = ( # 1
|
||||||
|
# 2
|
||||||
|
lambda x, y, z: # 3
|
||||||
|
# 4
|
||||||
|
x + y + z # 5
|
||||||
|
# 6
|
||||||
|
)
|
||||||
|
|
||||||
|
long_assignment_target.with_attribute.and_a_slice[with_an_index] = (
|
||||||
|
lambda x, y, z: x + y + z
|
||||||
|
)
|
||||||
|
|
||||||
|
long_assignment_target.with_attribute.and_a_slice[with_an_index] = lambda x, y, z: x + y + z
|
||||||
|
|
||||||
|
very_long_variable_name_x, very_long_variable_name_y = lambda a: a + some_very_long_expression, lambda b: b * another_very_long_expression_here
|
||||||
|
|
||||||
|
very_long_variable_name_for_result += lambda x: very_long_function_call_that_should_definitely_be_parenthesized_now(x, more_args, additional_parameters)
|
||||||
|
|
||||||
|
|
||||||
|
if 1:
|
||||||
|
if 2:
|
||||||
|
if 3:
|
||||||
|
if self.location in EVM_EVMLIKE_LOCATIONS and database is not None:
|
||||||
|
exported_dict["notes"] = EVM_ADDRESS_REGEX.sub(
|
||||||
|
repl=lambda matched_address: self._maybe_add_label_with_address(
|
||||||
|
database=database,
|
||||||
|
matched_address=matched_address,
|
||||||
|
),
|
||||||
|
string=exported_dict["notes"],
|
||||||
|
)
|
||||||
|
|
||||||
|
class C:
|
||||||
|
def f():
|
||||||
|
return dict(
|
||||||
|
filter(
|
||||||
|
lambda intent_response: self.is_retrieval_intent_response(
|
||||||
|
intent_response
|
||||||
|
),
|
||||||
|
self.responses.items(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"op",
|
||||||
|
[
|
||||||
|
# Not fluent
|
||||||
|
param(
|
||||||
|
lambda left, right: (
|
||||||
|
ibis.timestamp("2017-04-01")
|
||||||
|
),
|
||||||
|
),
|
||||||
|
# These four are fluent and fit on one line inside the parenthesized
|
||||||
|
# lambda body
|
||||||
|
param(
|
||||||
|
lambda left, right: (
|
||||||
|
ibis.timestamp("2017-04-01").cast(dt.date)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
param(
|
||||||
|
lambda left, right: (
|
||||||
|
ibis.timestamp("2017-04-01").cast(dt.date).between(left, right)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
param(lambda left, right: ibis.timestamp("2017-04-01").cast(dt.date)),
|
||||||
|
param(lambda left, right: ibis.timestamp("2017-04-01").cast(dt.date).between(left, right)),
|
||||||
|
# This is too long on one line in the lambda body and gets wrapped
|
||||||
|
# inside the body.
|
||||||
|
param(
|
||||||
|
lambda left, right: (
|
||||||
|
ibis.timestamp("2017-04-01").cast(dt.date).between(left, right).between(left, right)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_string_temporal_compare_between(con, op, left, right): ...
|
||||||
|
|
||||||
|
[
|
||||||
|
(
|
||||||
|
lambda eval_df, _: MetricValue(
|
||||||
|
scores=eval_df["prediction"].tolist(),
|
||||||
|
aggregate_results={"prediction_sum": sum(eval_df["prediction"])},
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
# reuses the list parentheses
|
||||||
|
lambda xxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzzzz: [xxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzzzz]
|
||||||
|
|
||||||
|
# adds parentheses around the body
|
||||||
|
lambda xxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzzzz: xxxxxxxxxxxxxxxxxxxx + yyyyyyyyyyyyyyyyyyyy + zzzzzzzzzzzzzzzzzzzz
|
||||||
|
|
||||||
|
# removes parentheses around the body
|
||||||
|
lambda xxxxxxxxxxxxxxxxxxxx: (xxxxxxxxxxxxxxxxxxxx + 1)
|
||||||
|
|
||||||
|
mapper = lambda x: dict_with_default[np.nan if isinstance(x, float) and np.isnan(x) else x]
|
||||||
|
|
||||||
|
lambda x, y, z: (
|
||||||
|
x + y + z
|
||||||
|
)
|
||||||
|
|
||||||
|
lambda x, y, z: (
|
||||||
|
x + y + z
|
||||||
|
# trailing body
|
||||||
|
)
|
||||||
|
|
||||||
|
lambda x, y, z: (
|
||||||
|
x + y + z # trailing eol body
|
||||||
|
)
|
||||||
|
|
||||||
|
lambda x, y, z: (
|
||||||
|
x + y + z
|
||||||
|
) # trailing lambda
|
||||||
|
|
||||||
|
lambda x, y, z: (
|
||||||
|
# leading body
|
||||||
|
x + y + z
|
||||||
|
)
|
||||||
|
|
||||||
|
lambda x, y, z: ( # leading eol body
|
||||||
|
x + y + z
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
lambda name:
|
||||||
|
source_bucket # trailing eol comment
|
||||||
|
if name == source_bucket_name
|
||||||
|
else storage.Bucket(mock_service, destination_bucket_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
lambda name:
|
||||||
|
# dangling header comment
|
||||||
|
source_bucket
|
||||||
|
if name == source_bucket_name
|
||||||
|
else storage.Bucket(mock_service, destination_bucket_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
x = (
|
||||||
|
lambda name:
|
||||||
|
# dangling header comment
|
||||||
|
source_bucket
|
||||||
|
if name == source_bucket_name
|
||||||
|
else storage.Bucket(mock_service, destination_bucket_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
lambda name: # dangling header comment
|
||||||
|
(
|
||||||
|
source_bucket
|
||||||
|
if name == source_bucket_name
|
||||||
|
else storage.Bucket(mock_service, destination_bucket_name)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
lambda from_ts, to_ts, _chain_id=chain_id: # dangling eol header comment
|
||||||
|
db_evmtx.count_transactions_in_range(
|
||||||
|
chain_id=_chain_id,
|
||||||
|
from_ts=from_ts,
|
||||||
|
to_ts=to_ts,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
lambda from_ts, to_ts, _chain_id=chain_id:
|
||||||
|
# dangling header comment before call
|
||||||
|
db_evmtx.count_transactions_in_range(
|
||||||
|
chain_id=_chain_id,
|
||||||
|
from_ts=from_ts,
|
||||||
|
to_ts=to_ts,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
lambda left, right:
|
||||||
|
# comment
|
||||||
|
ibis.timestamp("2017-04-01").cast(dt.date).between(left, right)
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
lambda left, right:
|
||||||
|
ibis.timestamp("2017-04-01") # comment
|
||||||
|
.cast(dt.date)
|
||||||
|
.between(left, right)
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
lambda xxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyy:
|
||||||
|
# comment
|
||||||
|
[xxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzzzz]
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
lambda x, y:
|
||||||
|
# comment
|
||||||
|
{
|
||||||
|
"key": x,
|
||||||
|
"another": y,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
lambda x, y:
|
||||||
|
# comment
|
||||||
|
(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
z
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
lambda x:
|
||||||
|
# comment
|
||||||
|
dict_with_default[np.nan if isinstance(x, float) and np.isnan(x) else x]
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
lambda from_ts, to_ts, _chain_id=chain_id:
|
||||||
|
db_evmtx.count_transactions_in_range[
|
||||||
|
# comment
|
||||||
|
_chain_id, from_ts, to_ts
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
lambda
|
||||||
|
# comment
|
||||||
|
*args, **kwargs:
|
||||||
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs) + 1
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
lambda # comment
|
||||||
|
*args, **kwargs:
|
||||||
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs) + 1
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
lambda # comment 1
|
||||||
|
# comment 2
|
||||||
|
*args, **kwargs: # comment 3
|
||||||
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs) + 1
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
lambda # comment 1
|
||||||
|
*args, **kwargs: # comment 3
|
||||||
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs) + 1
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
lambda *args, **kwargs:
|
||||||
|
# comment 1
|
||||||
|
( # comment 2
|
||||||
|
# comment 3
|
||||||
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs) + 1 # comment 4
|
||||||
|
# comment 5
|
||||||
|
) # comment 6
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
lambda *brgs, **kwargs:
|
||||||
|
# comment 1
|
||||||
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa( # comment 2
|
||||||
|
# comment 3
|
||||||
|
*brgs, **kwargs) + 1 # comment 4
|
||||||
|
# comment 5
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
lambda *crgs, **kwargs: # comment 1
|
||||||
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*crgs, **kwargs) + 1
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
lambda *drgs, **kwargs: # comment 1
|
||||||
|
(
|
||||||
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*drgs, **kwargs) + 1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
lambda * # comment 1
|
||||||
|
ergs, **
|
||||||
|
# comment 2
|
||||||
|
kwargs # comment 3
|
||||||
|
: # comment 4
|
||||||
|
(
|
||||||
|
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*ergs, **kwargs) + 1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
lambda # 1
|
||||||
|
# 2
|
||||||
|
left, # 3
|
||||||
|
# 4
|
||||||
|
right: # 5
|
||||||
|
# 6
|
||||||
|
ibis.timestamp("2017-04-01").cast(dt.date).between(left, right)
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
lambda x: # outer comment 1
|
||||||
|
(
|
||||||
|
lambda y: # inner comment 1
|
||||||
|
# inner comment 2
|
||||||
|
lambda z: (
|
||||||
|
# innermost comment
|
||||||
|
x + y + z
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
foo(
|
||||||
|
lambda from_ts, # comment prevents collapsing the parameters to one line
|
||||||
|
to_ts, _chain_id=chain_id: db_evmtx.count_transactions_in_range(
|
||||||
|
chain_id=_chain_id,
|
||||||
|
from_ts=from_ts,
|
||||||
|
to_ts=to_ts,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
foo(
|
||||||
|
lambda from_ts, # but still wrap the body if it gets too long
|
||||||
|
to_ts,
|
||||||
|
_chain_id=chain_id: db_evmtx.count_transactions_in_rangeeeeeeeeeeeeeeeeeeeeeeeeeeeee(
|
||||||
|
chain_id=_chain_id,
|
||||||
|
from_ts=from_ts,
|
||||||
|
to_ts=to_ts,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
transform = lambda left, right: ibis.timestamp("2017-04-01").cast(dt.date).between(left, right).between(left, right) # trailing comment
|
||||||
|
|
||||||
|
(
|
||||||
|
lambda: # comment
|
||||||
|
1
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
lambda # comment
|
||||||
|
:
|
||||||
|
1
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
lambda:
|
||||||
|
# comment
|
||||||
|
1
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
lambda: # comment 1
|
||||||
|
# comment 2
|
||||||
|
1
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
lambda # comment 1
|
||||||
|
# comment 2
|
||||||
|
: # comment 3
|
||||||
|
# comment 4
|
||||||
|
1
|
||||||
|
)
|
||||||
|
|
||||||
(
|
(
|
||||||
lambda
|
lambda
|
||||||
* # comment 2
|
* # comment 2
|
||||||
|
|
@ -271,3 +789,18 @@ def a():
|
||||||
x:
|
x:
|
||||||
x
|
x
|
||||||
)
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
lambda: # dangling-end-of-line
|
||||||
|
# dangling-own-line
|
||||||
|
( # leading-body-end-of-line
|
||||||
|
x
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
(
|
||||||
|
lambda: # dangling-end-of-line
|
||||||
|
( # leading-body-end-of-line
|
||||||
|
x
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use ruff_formatter::{Argument, Arguments, write};
|
use ruff_formatter::{Argument, Arguments, format_args, write};
|
||||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||||
|
|
||||||
use crate::context::{NodeLevel, WithNodeLevel};
|
use crate::context::{NodeLevel, WithNodeLevel};
|
||||||
|
|
@ -33,20 +33,27 @@ impl<'ast> Format<PyFormatContext<'ast>> for ParenthesizeIfExpands<'_, 'ast> {
|
||||||
{
|
{
|
||||||
let mut f = WithNodeLevel::new(NodeLevel::ParenthesizedExpression, f);
|
let mut f = WithNodeLevel::new(NodeLevel::ParenthesizedExpression, f);
|
||||||
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
[group(&format_with(|f| {
|
|
||||||
if_group_breaks(&token("(")).fmt(f)?;
|
|
||||||
|
|
||||||
if self.indent {
|
if self.indent {
|
||||||
soft_block_indent(&Arguments::from(&self.inner)).fmt(f)?;
|
let parens_id = f.group_id("indented_parenthesize_if_expands");
|
||||||
|
group(&format_args![
|
||||||
|
if_group_breaks(&token("(")),
|
||||||
|
indent_if_group_breaks(
|
||||||
|
&format_args![soft_line_break(), &Arguments::from(&self.inner)],
|
||||||
|
parens_id
|
||||||
|
),
|
||||||
|
soft_line_break(),
|
||||||
|
if_group_breaks(&token(")"))
|
||||||
|
])
|
||||||
|
.with_id(Some(parens_id))
|
||||||
|
.fmt(&mut f)
|
||||||
} else {
|
} else {
|
||||||
Arguments::from(&self.inner).fmt(f)?;
|
group(&format_args![
|
||||||
|
if_group_breaks(&token("(")),
|
||||||
|
Arguments::from(&self.inner),
|
||||||
|
if_group_breaks(&token(")")),
|
||||||
|
])
|
||||||
|
.fmt(&mut f)
|
||||||
}
|
}
|
||||||
|
|
||||||
if_group_breaks(&token(")")).fmt(f)
|
|
||||||
}))]
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,21 @@
|
||||||
use ruff_formatter::write;
|
use ruff_formatter::{FormatRuleWithOptions, RemoveSoftLinesBuffer, format_args, write};
|
||||||
use ruff_python_ast::AnyNodeRef;
|
use ruff_python_ast::{AnyNodeRef, Expr, ExprLambda};
|
||||||
use ruff_python_ast::ExprLambda;
|
|
||||||
use ruff_text_size::Ranged;
|
use ruff_text_size::Ranged;
|
||||||
|
|
||||||
use crate::comments::dangling_comments;
|
use crate::builders::parenthesize_if_expands;
|
||||||
use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses};
|
use crate::comments::{SourceComment, dangling_comments, leading_comments, trailing_comments};
|
||||||
|
use crate::expression::parentheses::{
|
||||||
|
NeedsParentheses, OptionalParentheses, Parentheses, is_expression_parenthesized,
|
||||||
|
};
|
||||||
|
use crate::expression::{CallChainLayout, has_own_parentheses};
|
||||||
use crate::other::parameters::ParametersParentheses;
|
use crate::other::parameters::ParametersParentheses;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::preview::is_parenthesize_lambda_bodies_enabled;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct FormatExprLambda;
|
pub struct FormatExprLambda {
|
||||||
|
layout: ExprLambdaLayout,
|
||||||
|
}
|
||||||
|
|
||||||
impl FormatNodeRule<ExprLambda> for FormatExprLambda {
|
impl FormatNodeRule<ExprLambda> for FormatExprLambda {
|
||||||
fn fmt_fields(&self, item: &ExprLambda, f: &mut PyFormatter) -> FormatResult<()> {
|
fn fmt_fields(&self, item: &ExprLambda, f: &mut PyFormatter) -> FormatResult<()> {
|
||||||
|
|
@ -20,13 +26,19 @@ impl FormatNodeRule<ExprLambda> for FormatExprLambda {
|
||||||
body,
|
body,
|
||||||
} = item;
|
} = item;
|
||||||
|
|
||||||
|
let body = &**body;
|
||||||
|
let parameters = parameters.as_deref();
|
||||||
|
|
||||||
let comments = f.context().comments().clone();
|
let comments = f.context().comments().clone();
|
||||||
let dangling = comments.dangling(item);
|
let dangling = comments.dangling(item);
|
||||||
|
let preview = is_parenthesize_lambda_bodies_enabled(f.context());
|
||||||
|
|
||||||
write!(f, [token("lambda")])?;
|
write!(f, [token("lambda")])?;
|
||||||
|
|
||||||
if let Some(parameters) = parameters {
|
// Format any dangling comments before the parameters, but save any dangling comments after
|
||||||
// In this context, a dangling comment can either be a comment between the `lambda` the
|
// the parameters/after the header to be formatted with the body below.
|
||||||
|
let dangling_header_comments = if let Some(parameters) = parameters {
|
||||||
|
// In this context, a dangling comment can either be a comment between the `lambda` and the
|
||||||
// parameters, or a comment between the parameters and the body.
|
// parameters, or a comment between the parameters and the body.
|
||||||
let (dangling_before_parameters, dangling_after_parameters) = dangling
|
let (dangling_before_parameters, dangling_after_parameters) = dangling
|
||||||
.split_at(dangling.partition_point(|comment| comment.end() < parameters.start()));
|
.split_at(dangling.partition_point(|comment| comment.end() < parameters.start()));
|
||||||
|
|
@ -86,7 +98,7 @@ impl FormatNodeRule<ExprLambda> for FormatExprLambda {
|
||||||
// *x: x
|
// *x: x
|
||||||
// )
|
// )
|
||||||
// ```
|
// ```
|
||||||
if comments.has_leading(&**parameters) {
|
if comments.has_leading(parameters) {
|
||||||
hard_line_break().fmt(f)?;
|
hard_line_break().fmt(f)?;
|
||||||
} else {
|
} else {
|
||||||
write!(f, [space()])?;
|
write!(f, [space()])?;
|
||||||
|
|
@ -95,32 +107,90 @@ impl FormatNodeRule<ExprLambda> for FormatExprLambda {
|
||||||
write!(f, [dangling_comments(dangling_before_parameters)])?;
|
write!(f, [dangling_comments(dangling_before_parameters)])?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try to keep the parameters on a single line, unless there are intervening comments.
|
||||||
|
if preview && !comments.contains_comments(parameters.into()) {
|
||||||
|
let mut buffer = RemoveSoftLinesBuffer::new(f);
|
||||||
|
write!(
|
||||||
|
buffer,
|
||||||
|
[parameters
|
||||||
|
.format()
|
||||||
|
.with_options(ParametersParentheses::Never)]
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
[parameters
|
[parameters
|
||||||
.format()
|
.format()
|
||||||
.with_options(ParametersParentheses::Never)]
|
.with_options(ParametersParentheses::Never)]
|
||||||
)?;
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
dangling_after_parameters
|
||||||
|
} else {
|
||||||
|
dangling
|
||||||
|
};
|
||||||
|
|
||||||
write!(f, [token(":")])?;
|
write!(f, [token(":")])?;
|
||||||
|
|
||||||
if dangling_after_parameters.is_empty() {
|
if dangling_header_comments.is_empty() {
|
||||||
write!(f, [space()])?;
|
write!(f, [space()])?;
|
||||||
} else {
|
} else if !preview {
|
||||||
write!(f, [dangling_comments(dangling_after_parameters)])?;
|
write!(f, [dangling_comments(dangling_header_comments)])?;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
write!(f, [token(":")])?;
|
|
||||||
|
|
||||||
// In this context, a dangling comment is a comment between the `lambda` and the body.
|
if !preview {
|
||||||
if dangling.is_empty() {
|
return body.format().fmt(f);
|
||||||
write!(f, [space()])?;
|
}
|
||||||
} else {
|
|
||||||
write!(f, [dangling_comments(dangling)])?;
|
let fmt_body = FormatBody {
|
||||||
|
body,
|
||||||
|
dangling_header_comments,
|
||||||
|
};
|
||||||
|
|
||||||
|
match self.layout {
|
||||||
|
ExprLambdaLayout::Assignment => fits_expanded(&fmt_body).fmt(f),
|
||||||
|
ExprLambdaLayout::Default => fmt_body.fmt(f),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
write!(f, [body.format()])
|
#[derive(Debug, Default, Copy, Clone)]
|
||||||
|
pub enum ExprLambdaLayout {
|
||||||
|
#[default]
|
||||||
|
Default,
|
||||||
|
|
||||||
|
/// The [`ExprLambda`] is the direct child of an assignment expression, so it needs to use
|
||||||
|
/// `fits_expanded` to prefer parenthesizing its own body before the assignment tries to
|
||||||
|
/// parenthesize the whole lambda. For example, we want this formatting:
|
||||||
|
///
|
||||||
|
/// ```py
|
||||||
|
/// long_assignment_target = lambda x, y, z: (
|
||||||
|
/// x + y + z
|
||||||
|
/// )
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// instead of either of these:
|
||||||
|
///
|
||||||
|
/// ```py
|
||||||
|
/// long_assignment_target = (
|
||||||
|
/// lambda x, y, z: (
|
||||||
|
/// x + y + z
|
||||||
|
/// )
|
||||||
|
/// )
|
||||||
|
///
|
||||||
|
/// long_assignment_target = (
|
||||||
|
/// lambda x, y, z: x + y + z
|
||||||
|
/// )
|
||||||
|
/// ```
|
||||||
|
Assignment,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FormatRuleWithOptions<ExprLambda, PyFormatContext<'_>> for FormatExprLambda {
|
||||||
|
type Options = ExprLambdaLayout;
|
||||||
|
|
||||||
|
fn with_options(mut self, options: Self::Options) -> Self {
|
||||||
|
self.layout = options;
|
||||||
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,3 +207,266 @@ impl NeedsParentheses for ExprLambda {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct FormatBody<'a> {
|
||||||
|
body: &'a Expr,
|
||||||
|
|
||||||
|
/// Dangling comments attached to the lambda header that should be formatted with the body.
|
||||||
|
///
|
||||||
|
/// These can include both own-line and end-of-line comments. For lambdas with parameters, this
|
||||||
|
/// means comments after the parameters:
|
||||||
|
///
|
||||||
|
/// ```py
|
||||||
|
/// (
|
||||||
|
/// lambda x, y # 1
|
||||||
|
/// # 2
|
||||||
|
/// : # 3
|
||||||
|
/// # 4
|
||||||
|
/// x + y
|
||||||
|
/// )
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Or all dangling comments for lambdas without parameters:
|
||||||
|
///
|
||||||
|
/// ```py
|
||||||
|
/// (
|
||||||
|
/// lambda # 1
|
||||||
|
/// # 2
|
||||||
|
/// : # 3
|
||||||
|
/// # 4
|
||||||
|
/// 1
|
||||||
|
/// )
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// In most cases these should formatted within the parenthesized body, as in:
|
||||||
|
///
|
||||||
|
/// ```py
|
||||||
|
/// (
|
||||||
|
/// lambda: ( # 1
|
||||||
|
/// # 2
|
||||||
|
/// # 3
|
||||||
|
/// # 4
|
||||||
|
/// 1
|
||||||
|
/// )
|
||||||
|
/// )
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// or without `# 2`:
|
||||||
|
///
|
||||||
|
/// ```py
|
||||||
|
/// (
|
||||||
|
/// lambda: ( # 1 # 3
|
||||||
|
/// # 4
|
||||||
|
/// 1
|
||||||
|
/// )
|
||||||
|
/// )
|
||||||
|
/// ```
|
||||||
|
dangling_header_comments: &'a [SourceComment],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format<PyFormatContext<'_>> for FormatBody<'_> {
|
||||||
|
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
|
||||||
|
let FormatBody {
|
||||||
|
dangling_header_comments,
|
||||||
|
body,
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
let body = *body;
|
||||||
|
let comments = f.context().comments().clone();
|
||||||
|
let body_comments = comments.leading_dangling_trailing(body);
|
||||||
|
|
||||||
|
if !dangling_header_comments.is_empty() {
|
||||||
|
// Split the dangling header comments into trailing comments formatted with the lambda
|
||||||
|
// header (1) and leading comments formatted with the body (2, 3, 4).
|
||||||
|
//
|
||||||
|
// ```python
|
||||||
|
// (
|
||||||
|
// lambda # 1
|
||||||
|
// # 2
|
||||||
|
// : # 3
|
||||||
|
// # 4
|
||||||
|
// y
|
||||||
|
// )
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// Note that these are split based on their line position rather than using
|
||||||
|
// `partition_point` based on a range, for example.
|
||||||
|
let (trailing_header_comments, leading_body_comments) = dangling_header_comments
|
||||||
|
.split_at(
|
||||||
|
dangling_header_comments
|
||||||
|
.iter()
|
||||||
|
.position(|comment| comment.line_position().is_own_line())
|
||||||
|
.unwrap_or(dangling_header_comments.len()),
|
||||||
|
);
|
||||||
|
|
||||||
|
// If the body is parenthesized and has its own leading comments, preserve the
|
||||||
|
// separation between the dangling lambda comments and the body comments. For
|
||||||
|
// example, preserve this comment positioning:
|
||||||
|
//
|
||||||
|
// ```python
|
||||||
|
// (
|
||||||
|
// lambda: # 1
|
||||||
|
// # 2
|
||||||
|
// ( # 3
|
||||||
|
// x
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// 1 and 2 are dangling on the lambda and emitted first, followed by a hard line
|
||||||
|
// break and the parenthesized body with its leading comments.
|
||||||
|
//
|
||||||
|
// However, when removing 2, 1 and 3 can instead be formatted on the same line:
|
||||||
|
//
|
||||||
|
// ```python
|
||||||
|
// (
|
||||||
|
// lambda: ( # 1 # 3
|
||||||
|
// x
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// ```
|
||||||
|
let comments = f.context().comments();
|
||||||
|
if is_expression_parenthesized(body.into(), comments.ranges(), f.context().source())
|
||||||
|
&& comments.has_leading(body)
|
||||||
|
{
|
||||||
|
trailing_comments(dangling_header_comments).fmt(f)?;
|
||||||
|
|
||||||
|
// Note that `leading_body_comments` have already been formatted as part of
|
||||||
|
// `dangling_header_comments` above, but their presence still determines the spacing
|
||||||
|
// here.
|
||||||
|
if leading_body_comments.is_empty() {
|
||||||
|
space().fmt(f)?;
|
||||||
|
} else {
|
||||||
|
hard_line_break().fmt(f)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.format().with_options(Parentheses::Always).fmt(f)
|
||||||
|
} else {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
[
|
||||||
|
space(),
|
||||||
|
token("("),
|
||||||
|
trailing_comments(trailing_header_comments),
|
||||||
|
block_indent(&format_args!(
|
||||||
|
leading_comments(leading_body_comments),
|
||||||
|
body.format().with_options(Parentheses::Never)
|
||||||
|
)),
|
||||||
|
token(")")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If the body has comments, we always want to preserve the parentheses. This also
|
||||||
|
// ensures that we correctly handle parenthesized comments, and don't need to worry
|
||||||
|
// about them in the implementation below.
|
||||||
|
else if body_comments.has_leading() || body_comments.has_trailing_own_line() {
|
||||||
|
body.format().with_options(Parentheses::Always).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
|
||||||
|
// same line as the lambda parameters. For example, `db_evmtx...` in:
|
||||||
|
//
|
||||||
|
// ```py
|
||||||
|
// transaction_count = self._query_txs_for_range(
|
||||||
|
// get_count_fn=lambda from_ts, to_ts, _chain_id=chain_id: db_evmtx.count_transactions_in_range(
|
||||||
|
// chain_id=_chain_id,
|
||||||
|
// from_ts=from_ts,
|
||||||
|
// to_ts=to_ts,
|
||||||
|
// ),
|
||||||
|
// )
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// should cause the whole lambda body to be parenthesized instead:
|
||||||
|
//
|
||||||
|
// ```py
|
||||||
|
// transaction_count = self._query_txs_for_range(
|
||||||
|
// get_count_fn=lambda from_ts, to_ts, _chain_id=chain_id: (
|
||||||
|
// db_evmtx.count_transactions_in_range(
|
||||||
|
// chain_id=_chain_id,
|
||||||
|
// from_ts=from_ts,
|
||||||
|
// to_ts=to_ts,
|
||||||
|
// )
|
||||||
|
// ),
|
||||||
|
// )
|
||||||
|
// ```
|
||||||
|
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(),
|
||||||
|
) == CallChainLayout::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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 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
|
||||||
|
// lambda being formatted well too. For example:
|
||||||
|
//
|
||||||
|
// ```py
|
||||||
|
// lambda xxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzzzz: [xxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzzzz]
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// gets formatted as:
|
||||||
|
//
|
||||||
|
// ```py
|
||||||
|
// lambda xxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzzzz: [
|
||||||
|
// xxxxxxxxxxxxxxxxxxxx,
|
||||||
|
// yyyyyyyyyyyyyyyyyyyy,
|
||||||
|
// zzzzzzzzzzzzzzzzzzzz
|
||||||
|
// ]
|
||||||
|
// ```
|
||||||
|
else if has_own_parentheses(body, f.context()).is_some() {
|
||||||
|
body.format().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
|
||||||
|
// parentheses around lambda bodies that fit on one line. For example:
|
||||||
|
//
|
||||||
|
// ```py
|
||||||
|
// lambda xxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzzzz: xxxxxxxxxxxxxxxxxxxx + yyyyyyyyyyyyyyyyyyyy + zzzzzzzzzzzzzzzzzzzz
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// is formatted as:
|
||||||
|
//
|
||||||
|
// ```py
|
||||||
|
// lambda xxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzzzz: (
|
||||||
|
// xxxxxxxxxxxxxxxxxxxx + yyyyyyyyyyyyyyyyyyyy + zzzzzzzzzzzzzzzzzzzz
|
||||||
|
// )
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// while
|
||||||
|
//
|
||||||
|
// ```py
|
||||||
|
// lambda xxxxxxxxxxxxxxxxxxxx: (xxxxxxxxxxxxxxxxxxxx + 1)
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// is formatted as:
|
||||||
|
//
|
||||||
|
// ```py
|
||||||
|
// lambda xxxxxxxxxxxxxxxxxxxx: xxxxxxxxxxxxxxxxxxxx + 1
|
||||||
|
// ```
|
||||||
|
else {
|
||||||
|
parenthesize_if_expands(&body.format().with_options(Parentheses::Never)).fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,3 +52,10 @@ pub(crate) const fn is_avoid_parens_for_long_as_captures_enabled(
|
||||||
) -> bool {
|
) -> bool {
|
||||||
context.is_preview()
|
context.is_preview()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the
|
||||||
|
/// [`parenthesize_lambda_bodies`](https://github.com/astral-sh/ruff/pull/21385) preview style is
|
||||||
|
/// enabled.
|
||||||
|
pub(crate) const fn is_parenthesize_lambda_bodies_enabled(context: &PyFormatContext) -> bool {
|
||||||
|
context.is_preview()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use crate::comments::{
|
||||||
Comments, LeadingDanglingTrailingComments, SourceComment, trailing_comments,
|
Comments, LeadingDanglingTrailingComments, SourceComment, trailing_comments,
|
||||||
};
|
};
|
||||||
use crate::context::{NodeLevel, WithNodeLevel};
|
use crate::context::{NodeLevel, WithNodeLevel};
|
||||||
|
use crate::expression::expr_lambda::ExprLambdaLayout;
|
||||||
use crate::expression::parentheses::{
|
use crate::expression::parentheses::{
|
||||||
NeedsParentheses, OptionalParentheses, Parentheses, Parenthesize, is_expression_parenthesized,
|
NeedsParentheses, OptionalParentheses, Parentheses, Parenthesize, is_expression_parenthesized,
|
||||||
optional_parentheses,
|
optional_parentheses,
|
||||||
|
|
@ -18,6 +19,7 @@ use crate::expression::{
|
||||||
maybe_parenthesize_expression,
|
maybe_parenthesize_expression,
|
||||||
};
|
};
|
||||||
use crate::other::interpolated_string::InterpolatedStringLayout;
|
use crate::other::interpolated_string::InterpolatedStringLayout;
|
||||||
|
use crate::preview::is_parenthesize_lambda_bodies_enabled;
|
||||||
use crate::statement::trailing_semicolon;
|
use crate::statement::trailing_semicolon;
|
||||||
use crate::string::StringLikeExtensions;
|
use crate::string::StringLikeExtensions;
|
||||||
use crate::string::implicit::{
|
use crate::string::implicit::{
|
||||||
|
|
@ -303,12 +305,7 @@ impl Format<PyFormatContext<'_>> for FormatStatementsLastExpression<'_> {
|
||||||
&& format_implicit_flat.is_none()
|
&& format_implicit_flat.is_none()
|
||||||
&& format_interpolated_string.is_none()
|
&& format_interpolated_string.is_none()
|
||||||
{
|
{
|
||||||
return maybe_parenthesize_expression(
|
return maybe_parenthesize_value(value, *statement).fmt(f);
|
||||||
value,
|
|
||||||
*statement,
|
|
||||||
Parenthesize::IfBreaks,
|
|
||||||
)
|
|
||||||
.fmt(f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let comments = f.context().comments().clone();
|
let comments = f.context().comments().clone();
|
||||||
|
|
@ -586,11 +583,7 @@ impl Format<PyFormatContext<'_>> for FormatStatementsLastExpression<'_> {
|
||||||
space(),
|
space(),
|
||||||
operator,
|
operator,
|
||||||
space(),
|
space(),
|
||||||
maybe_parenthesize_expression(
|
maybe_parenthesize_value(value, *statement)
|
||||||
value,
|
|
||||||
*statement,
|
|
||||||
Parenthesize::IfBreaks
|
|
||||||
)
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1369,3 +1362,32 @@ fn is_attribute_with_parenthesized_value(target: &Expr, context: &PyFormatContex
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Like [`maybe_parenthesize_expression`] but with special handling for lambdas in preview.
|
||||||
|
fn maybe_parenthesize_value<'a>(
|
||||||
|
expression: &'a Expr,
|
||||||
|
parent: AnyNodeRef<'a>,
|
||||||
|
) -> MaybeParenthesizeValue<'a> {
|
||||||
|
MaybeParenthesizeValue { expression, parent }
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MaybeParenthesizeValue<'a> {
|
||||||
|
expression: &'a Expr,
|
||||||
|
parent: AnyNodeRef<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Format<PyFormatContext<'_>> for MaybeParenthesizeValue<'_> {
|
||||||
|
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
|
||||||
|
let MaybeParenthesizeValue { expression, parent } = self;
|
||||||
|
|
||||||
|
if is_parenthesize_lambda_bodies_enabled(f.context())
|
||||||
|
&& let Expr::Lambda(lambda) = expression
|
||||||
|
&& !f.context().comments().has_leading(lambda)
|
||||||
|
{
|
||||||
|
parenthesize_if_expands(&lambda.format().with_options(ExprLambdaLayout::Assignment))
|
||||||
|
.fmt(f)
|
||||||
|
} else {
|
||||||
|
maybe_parenthesize_expression(expression, *parent, Parenthesize::IfBreaks).fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -906,11 +906,10 @@ x = {
|
||||||
-)
|
-)
|
||||||
+string_with_escaped_nameescape = "........................................................................... \\N{LAO KO LA}"
|
+string_with_escaped_nameescape = "........................................................................... \\N{LAO KO LA}"
|
||||||
|
|
||||||
-msg = lambda x: (
|
msg = lambda x: (
|
||||||
- f"this is a very very very very long lambda value {x} that doesn't fit on a"
|
- f"this is a very very very very long lambda value {x} that doesn't fit on a"
|
||||||
- " single line"
|
- " single line"
|
||||||
+msg = (
|
+ f"this is a very very very very long lambda value {x} that doesn't fit on a single line"
|
||||||
+ lambda x: f"this is a very very very very long lambda value {x} that doesn't fit on a single line"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
dict_with_lambda_values = {
|
dict_with_lambda_values = {
|
||||||
|
|
@ -1403,8 +1402,8 @@ string_with_escaped_nameescape = "..............................................
|
||||||
|
|
||||||
string_with_escaped_nameescape = "........................................................................... \\N{LAO KO LA}"
|
string_with_escaped_nameescape = "........................................................................... \\N{LAO KO LA}"
|
||||||
|
|
||||||
msg = (
|
msg = lambda x: (
|
||||||
lambda x: f"this is a very very very very long lambda value {x} that doesn't fit on a single line"
|
f"this is a very very very very long lambda value {x} that doesn't fit on a single line"
|
||||||
)
|
)
|
||||||
|
|
||||||
dict_with_lambda_values = {
|
dict_with_lambda_values = {
|
||||||
|
|
|
||||||
|
|
@ -375,7 +375,7 @@ a = b if """
|
||||||
# Another use case
|
# Another use case
|
||||||
data = yaml.load("""\
|
data = yaml.load("""\
|
||||||
a: 1
|
a: 1
|
||||||
@@ -77,19 +106,23 @@
|
@@ -77,10 +106,12 @@
|
||||||
b: 2
|
b: 2
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
|
|
@ -390,19 +390,7 @@ a = b if """
|
||||||
|
|
||||||
MULTILINE = """
|
MULTILINE = """
|
||||||
foo
|
foo
|
||||||
""".replace("\n", "")
|
@@ -156,16 +187,24 @@
|
||||||
-generated_readme = lambda project_name: """
|
|
||||||
+generated_readme = (
|
|
||||||
+ lambda project_name: """
|
|
||||||
{}
|
|
||||||
|
|
||||||
<Add content here!>
|
|
||||||
""".strip().format(project_name)
|
|
||||||
+)
|
|
||||||
parser.usage += """
|
|
||||||
Custom extra help summary.
|
|
||||||
|
|
||||||
@@ -156,16 +189,24 @@
|
|
||||||
10 LOAD_CONST 0 (None)
|
10 LOAD_CONST 0 (None)
|
||||||
12 RETURN_VALUE
|
12 RETURN_VALUE
|
||||||
""" % (_C.__init__.__code__.co_firstlineno + 1,)
|
""" % (_C.__init__.__code__.co_firstlineno + 1,)
|
||||||
|
|
@ -433,7 +421,7 @@ a = b if """
|
||||||
[
|
[
|
||||||
"""cow
|
"""cow
|
||||||
moos""",
|
moos""",
|
||||||
@@ -206,7 +247,9 @@
|
@@ -206,7 +245,9 @@
|
||||||
"c"
|
"c"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -444,7 +432,7 @@ a = b if """
|
||||||
|
|
||||||
assert some_var == expected_result, """
|
assert some_var == expected_result, """
|
||||||
test
|
test
|
||||||
@@ -224,10 +267,8 @@
|
@@ -224,10 +265,8 @@
|
||||||
"""Sxxxxxxx xxxxxxxx, xxxxxxx xx xxxxxxxxx
|
"""Sxxxxxxx xxxxxxxx, xxxxxxx xx xxxxxxxxx
|
||||||
xxxxxxxxxxxxx xxxxxxx xxxxxxxxx xxx-xxxxxxxxxx xxxxxx xx xxx-xxxxxx"""
|
xxxxxxxxxxxxx xxxxxxx xxxxxxxxx xxx-xxxxxxxxxx xxxxxx xx xxx-xxxxxx"""
|
||||||
),
|
),
|
||||||
|
|
@ -457,7 +445,7 @@ a = b if """
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,14 +287,12 @@
|
@@ -246,14 +285,12 @@
|
||||||
a
|
a
|
||||||
a"""
|
a"""
|
||||||
),
|
),
|
||||||
|
|
@ -597,13 +585,11 @@ data = yaml.load(
|
||||||
MULTILINE = """
|
MULTILINE = """
|
||||||
foo
|
foo
|
||||||
""".replace("\n", "")
|
""".replace("\n", "")
|
||||||
generated_readme = (
|
generated_readme = lambda project_name: """
|
||||||
lambda project_name: """
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
<Add content here!>
|
<Add content here!>
|
||||||
""".strip().format(project_name)
|
""".strip().format(project_name)
|
||||||
)
|
|
||||||
parser.usage += """
|
parser.usage += """
|
||||||
Custom extra help summary.
|
Custom extra help summary.
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,7 +1,6 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/multiline_string_deviations.py
|
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/multiline_string_deviations.py
|
||||||
snapshot_kind: text
|
|
||||||
---
|
---
|
||||||
## Input
|
## Input
|
||||||
```python
|
```python
|
||||||
|
|
@ -106,3 +105,22 @@ generated_readme = (
|
||||||
""".strip().format(project_name)
|
""".strip().format(project_name)
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Preview changes
|
||||||
|
```diff
|
||||||
|
--- Stable
|
||||||
|
+++ Preview
|
||||||
|
@@ -44,10 +44,8 @@
|
||||||
|
# this by changing `Lambda::needs_parentheses` to return `BestFit` but it causes
|
||||||
|
# issues when the lambda has comments.
|
||||||
|
# Let's keep this as a known deviation for now.
|
||||||
|
-generated_readme = (
|
||||||
|
- lambda project_name: """
|
||||||
|
+generated_readme = lambda project_name: """
|
||||||
|
{}
|
||||||
|
|
||||||
|
<Add content here!>
|
||||||
|
""".strip().format(project_name)
|
||||||
|
-)
|
||||||
|
```
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue