Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix long case blocks not split into multiple lines #4024

Merged
merged 13 commits into from Nov 7, 2023
2 changes: 2 additions & 0 deletions CHANGES.md
Expand Up @@ -19,6 +19,8 @@
indented less (#3964)
- Multiline list and dict unpacking as the sole argument to a function is now also
indented less (#3992)
- Fix a bug that long `case` blocks will not be split into multiple lines, while now
also enabled general trailing comma rules on `case` blocks (#4024)
JelleZijlstra marked this conversation as resolved.
Show resolved Hide resolved

### Configuration

Expand Down
19 changes: 18 additions & 1 deletion src/black/linegen.py
Expand Up @@ -1229,7 +1229,7 @@ def normalize_prefix(leaf: Leaf, *, inside_brackets: bool) -> None:
leaf.prefix = ""


def normalize_invisible_parens(
def normalize_invisible_parens( # noqa: C901
node: Node, parens_after: Set[str], *, mode: Mode, features: Collection[Feature]
) -> None:
"""Make existing optional parentheses invisible or create new ones.
Expand Down Expand Up @@ -1260,6 +1260,13 @@ def normalize_invisible_parens(
child, parens_after=parens_after, mode=mode, features=features
)

# Fixes a bug where invisible parens are not properly wrapped around
# case blocks.
if isinstance(child, Node) and child.type == syms.case_block:
rdrll marked this conversation as resolved.
Show resolved Hide resolved
normalize_invisible_parens(
child, parens_after={"case"}, mode=mode, features=features
)

# Add parentheses around long tuple unpacking in assignments.
if (
index == 0
Expand Down Expand Up @@ -1305,6 +1312,16 @@ def normalize_invisible_parens(
# invisible parentheses to work more precisely.
continue

elif (
isinstance(child, Leaf)
and child.next_sibling is not None
and child.next_sibling.type == token.COLON
and child.value == "case"
):
# A special patch for "case case:" scenario, the second occurrence
# of case will be not parsed as a Python keyword.
break

elif not (isinstance(child, Leaf) and is_multiline_string(child)):
wrap_in_parentheses(node, child, visible=False)

Expand Down
19 changes: 3 additions & 16 deletions tests/data/cases/pattern_matching_extras.py
Expand Up @@ -30,22 +30,6 @@ def func(match: case, case: match) -> case:
...


match maybe, multiple:
case perhaps, 5:
pass
case perhaps, 6,:
pass


match more := (than, one), indeed,:
case _, (5, 6):
pass
case [[5], (6)], [7],:
pass
case _:
pass


match a, *b, c:
case [*_]:
assert "seq" == _
Expand Down Expand Up @@ -74,6 +58,9 @@ def func(match: case, case: match) -> case:
case case:
pass

case something:
pass


match match:
case case:
Expand Down
34 changes: 34 additions & 0 deletions tests/data/cases/pattern_matching_long.py
@@ -0,0 +1,34 @@
# flags: --minimum-version=3.10
match x:
case "abcd" | "abcd" | "abcd" :
pass
case "abcd" | "abcd" | "abcd" | "abcd" | "abcd" | "abcd" | "abcd" | "abcd" | "abcd" | "abcd" | "abcd" | "abcd" | "abcd" | "abcd" | "abcd":
pass
case xxxxxxxxxxxxxxxxxxxxxxx:
pass

# output

match x:
case "abcd" | "abcd" | "abcd":
pass
case (
"abcd"
| "abcd"
| "abcd"
| "abcd"
| "abcd"
| "abcd"
| "abcd"
| "abcd"
| "abcd"
| "abcd"
| "abcd"
| "abcd"
| "abcd"
| "abcd"
| "abcd"
):
pass
case xxxxxxxxxxxxxxxxxxxxxxx:
pass
39 changes: 39 additions & 0 deletions tests/data/cases/pattern_matching_trailing_comma.py
@@ -0,0 +1,39 @@
# flags: --minimum-version=3.10
match maybe, multiple:
case perhaps, 5:
pass
case perhaps, 6,:
pass


match more := (than, one), indeed,:
case _, (5, 6):
pass
case [[5], (6)], [7],:
pass
case _:
pass


# output

match maybe, multiple:
case perhaps, 5:
pass
case (
perhaps,
6,
):
pass


match more := (than, one), indeed,:
case _, (5, 6):
pass
case (
[[5], (6)],
[7],
):
pass
case _:
pass