From 647542e6cbcd965c046c8657f217d276c4d54ab4 Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Fri, 28 Jan 2022 02:34:39 -0800 Subject: [PATCH 1/3] Fix arithmetic stability issue It turns out "simple_stmt" isn't that simple: it can contain multiple statements separated by semicolons. Invisible parenthesis logic for arithmetic expressions only looked at the first child of simple_stmt. This causes instability in the presence of semicolons, since the next run through the statement following the semicolon will be the first child of another simple_stmt. I believe this along with #2572 fix the known stability issues. --- src/black/linegen.py | 10 +++++++--- src/black/nodes.py | 7 +++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/black/linegen.py b/src/black/linegen.py index ac60ed1986d..ca121ada3ec 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -7,7 +7,7 @@ from black.nodes import WHITESPACE, RARROW, STATEMENT, STANDALONE_COMMENT from black.nodes import ASSIGNMENTS, OPENING_BRACKETS, CLOSING_BRACKETS -from black.nodes import Visitor, syms, first_child_is_arith, ensure_visible +from black.nodes import Visitor, syms, is_arith_like, ensure_visible from black.nodes import is_docstring, is_empty_tuple, is_one_tuple, is_one_tuple_between from black.nodes import is_name_token, is_lpar_token, is_rpar_token from black.nodes import is_walrus_assignment, is_yield, is_vararg, is_multiline_string @@ -156,8 +156,12 @@ def visit_suite(self, node: Node) -> Iterator[Line]: def visit_simple_stmt(self, node: Node) -> Iterator[Line]: """Visit a statement without nested statements.""" - if first_child_is_arith(node): - wrap_in_parentheses(node, node.children[0], visible=False) + prev_type = None + for child in node.children: + if (prev_type is None or prev_type == token.SEMI) and is_arith_like(child): + wrap_in_parentheses(node, child, visible=False) + prev_type = child.type + is_suite_like = node.parent and node.parent.type in STATEMENT if is_suite_like: if self.mode.is_pyi and is_stub_body(node): diff --git a/src/black/nodes.py b/src/black/nodes.py index 74dfa896295..aff0419fa7c 100644 --- a/src/black/nodes.py +++ b/src/black/nodes.py @@ -536,15 +536,14 @@ def first_leaf_column(node: Node) -> Optional[int]: return None -def first_child_is_arith(node: Node) -> bool: - """Whether first child is an arithmetic or a binary arithmetic expression""" - expr_types = { +def is_arith_like(node: Node) -> bool: + """Whether node is an arithmetic or a binary arithmetic expression""" + return node.type in { syms.arith_expr, syms.shift_expr, syms.xor_expr, syms.and_expr, } - return bool(node.children and node.children[0].type in expr_types) def is_docstring(leaf: Leaf) -> bool: From 16cbced7ec253d08a54d7815c7548aaba38a2c79 Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Fri, 28 Jan 2022 02:39:27 -0800 Subject: [PATCH 2/3] changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 0dc4952f069..704edbe63e3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -43,6 +43,7 @@ - Work around bug that causes unstable formatting in some cases in the presence of the magic trailing comma (#2807) - Deprecate the `black-primer` tool (#2809) +- Fix unstable formatting with semicolons and arithmetic expressions (#2817) ### Packaging From ae71c47f9f3b4169f59d8068b6a9170ed0215206 Mon Sep 17 00:00:00 2001 From: hauntsaninja <> Date: Fri, 28 Jan 2022 02:43:25 -0800 Subject: [PATCH 3/3] types --- src/black/linegen.py | 2 +- src/black/nodes.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/black/linegen.py b/src/black/linegen.py index ca121ada3ec..081ffdc0e87 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -156,7 +156,7 @@ def visit_suite(self, node: Node) -> Iterator[Line]: def visit_simple_stmt(self, node: Node) -> Iterator[Line]: """Visit a statement without nested statements.""" - prev_type = None + prev_type: Optional[int] = None for child in node.children: if (prev_type is None or prev_type == token.SEMI) and is_arith_like(child): wrap_in_parentheses(node, child, visible=False) diff --git a/src/black/nodes.py b/src/black/nodes.py index aff0419fa7c..d30e38baeb0 100644 --- a/src/black/nodes.py +++ b/src/black/nodes.py @@ -536,7 +536,7 @@ def first_leaf_column(node: Node) -> Optional[int]: return None -def is_arith_like(node: Node) -> bool: +def is_arith_like(node: LN) -> bool: """Whether node is an arithmetic or a binary arithmetic expression""" return node.type in { syms.arith_expr,