From a9198eefdfbe81d95bb25d1b64e5bfa34132577f Mon Sep 17 00:00:00 2001 From: Yilei Yang Date: Mon, 31 Oct 2022 15:39:13 -0700 Subject: [PATCH 1/6] Prefer splitting right hand side of assignment statements. --- CHANGES.md | 2 + src/black/linegen.py | 149 +++++++++++++----- src/black/lines.py | 14 ++ src/black/mode.py | 1 + .../data/preview/long_strings__regression.py | 6 +- tests/data/preview/prefer_rhs_split.py | 55 +++++++ 6 files changed, 181 insertions(+), 46 deletions(-) create mode 100644 tests/data/preview/prefer_rhs_split.py diff --git a/CHANGES.md b/CHANGES.md index 1dcd7f09b3c..7e7b01847dc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,6 +17,8 @@ - Enforce empty lines before classes and functions with sticky leading comments (#3302) - Implicitly concatenated strings used as function args are now wrapped inside parentheses (#3307) +- For assignment statements, prefer splitting the right hand side if the left hand side + fits on a single line (#3368) ### Configuration diff --git a/src/black/linegen.py b/src/black/linegen.py index a2e41bf5912..19f85655fe9 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -2,6 +2,7 @@ Generating lines of code. """ import sys +from dataclasses import dataclass from functools import partial, wraps from typing import Collection, Iterator, List, Optional, Set, Union, cast @@ -12,12 +13,14 @@ append_leaves, can_be_split, can_omit_invisible_parens, + is_assignment_line, is_line_short_enough, line_to_string, ) from black.mode import Feature, Mode, Preview from black.nodes import ( ASSIGNMENTS, + BRACKETS, CLOSING_BRACKETS, OPENING_BRACKETS, RARROW, @@ -600,6 +603,17 @@ def left_hand_split(line: Line, _features: Collection[Feature] = ()) -> Iterator yield result +@dataclass +class _RHSResult: + """Intermediate split result from a right hand split.""" + + head: Line + body: Line + tail: Line + opening_bracket: Leaf + closing_bracket: Leaf + + def right_hand_split( line: Line, line_length: int, @@ -614,66 +628,73 @@ def right_hand_split( Note: running this function modifies `bracket_depth` on the leaves of `line`. """ - tail_leaves: List[Leaf] = [] - body_leaves: List[Leaf] = [] - head_leaves: List[Leaf] = [] - current_leaves = tail_leaves - opening_bracket: Optional[Leaf] = None - closing_bracket: Optional[Leaf] = None - for leaf in reversed(line.leaves): - if current_leaves is body_leaves: - if leaf is opening_bracket: - current_leaves = head_leaves if body_leaves else tail_leaves - current_leaves.append(leaf) - if current_leaves is tail_leaves: - if leaf.type in CLOSING_BRACKETS and id(leaf) not in omit: - opening_bracket = leaf.opening_bracket - closing_bracket = leaf - current_leaves = body_leaves - if not (opening_bracket and closing_bracket and head_leaves): - # If there is no opening or closing_bracket that means the split failed and - # all content is in the tail. Otherwise, if `head_leaves` are empty, it means - # the matching `opening_bracket` wasn't available on `line` anymore. - raise CannotSplit("No brackets found") + rhs_result = _first_right_hand_split(line, omit=omit) + yield from _maybe_split_without_optional_parens( + rhs_result, line, line_length, features=features, omit=omit + ) - tail_leaves.reverse() - body_leaves.reverse() - head_leaves.reverse() - head = bracket_split_build_line(head_leaves, line, opening_bracket) - body = bracket_split_build_line(body_leaves, line, opening_bracket, is_body=True) - tail = bracket_split_build_line(tail_leaves, line, opening_bracket) - bracket_split_succeeded_or_raise(head, body, tail) + +def _maybe_split_without_optional_parens( + rhs: _RHSResult, + line: Line, + line_length: int, + features: Collection[Feature] = (), + omit: Collection[LeafID] = (), +) -> Iterator[Line]: if ( Feature.FORCE_OPTIONAL_PARENTHESES not in features # the opening bracket is an optional paren - and opening_bracket.type == token.LPAR - and not opening_bracket.value + and rhs.opening_bracket.type == token.LPAR + and not rhs.opening_bracket.value # the closing bracket is an optional paren - and closing_bracket.type == token.RPAR - and not closing_bracket.value + and rhs.closing_bracket.type == token.RPAR + and not rhs.closing_bracket.value # it's not an import (optional parens are the only thing we can split on # in this case; attempting a split without them is a waste of time) and not line.is_import # there are no standalone comments in the body - and not body.contains_standalone_comments(0) + and not rhs.body.contains_standalone_comments(0) # and we can actually remove the parens - and can_omit_invisible_parens(body, line_length) + and can_omit_invisible_parens(rhs.body, line_length) ): - omit = {id(closing_bracket), *omit} + omit = {id(rhs.closing_bracket), *omit} try: - yield from right_hand_split(line, line_length, features=features, omit=omit) - return + alt_rhs = _first_right_hand_split(line, omit=omit) + if not ( + Preview.prefer_splitting_right_hand_side_of_assignments in line.mode + # this is an assignment statement + and is_assignment_line(line) + # the left side of assignement contains brackets + and any(leaf.type in BRACKETS for leaf in rhs.head.leaves[:-1]) + # the left side of assignment is short enough + and is_line_short_enough(rhs.head, line_length=line_length) + and ( + # no more splits inside the optional parens (this is done by + # checking whether the resulting first line still has the `=` + # token) + not any(leaf.type == token.EQUAL for leaf in alt_rhs.head.leaves) + # the split from inside the optional parens produces a long line + or not is_line_short_enough(alt_rhs.head, line_length=line_length) + ) + ): + yield from _maybe_split_without_optional_parens( + alt_rhs, line, line_length, features=features, omit=omit + ) + return except CannotSplit as e: if not ( - can_be_split(body) - or is_line_short_enough(body, line_length=line_length) + can_be_split(rhs.body) + or is_line_short_enough(rhs.body, line_length=line_length) ): raise CannotSplit( "Splitting failed, body is still too long and can't be split." ) from e - elif head.contains_multiline_strings() or tail.contains_multiline_strings(): + elif ( + rhs.head.contains_multiline_strings() + or rhs.tail.contains_multiline_strings() + ): raise CannotSplit( "The current optional pair of parentheses is bound to fail to" " satisfy the splitting algorithm because the head or the tail" @@ -681,13 +702,55 @@ def right_hand_split( " line." ) from e - ensure_visible(opening_bracket) - ensure_visible(closing_bracket) - for result in (head, body, tail): + ensure_visible(rhs.opening_bracket) + ensure_visible(rhs.closing_bracket) + for result in (rhs.head, rhs.body, rhs.tail): if result: yield result +def _first_right_hand_split( + line: Line, + omit: Collection[LeafID] = (), +) -> _RHSResult: + """Split the line into head, body, tail starting with the last bracket pair. + + Note: this function should not have side effects. It's replied upon by + _maybe_split_without_optional_parens to get an opinion whether to prefer + splitting on the right side of an assignment statement. + """ + tail_leaves: List[Leaf] = [] + body_leaves: List[Leaf] = [] + head_leaves: List[Leaf] = [] + current_leaves = tail_leaves + opening_bracket: Optional[Leaf] = None + closing_bracket: Optional[Leaf] = None + for leaf in reversed(line.leaves): + if current_leaves is body_leaves: + if leaf is opening_bracket: + current_leaves = head_leaves if body_leaves else tail_leaves + current_leaves.append(leaf) + if current_leaves is tail_leaves: + if leaf.type in CLOSING_BRACKETS and id(leaf) not in omit: + opening_bracket = leaf.opening_bracket + closing_bracket = leaf + current_leaves = body_leaves + if not (opening_bracket and closing_bracket and head_leaves): + # If there is no opening or closing_bracket that means the split failed and + # all content is in the tail. Otherwise, if `head_leaves` are empty, it means + # the matching `opening_bracket` wasn't available on `line` anymore. + raise CannotSplit("No brackets found") + + tail_leaves.reverse() + body_leaves.reverse() + head_leaves.reverse() + head = bracket_split_build_line(head_leaves, line, opening_bracket) + body = bracket_split_build_line(body_leaves, line, opening_bracket, is_body=True) + tail = bracket_split_build_line(tail_leaves, line, opening_bracket) + bracket_split_succeeded_or_raise(head, body, tail) + return _RHSResult(head, body, tail, opening_bracket, closing_bracket) + + def bracket_split_succeeded_or_raise(head: Line, body: Line, tail: Line) -> None: """Raise :exc:`CannotSplit` if the last left- or right-hand split failed. diff --git a/src/black/lines.py b/src/black/lines.py index 0d074534def..18eae8dabd2 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -873,3 +873,17 @@ def line_to_string(line: Line) -> str: WARNING: This is known to be computationally expensive. """ return str(line).strip("\n") + + +def is_assignment_line(line: Line) -> bool: + """Returns whether line is an assignment statement.""" + leaves = line.leaves + if not leaves: + return False + parent = leaves[0].parent + if not parent: + return False + grandparent = parent.parent + if not grandparent: + return False + return len(grandparent.children) > 2 and grandparent.children[1].type == token.EQUAL diff --git a/src/black/mode.py b/src/black/mode.py index e2eff2391b1..e72abca6699 100644 --- a/src/black/mode.py +++ b/src/black/mode.py @@ -154,6 +154,7 @@ class Preview(Enum): long_docstring_quotes_on_newline = auto() normalize_docstring_quotes_and_prefixes_properly = auto() one_element_subscript = auto() + prefer_splitting_right_hand_side_of_assignments = auto() remove_block_trailing_newline = auto() remove_redundant_parens = auto() string_processing = auto() diff --git a/tests/data/preview/long_strings__regression.py b/tests/data/preview/long_strings__regression.py index 8b00e76f40e..55cfd9bba82 100644 --- a/tests/data/preview/long_strings__regression.py +++ b/tests/data/preview/long_strings__regression.py @@ -976,9 +976,9 @@ def xxxxxxx_xxxxxx(xxxx): ) -value.__dict__[ - key -] = "test" # set some Thrift field to non-None in the struct aa bb cc dd ee +value.__dict__[key] = ( + "test" # set some Thrift field to non-None in the struct aa bb cc dd ee +) RE_ONE_BACKSLASH = { "asdf_hjkl_jkl": re.compile( diff --git a/tests/data/preview/prefer_rhs_split.py b/tests/data/preview/prefer_rhs_split.py new file mode 100644 index 00000000000..63730397d75 --- /dev/null +++ b/tests/data/preview/prefer_rhs_split.py @@ -0,0 +1,55 @@ +first_item, second_item = ( + some_looooooooong_module.some_looooooooooooooong_function_name( + first_argument, second_argument, third_argument + ) +) + +some_dict["with_a_long_key"] = ( + some_looooooooong_module.some_looooooooooooooong_function_name( + first_argument, second_argument, third_argument + ) +) + +# Make sure it works when the RHS only has one pair of (optional) parens. +first_item, second_item = ( + some_looooooooong_module.SomeClass.some_looooooooooooooong_variable_name +) + +some_dict["with_a_long_key"] = ( + some_looooooooong_module.SomeClass.some_looooooooooooooong_variable_name +) + +# Make sure chaining assignments work. +first_item, second_item, third_item, forth_item = m["everything"] = ( + some_looooooooong_module.some_looooooooooooooong_function_name( + first_argument, second_argument, third_argument + ) +) + +# Make sure when the RHS's first split at the non-optional paren fits, +# we split there instead of the outer RHS optional paren. +first_item, second_item = some_looooooooong_module.some_loooooog_function_name( + first_argument, second_argument, third_argument +) + +( + first_item, + second_item, + third_item, + forth_item, + fifth_item, + last_item_very_loooooong, +) = some_looooooooong_module.some_looooooooooooooong_function_name( + first_argument, second_argument, third_argument +) + +( + first_item, + second_item, + third_item, + forth_item, + fifth_item, + last_item_very_loooooong, +) = everyting = some_loooooog_function_name( + first_argument, second_argument, third_argument +) From f0448a9ff75077230797a6a4ce0054ce020266f8 Mon Sep 17 00:00:00 2001 From: Yilei Yang Date: Fri, 18 Nov 2022 17:35:20 -0800 Subject: [PATCH 2/6] Still chose the split omitting optional parens when it should be preferred. The logic is implemented in the _prefer_split_rhs_oop function. --- src/black/linegen.py | 66 ++++++++++++++----- tests/data/preview/prefer_rhs_split.py | 30 +++++++++ .../preview/prefer_rhs_split_reformatted.py | 38 +++++++++++ 3 files changed, 116 insertions(+), 18 deletions(-) create mode 100644 tests/data/preview/prefer_rhs_split_reformatted.py diff --git a/src/black/linegen.py b/src/black/linegen.py index a0c5cfce558..251a6223e3a 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -647,12 +647,12 @@ def right_hand_split( Note: running this function modifies `bracket_depth` on the leaves of `line`. """ rhs_result = _first_right_hand_split(line, omit=omit) - yield from _maybe_split_without_optional_parens( + yield from _maybe_split_omitting_optional_parens( rhs_result, line, line_length, features=features, omit=omit ) -def _maybe_split_without_optional_parens( +def _maybe_split_omitting_optional_parens( rhs: _RHSResult, line: Line, line_length: int, @@ -677,26 +677,27 @@ def _maybe_split_without_optional_parens( ): omit = {id(rhs.closing_bracket), *omit} try: - alt_rhs = _first_right_hand_split(line, omit=omit) + # The _RHSResult Omitting Optional Parens. + rhs_oop = _first_right_hand_split(line, omit=omit) if not ( Preview.prefer_splitting_right_hand_side_of_assignments in line.mode - # this is an assignment statement - and is_assignment_line(line) + # the split is right after `=` + and len(rhs.head.leaves) >= 2 + and rhs.head.leaves[-2].type == token.EQUAL # the left side of assignement contains brackets and any(leaf.type in BRACKETS for leaf in rhs.head.leaves[:-1]) - # the left side of assignment is short enough - and is_line_short_enough(rhs.head, line_length=line_length) - and ( - # no more splits inside the optional parens (this is done by - # checking whether the resulting first line still has the `=` - # token) - not any(leaf.type == token.EQUAL for leaf in alt_rhs.head.leaves) - # the split from inside the optional parens produces a long line - or not is_line_short_enough(alt_rhs.head, line_length=line_length) - ) + # the left side of assignment is short enough (the -1 is for the ending + # optional paren) + and is_line_short_enough(rhs.head, line_length=line_length-1) + # the left side of assignment won't explode further because of magic + # trailing comma + and rhs.head.magic_trailing_comma is None + # the split by omitting optional parens isn't preferred by some other + # reason + and not _prefer_split_rhs_oop(rhs_oop, line_length=line_length) ): - yield from _maybe_split_without_optional_parens( - alt_rhs, line, line_length, features=features, omit=omit + yield from _maybe_split_omitting_optional_parens( + rhs_oop, line, line_length, features=features, omit=omit ) return @@ -727,6 +728,35 @@ def _maybe_split_without_optional_parens( yield result +def _prefer_split_rhs_oop(rhs_oop: _RHSResult, line_length: int) -> bool: + """ + Returns whether we should prefer the result from a split omitting optional parens. + """ + has_closing_bracket_after_assign = False + for leaf in reversed(rhs_oop.head.leaves): + if leaf.type == token.EQUAL: + break + if leaf.type in CLOSING_BRACKETS: + has_closing_bracket_after_assign = True + break + return ( + # contains matching brackets after the `=` (done by checking there is a + # closing bracket) + has_closing_bracket_after_assign + or ( + # the split is actually from inside the optional parens (done by checking + # the first line still contains the `=`) + any(leaf.type == token.EQUAL for leaf in rhs_oop.head.leaves) + # the first line is short enough + and is_line_short_enough(rhs_oop.head, line_length=line_length) + ) + # contains unsplittable type ignore + or rhs_oop.head.contains_unsplittable_type_ignore() + or rhs_oop.body.contains_unsplittable_type_ignore() + or rhs_oop.tail.contains_unsplittable_type_ignore() + ) + + def _first_right_hand_split( line: Line, omit: Collection[LeafID] = (), @@ -734,7 +764,7 @@ def _first_right_hand_split( """Split the line into head, body, tail starting with the last bracket pair. Note: this function should not have side effects. It's replied upon by - _maybe_split_without_optional_parens to get an opinion whether to prefer + _maybe_split_omitting_optional_parens to get an opinion whether to prefer splitting on the right side of an assignment statement. """ tail_leaves: List[Leaf] = [] diff --git a/tests/data/preview/prefer_rhs_split.py b/tests/data/preview/prefer_rhs_split.py index 63730397d75..5b89113e618 100644 --- a/tests/data/preview/prefer_rhs_split.py +++ b/tests/data/preview/prefer_rhs_split.py @@ -53,3 +53,33 @@ ) = everyting = some_loooooog_function_name( first_argument, second_argument, third_argument ) + + +# Make sure unsplittable type ignore won't be moved. +some_kind_of_table[some_key] = util.some_function( # type: ignore # noqa: E501 + some_arg +).intersection(pk_cols) + +some_kind_of_table[ + some_key +] = lambda obj: obj.some_long_named_method() # type: ignore # noqa: E501 + +some_kind_of_table[ + some_key # type: ignore # noqa: E501 +] = lambda obj: obj.some_long_named_method() + + +# Make when when the left side of assignement plus the opening paren "... = (" is +# exactly line length limit + 1, it won't be split like that. +xxxxxxxxx_yyy_zzzzzzzz[ + xx.xxxxxx(x_yyy_zzzzzz.xxxxx[0]), x_yyy_zzzzzz.xxxxxx(xxxx=1) +] = 1 + + +# Right side of assignment contains un-nested pairs of inner parens. +some_kind_of_instance.some_kind_of_map[a_key] = ( + isinstance(some_var, SomeClass) + and table.something_and_something != table.something_else +) or ( + isinstance(some_other_var, BaseClass) and table.something != table.some_other_thing +) diff --git a/tests/data/preview/prefer_rhs_split_reformatted.py b/tests/data/preview/prefer_rhs_split_reformatted.py new file mode 100644 index 00000000000..781e75be0aa --- /dev/null +++ b/tests/data/preview/prefer_rhs_split_reformatted.py @@ -0,0 +1,38 @@ +# Test cases separate from `prefer_rhs_split.py` that contains unformatted source. + +# Left hand side fits in a single line but will still be exploded by the +# magic trailing comma. +first_value, (m1, m2,), third_value = xxxxxx_yyyyyy_zzzzzz_wwwwww_uuuuuuu_vvvvvvvvvvv( + arg1, + arg2, +) + +# Make when when the left side of assignement plus the opening paren "... = (" is +# exactly line length limit + 1, it won't be split like that. +xxxxxxxxx_yyy_zzzzzzzz[xx.xxxxxx(x_yyy_zzzzzz.xxxxx[0]), x_yyy_zzzzzz.xxxxxx(xxxx=1)] = 1 + + +# output + + +# Test cases separate from `prefer_rhs_split.py` that contains unformatted source. + +# Left hand side fits in a single line but will still be exploded by the +# magic trailing comma. +( + first_value, + ( + m1, + m2, + ), + third_value, +) = xxxxxx_yyyyyy_zzzzzz_wwwwww_uuuuuuu_vvvvvvvvvvv( + arg1, + arg2, +) + +# Make when when the left side of assignement plus the opening paren "... = (" is +# exactly line length limit + 1, it won't be split like that. +xxxxxxxxx_yyy_zzzzzzzz[ + xx.xxxxxx(x_yyy_zzzzzz.xxxxx[0]), x_yyy_zzzzzz.xxxxxx(xxxx=1) +] = 1 From a168c587401b175b813af5a9b84394f65ee2ac82 Mon Sep 17 00:00:00 2001 From: Yilei Yang Date: Fri, 18 Nov 2022 17:36:51 -0800 Subject: [PATCH 3/6] Delete unused function. --- src/black/linegen.py | 1 - src/black/lines.py | 14 -------------- 2 files changed, 15 deletions(-) diff --git a/src/black/linegen.py b/src/black/linegen.py index 251a6223e3a..8b603b332d3 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -19,7 +19,6 @@ append_leaves, can_be_split, can_omit_invisible_parens, - is_assignment_line, is_line_short_enough, line_to_string, ) diff --git a/src/black/lines.py b/src/black/lines.py index 9118c619180..08281bcf370 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -875,17 +875,3 @@ def line_to_string(line: Line) -> str: WARNING: This is known to be computationally expensive. """ return str(line).strip("\n") - - -def is_assignment_line(line: Line) -> bool: - """Returns whether line is an assignment statement.""" - leaves = line.leaves - if not leaves: - return False - parent = leaves[0].parent - if not parent: - return False - grandparent = parent.parent - if not grandparent: - return False - return len(grandparent.children) > 2 and grandparent.children[1].type == token.EQUAL From 5a8ed243fc84b5b7fa9773ce6206d126d57352bb Mon Sep 17 00:00:00 2001 From: Yilei Yang Date: Fri, 18 Nov 2022 17:37:30 -0800 Subject: [PATCH 4/6] Reformat files. --- src/black/linegen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/black/linegen.py b/src/black/linegen.py index 8b603b332d3..c911eaacba2 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -687,7 +687,7 @@ def _maybe_split_omitting_optional_parens( and any(leaf.type in BRACKETS for leaf in rhs.head.leaves[:-1]) # the left side of assignment is short enough (the -1 is for the ending # optional paren) - and is_line_short_enough(rhs.head, line_length=line_length-1) + and is_line_short_enough(rhs.head, line_length=line_length - 1) # the left side of assignment won't explode further because of magic # trailing comma and rhs.head.magic_trailing_comma is None From c840aa5754dbf1ff243e038d9977d993938642b1 Mon Sep 17 00:00:00 2001 From: Yilei Yang Date: Fri, 18 Nov 2022 17:39:32 -0800 Subject: [PATCH 5/6] Re-order functions to make diff more simple. --- src/black/linegen.py | 96 ++++++++++++++++++++++---------------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/src/black/linegen.py b/src/black/linegen.py index c911eaacba2..40395ed3ca6 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -651,6 +651,54 @@ def right_hand_split( ) +def _first_right_hand_split( + line: Line, + omit: Collection[LeafID] = (), +) -> _RHSResult: + """Split the line into head, body, tail starting with the last bracket pair. + + Note: this function should not have side effects. It's replied upon by + _maybe_split_omitting_optional_parens to get an opinion whether to prefer + splitting on the right side of an assignment statement. + """ + tail_leaves: List[Leaf] = [] + body_leaves: List[Leaf] = [] + head_leaves: List[Leaf] = [] + current_leaves = tail_leaves + opening_bracket: Optional[Leaf] = None + closing_bracket: Optional[Leaf] = None + for leaf in reversed(line.leaves): + if current_leaves is body_leaves: + if leaf is opening_bracket: + current_leaves = head_leaves if body_leaves else tail_leaves + current_leaves.append(leaf) + if current_leaves is tail_leaves: + if leaf.type in CLOSING_BRACKETS and id(leaf) not in omit: + opening_bracket = leaf.opening_bracket + closing_bracket = leaf + current_leaves = body_leaves + if not (opening_bracket and closing_bracket and head_leaves): + # If there is no opening or closing_bracket that means the split failed and + # all content is in the tail. Otherwise, if `head_leaves` are empty, it means + # the matching `opening_bracket` wasn't available on `line` anymore. + raise CannotSplit("No brackets found") + + tail_leaves.reverse() + body_leaves.reverse() + head_leaves.reverse() + head = bracket_split_build_line( + head_leaves, line, opening_bracket, component=_BracketSplitComponent.head + ) + body = bracket_split_build_line( + body_leaves, line, opening_bracket, component=_BracketSplitComponent.body + ) + tail = bracket_split_build_line( + tail_leaves, line, opening_bracket, component=_BracketSplitComponent.tail + ) + bracket_split_succeeded_or_raise(head, body, tail) + return _RHSResult(head, body, tail, opening_bracket, closing_bracket) + + def _maybe_split_omitting_optional_parens( rhs: _RHSResult, line: Line, @@ -756,54 +804,6 @@ def _prefer_split_rhs_oop(rhs_oop: _RHSResult, line_length: int) -> bool: ) -def _first_right_hand_split( - line: Line, - omit: Collection[LeafID] = (), -) -> _RHSResult: - """Split the line into head, body, tail starting with the last bracket pair. - - Note: this function should not have side effects. It's replied upon by - _maybe_split_omitting_optional_parens to get an opinion whether to prefer - splitting on the right side of an assignment statement. - """ - tail_leaves: List[Leaf] = [] - body_leaves: List[Leaf] = [] - head_leaves: List[Leaf] = [] - current_leaves = tail_leaves - opening_bracket: Optional[Leaf] = None - closing_bracket: Optional[Leaf] = None - for leaf in reversed(line.leaves): - if current_leaves is body_leaves: - if leaf is opening_bracket: - current_leaves = head_leaves if body_leaves else tail_leaves - current_leaves.append(leaf) - if current_leaves is tail_leaves: - if leaf.type in CLOSING_BRACKETS and id(leaf) not in omit: - opening_bracket = leaf.opening_bracket - closing_bracket = leaf - current_leaves = body_leaves - if not (opening_bracket and closing_bracket and head_leaves): - # If there is no opening or closing_bracket that means the split failed and - # all content is in the tail. Otherwise, if `head_leaves` are empty, it means - # the matching `opening_bracket` wasn't available on `line` anymore. - raise CannotSplit("No brackets found") - - tail_leaves.reverse() - body_leaves.reverse() - head_leaves.reverse() - head = bracket_split_build_line( - head_leaves, line, opening_bracket, component=_BracketSplitComponent.head - ) - body = bracket_split_build_line( - body_leaves, line, opening_bracket, component=_BracketSplitComponent.body - ) - tail = bracket_split_build_line( - tail_leaves, line, opening_bracket, component=_BracketSplitComponent.tail - ) - bracket_split_succeeded_or_raise(head, body, tail) - return _RHSResult(head, body, tail, opening_bracket, closing_bracket) - - def bracket_split_succeeded_or_raise(head: Line, body: Line, tail: Line) -> None: """Raise :exc:`CannotSplit` if the last left- or right-hand split failed. From bcc807595b8a29bd7a4eeb565433200e0647377c Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 15 Dec 2022 08:16:04 -0800 Subject: [PATCH 6/6] Update src/black/linegen.py --- src/black/linegen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/black/linegen.py b/src/black/linegen.py index 80f17095ff3..d00251d4e18 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -656,7 +656,7 @@ def _first_right_hand_split( ) -> _RHSResult: """Split the line into head, body, tail starting with the last bracket pair. - Note: this function should not have side effects. It's replied upon by + Note: this function should not have side effects. It's relied upon by _maybe_split_omitting_optional_parens to get an opinion whether to prefer splitting on the right side of an assignment statement. """