diff --git a/crates/ruff_python_formatter/src/expression/mod.rs b/crates/ruff_python_formatter/src/expression/mod.rs index a320a1edf54b9..3a5fc61109060 100644 --- a/crates/ruff_python_formatter/src/expression/mod.rs +++ b/crates/ruff_python_formatter/src/expression/mod.rs @@ -359,12 +359,14 @@ impl Format> for MaybeParenthesizeExpression<'_> { parenthesize, } = self; - let preserve_parentheses = parenthesize.is_optional() - && is_expression_parenthesized( - (*expression).into(), - f.context().comments().ranges(), - f.context().source(), - ); + let preserve_parentheses = matches!( + parenthesize, + Parenthesize::Optional | Parenthesize::IfBreaksComprehension + ) && is_expression_parenthesized( + (*expression).into(), + f.context().comments().ranges(), + f.context().source(), + ); // If we want to preserve parentheses, short-circuit. if preserve_parentheses { @@ -387,7 +389,9 @@ impl Format> for MaybeParenthesizeExpression<'_> { // Therefore, it is unnecessary to add an additional pair of parentheses if an outer expression // is parenthesized. Unless, it's the `Parenthesize::IfBreaksParenthesizedNested` layout // where parenthesizing nested `maybe_parenthesized_expression` is explicitly desired. - _ if f.context().node_level().is_parenthesized() => { + _ if f.context().node_level().is_parenthesized() + && !matches!(parenthesize, Parenthesize::IfBreaksComprehension) => + { return if matches!(parenthesize, Parenthesize::IfBreaksParenthesizedNested) { parenthesize_if_expands(&expression.format().with_options(Parentheses::Never)) .with_indent(!is_expression_huggable(expression, f.context())) @@ -408,7 +412,8 @@ impl Format> for MaybeParenthesizeExpression<'_> { Parenthesize::Optional | Parenthesize::IfBreaks | Parenthesize::IfBreaksParenthesized - | Parenthesize::IfBreaksParenthesizedNested => { + | Parenthesize::IfBreaksParenthesizedNested + | Parenthesize::IfBreaksComprehension => { if can_omit_optional_parentheses(expression, f.context()) { optional_parentheses(&unparenthesized).fmt(f) } else { @@ -417,7 +422,9 @@ impl Format> for MaybeParenthesizeExpression<'_> { } }, OptionalParentheses::BestFit => match parenthesize { - Parenthesize::IfBreaksParenthesized | Parenthesize::IfBreaksParenthesizedNested => { + Parenthesize::IfBreaksParenthesized + | Parenthesize::IfBreaksParenthesizedNested + | Parenthesize::IfBreaksComprehension => { // Can-omit layout is relevant for `"abcd".call`. We don't want to add unnecessary // parentheses in this case. if can_omit_optional_parentheses(expression, f.context()) { @@ -448,7 +455,8 @@ impl Format> for MaybeParenthesizeExpression<'_> { | Parenthesize::IfBreaks | Parenthesize::IfRequired | Parenthesize::IfBreaksParenthesized - | Parenthesize::IfBreaksParenthesizedNested => unparenthesized.fmt(f), + | Parenthesize::IfBreaksParenthesizedNested + | Parenthesize::IfBreaksComprehension => unparenthesized.fmt(f), }, OptionalParentheses::Always => { @@ -989,6 +997,10 @@ impl OwnParentheses { const fn is_non_empty(self) -> bool { matches!(self, OwnParentheses::NonEmpty) } + + pub(crate) const fn is_empty(self) -> bool { + matches!(self, OwnParentheses::Empty) + } } /// Returns the [`OwnParentheses`] value for a given [`Expr`], to indicate whether it has its diff --git a/crates/ruff_python_formatter/src/expression/parentheses.rs b/crates/ruff_python_formatter/src/expression/parentheses.rs index a76f8a0aec7c7..fa077eecbddcb 100644 --- a/crates/ruff_python_formatter/src/expression/parentheses.rs +++ b/crates/ruff_python_formatter/src/expression/parentheses.rs @@ -69,12 +69,12 @@ pub(crate) enum Parenthesize { /// [`maybe_parenthesized_expression`] calls unlike other layouts that always omit parentheses /// when outer parentheses are present. IfBreaksParenthesizedNested, -} -impl Parenthesize { - pub(crate) const fn is_optional(self) -> bool { - matches!(self, Parenthesize::Optional) - } + /// Parenthesize the expression if it doesn't fit on a line or if the expression is + /// parenthesized in the source code. This is similar to `Self::Optional` except that this + /// variant will also include parentheses in nested calls, like + /// `Self::IfBreaksParenthesizedNested`. + IfBreaksComprehension, } /// Whether it is necessary to add parentheses around an expression. diff --git a/crates/ruff_python_formatter/src/other/comprehension.rs b/crates/ruff_python_formatter/src/other/comprehension.rs index cfa75b3e881a5..7cdf381a8c290 100644 --- a/crates/ruff_python_formatter/src/other/comprehension.rs +++ b/crates/ruff_python_formatter/src/other/comprehension.rs @@ -5,8 +5,10 @@ use ruff_text_size::{Ranged, TextRange}; use crate::comments::{leading_comments, trailing_comments}; use crate::expression::expr_tuple::TupleParentheses; -use crate::expression::parentheses::is_expression_parenthesized; +use crate::expression::parentheses::{Parenthesize, is_expression_parenthesized}; +use crate::expression::{OwnParentheses, has_own_parentheses, maybe_parenthesize_expression}; use crate::prelude::*; +use crate::preview::is_wrap_comprehension_in_enabled; #[derive(Default)] pub struct FormatComprehension; @@ -104,14 +106,32 @@ impl FormatNodeRule for FormatComprehension { leading_comments(before_in_comments), token("in"), trailing_comments(trailing_in_comments), - Spacer { - expression: iter, - preserve_parentheses: true - }, - iter.format(), ] )?; + if is_wrap_comprehension_in_enabled(f.context()) + && has_own_parentheses(iter, f.context()).is_none_or(OwnParentheses::is_empty) + { + write!( + f, + [ + space(), + maybe_parenthesize_expression(iter, item, Parenthesize::IfBreaksComprehension) + ] + )?; + } else { + write!( + f, + [ + Spacer { + expression: iter, + preserve_parentheses: true + }, + iter.format(), + ] + )?; + } + if !ifs.is_empty() { let joined = format_with(|f| { let mut joiner = f.join_with(soft_line_break_or_space()); diff --git a/crates/ruff_python_formatter/src/preview.rs b/crates/ruff_python_formatter/src/preview.rs index b6479ab1b43f0..3a3de86ce31f9 100644 --- a/crates/ruff_python_formatter/src/preview.rs +++ b/crates/ruff_python_formatter/src/preview.rs @@ -36,3 +36,9 @@ pub(crate) const fn is_remove_parens_around_except_types_enabled( ) -> bool { context.is_preview() } + +/// Returns `true` if the [`wrap_comprehension_in`](https://github.com/astral-sh/ruff/pull/TODO) +/// preview style is enabled. +pub(crate) const fn is_wrap_comprehension_in_enabled(context: &PyFormatContext) -> bool { + context.is_preview() +} diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@cases__preview_wrap_comprehension_in.py.snap b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@cases__preview_wrap_comprehension_in.py.snap index 94553912b78ac..7879702fde435 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@cases__preview_wrap_comprehension_in.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/black_compatibility@cases__preview_wrap_comprehension_in.py.snap @@ -81,83 +81,18 @@ dict_with_really_long_names = { ```diff --- Black +++ Ruff -@@ -1,20 +1,14 @@ +@@ -46,7 +46,9 @@ [ - a -- for graph_path_expression in ( -- refined_constraint.condition_as_predicate.variables -- ) -+ for graph_path_expression in refined_constraint.condition_as_predicate.variables - ] - [ - a -- for graph_path_expression in ( -- refined_constraint.condition_as_predicate.variables -- ) -+ for graph_path_expression in refined_constraint.condition_as_predicate.variables - ] - [ - a -- for graph_path_expression in ( -- refined_constraint.condition_as_predicate.variables -- ) -+ for graph_path_expression in refined_constraint.condition_as_predicate.variables - ] - [ - a -@@ -25,9 +19,7 @@ - - [ - (foobar_very_long_key, foobar_very_long_value) -- for foobar_very_long_key, foobar_very_long_value in ( -- foobar_very_long_dictionary.items() -- ) -+ for foobar_very_long_key, foobar_very_long_value in foobar_very_long_dictionary.items() - ] - - # Don't split the `in` if it's not too long -@@ -47,9 +39,7 @@ [ x - for x in bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb -- for y in ( -- xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -- ) -+ for y in xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - ] - ] - -@@ -58,31 +48,23 @@ - graph_path_expression - for refined_constraint in self._local_constraint_refinements.values() - if refined_constraint is not None -- for graph_path_expression in ( -- refined_constraint.condition_as_predicate.variables -- ) -+ for graph_path_expression in refined_constraint.condition_as_predicate.variables - ] - - # Dictionary comprehensions - dict_with_really_long_names = { - really_really_long_key_name: an_even_longer_really_really_long_key_value -- for really_really_long_key_name, an_even_longer_really_really_long_key_value in ( -- really_really_really_long_dict_name.items() -- ) -+ for really_really_long_key_name, an_even_longer_really_really_long_key_value in really_really_really_long_dict_name.items() - } - { - key_with_super_really_long_name: key_with_super_really_long_name -- for key_with_super_really_long_name in ( -- dictionary_with_super_really_long_name -- ) -+ for key_with_super_really_long_name in dictionary_with_super_really_long_name - } - { - key_with_super_really_long_name: key_with_super_really_long_name -- for key_with_super_really_long_name in ( -- dictionary_with_super_really_long_name -- ) -+ for key_with_super_really_long_name in dictionary_with_super_really_long_name +- for x in bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb ++ for x in ( ++ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb ++ ) + for y in ( + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + ) +@@ -84,5 +86,5 @@ } { key_with_super_really_long_name: key_with_super_really_long_name @@ -171,15 +106,21 @@ dict_with_really_long_names = { ```python [ a - for graph_path_expression in refined_constraint.condition_as_predicate.variables + for graph_path_expression in ( + refined_constraint.condition_as_predicate.variables + ) ] [ a - for graph_path_expression in refined_constraint.condition_as_predicate.variables + for graph_path_expression in ( + refined_constraint.condition_as_predicate.variables + ) ] [ a - for graph_path_expression in refined_constraint.condition_as_predicate.variables + for graph_path_expression in ( + refined_constraint.condition_as_predicate.variables + ) ] [ a @@ -190,7 +131,9 @@ dict_with_really_long_names = { [ (foobar_very_long_key, foobar_very_long_value) - for foobar_very_long_key, foobar_very_long_value in foobar_very_long_dictionary.items() + for foobar_very_long_key, foobar_very_long_value in ( + foobar_very_long_dictionary.items() + ) ] # Don't split the `in` if it's not too long @@ -209,8 +152,12 @@ expected = [i for i in (a if b else c)] [ [ x - for x in bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb - for y in xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + for x in ( + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + ) + for y in ( + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + ) ] ] @@ -219,21 +166,29 @@ graph_path_expressions_in_local_constraint_refinements = [ graph_path_expression for refined_constraint in self._local_constraint_refinements.values() if refined_constraint is not None - for graph_path_expression in refined_constraint.condition_as_predicate.variables + for graph_path_expression in ( + refined_constraint.condition_as_predicate.variables + ) ] # Dictionary comprehensions dict_with_really_long_names = { really_really_long_key_name: an_even_longer_really_really_long_key_value - for really_really_long_key_name, an_even_longer_really_really_long_key_value in really_really_really_long_dict_name.items() + for really_really_long_key_name, an_even_longer_really_really_long_key_value in ( + really_really_really_long_dict_name.items() + ) } { key_with_super_really_long_name: key_with_super_really_long_name - for key_with_super_really_long_name in dictionary_with_super_really_long_name + for key_with_super_really_long_name in ( + dictionary_with_super_really_long_name + ) } { key_with_super_really_long_name: key_with_super_really_long_name - for key_with_super_really_long_name in dictionary_with_super_really_long_name + for key_with_super_really_long_name in ( + dictionary_with_super_really_long_name + ) } { key_with_super_really_long_name: key_with_super_really_long_name diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__dict_comp.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__dict_comp.py.snap index fefd84ab8166c..15291a75682c3 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__dict_comp.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__dict_comp.py.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_formatter/tests/fixtures.rs input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/dict_comp.py -snapshot_kind: text --- ## Input ```python @@ -404,3 +403,68 @@ query = { for key, queries in self._filters.items() } ``` + + +## Preview changes +```diff +--- Stable ++++ Preview +@@ -20,9 +20,10 @@ + # above c + c # c + # above in +- in # in +- # above e +- e # e ++ in ( # in ++ # above e ++ e # e ++ ) + # above if + if # if + # above f +@@ -43,7 +44,9 @@ + for ccccccccccccccccccccccccccccccccccccccc, ddddddddddddddddddd, [ + eeeeeeeeeeeeeeeeeeeeee, + fffffffffffffffffffffffff, +- ] in eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeffffffffffffffffffffffffggggggggggggggggggggghhhhhhhhhhhhhhothermoreeand_even_moreddddddddddddddddddddd ++ ] in ( ++ eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeffffffffffffffffffffffffggggggggggggggggggggghhhhhhhhhhhhhhothermoreeand_even_moreddddddddddddddddddddd ++ ) + if fffffffffffffffffffffffffffffffffffffffffff + < gggggggggggggggggggggggggggggggggggggggggggggg + < hhhhhhhhhhhhhhhhhhhhhhhhhh +@@ -74,7 +77,9 @@ + a, + a, + a, +- ] in this_is_a_very_long_variable_which_will_cause_a_trailing_comma_which_breaks_the_comprehension ++ ] in ( ++ this_is_a_very_long_variable_which_will_cause_a_trailing_comma_which_breaks_the_comprehension ++ ) + } + { + k: v +@@ -99,7 +104,9 @@ + a, + a, + a, +- ) in this_is_a_very_long_variable_which_will_cause_a_trailing_comma_which_breaks_the_comprehension ++ ) in ( ++ this_is_a_very_long_variable_which_will_cause_a_trailing_comma_which_breaks_the_comprehension ++ ) + } + + # Leading +@@ -126,7 +133,9 @@ + a, + a, + a, # Trailing +- ) in this_is_a_very_long_variable_which_will_cause_a_trailing_comma_which_breaks_the_comprehension # Trailing ++ ) in ( ++ this_is_a_very_long_variable_which_will_cause_a_trailing_comma_which_breaks_the_comprehension ++ ) # Trailing + } # Trailing + # Trailing + +``` diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__generator_exp.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__generator_exp.py.snap index d19ce0f393f42..2b6d34eac50f2 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__generator_exp.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__generator_exp.py.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_formatter/tests/fixtures.rs input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/generator_exp.py -snapshot_kind: text --- ## Input ```python diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__list_comp.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__list_comp.py.snap index 0dcdb03493723..8afe315bb9601 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__list_comp.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__list_comp.py.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_formatter/tests/fixtures.rs input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/list_comp.py -snapshot_kind: text --- ## Input ```python @@ -379,3 +378,62 @@ y = [ x ] ``` + + +## Preview changes +```diff +--- Stable ++++ Preview +@@ -20,9 +20,10 @@ + # above c + c # c + # above in +- in # in +- # above e +- e # e ++ in ( # in ++ # above e ++ e # e ++ ) + # above if + if # if + # above f +@@ -40,7 +41,9 @@ + for ccccccccccccccccccccccccccccccccccccccc, ddddddddddddddddddd, [ + eeeeeeeeeeeeeeeeeeeeee, + fffffffffffffffffffffffff, +- ] in eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeffffffffffffffffffffffffggggggggggggggggggggghhhhhhhhhhhhhhothermoreeand_even_moreddddddddddddddddddddd ++ ] in ( ++ eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeffffffffffffffffffffffffggggggggggggggggggggghhhhhhhhhhhhhhothermoreeand_even_moreddddddddddddddddddddd ++ ) + if fffffffffffffffffffffffffffffffffffffffffff + < gggggggggggggggggggggggggggggggggggggggggggggg + < hhhhhhhhhhhhhhhhhhhhhhhhhh +@@ -130,8 +133,10 @@ + + [ + 1 +- for components in b # pylint: disable=undefined-loop-variable # integer 1 may only have decimal 01-09 +- + c # negative decimal ++ for components in ( # pylint: disable=undefined-loop-variable ++ b # integer 1 may only have decimal 01-09 ++ + c ++ ) # negative decimal + ] + + # Parenthesized targets and iterators. +@@ -186,9 +191,10 @@ + a + for + # comment +- a in +- # comment +- x ++ a in ( ++ # comment ++ x ++ ) + if + # asdasd + "askldaklsdnmklasmdlkasmdlkasmdlkasmdasd" != "as,mdnaskldmlkasdmlaksdmlkasdlkasdm" +``` diff --git a/crates/ruff_python_formatter/tests/snapshots/format@expression__set_comp.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@expression__set_comp.py.snap index 179af7f4cd69b..2fe0894d2a43b 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@expression__set_comp.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@expression__set_comp.py.snap @@ -1,7 +1,6 @@ --- source: crates/ruff_python_formatter/tests/fixtures.rs input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/set_comp.py -snapshot_kind: text --- ## Input ```python @@ -124,3 +123,35 @@ selected_choices = { if str(v) not in self.choices.field.empty_values } ``` + + +## Preview changes +```diff +--- Stable ++++ Preview +@@ -20,9 +20,10 @@ + # above c + c # c + # above in +- in # in +- # above e +- e # e ++ in ( # in ++ # above e ++ e # e ++ ) + # above if + if # if + # above f +@@ -40,7 +41,9 @@ + for ccccccccccccccccccccccccccccccccccccccc, ddddddddddddddddddd, [ + eeeeeeeeeeeeeeeeeeeeee, + fffffffffffffffffffffffff, +- ] in eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeffffffffffffffffffffffffggggggggggggggggggggghhhhhhhhhhhhhhothermoreeand_even_moreddddddddddddddddddddd ++ ] in ( ++ eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeffffffffffffffffffffffffggggggggggggggggggggghhhhhhhhhhhhhhothermoreeand_even_moreddddddddddddddddddddd ++ ) + if fffffffffffffffffffffffffffffffffffffffffff + < gggggggggggggggggggggggggggggggggggggggggggggg + < hhhhhhhhhhhhhhhhhhhhhhhhhh +```