From d9cf285d7104db5a61607afe2ae4322220681a51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 8 Jul 2024 13:23:14 -0300 Subject: [PATCH] Enforce parens when formatting on more operators (#13711) * On bitwise operators * On right precedence operators which are semantically non-associative (such as -- and potentially ---) --- lib/elixir/lib/code/formatter.ex | 36 +++++++++++-------- lib/elixir/src/elixir_parser.yrl | 2 +- .../elixir/code_formatter/operators_test.exs | 5 ++- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/lib/elixir/lib/code/formatter.ex b/lib/elixir/lib/code/formatter.ex index edee3ae9bb2..e5b0cc9e200 100644 --- a/lib/elixir/lib/code/formatter.ex +++ b/lib/elixir/lib/code/formatter.ex @@ -31,30 +31,39 @@ defmodule Code.Formatter do @right_new_line_before_binary_operators [:|, :when] # Operators that are logical cannot be mixed without parens - @required_parens_logical_binary_operands [:||, :|||, :or, :&&, :&&&, :and] + @required_parens_logical_binary_operands [:||, :or, :&&, :and] # Operators with next break fits @next_break_fits_operators [:<-, :==, :!=, :=~, :===, :!==, :<, :>, :<=, :>=, :=, :"::"] - # Operators that always require parens on operands when they are the parent + # Operators that always require parens even + # when they are their own parents as they are not semantically associative + @required_parens_even_when_parent [:--, :---] + + # Operators that always require parens on operands + # when they are the parent of another operator with a difference precedence + # Most operators are listed, except comparison, arithmetic, and low precedence @required_parens_on_binary_operands [ - :|>, + :|||, + :&&&, :<<<, :>>>, + :|>, :<~, :~>, :<<~, :~>>, :<~>, :"<|>", - :"^^^", - :+++, - :---, :in, + :"^^^", + :"//", :++, :--, - :.., - :<> + :+++, + :---, + :<>, + :.. ] @locals_without_parens [ @@ -785,14 +794,13 @@ defmodule Code.Formatter do op_string = Atom.to_string(op) cond do - # If the operator has the same precedence as the parent and is on - # the correct side, we respect the nesting rule to avoid multiple - # nestings. This only applies for left associativity or same operator. - parent_prec == prec and parent_assoc == side and (side == :left or op == parent_op) -> + # If we have the same operator and it is in the correct side, + # we don't add parens unless it is explicitly required. + parent_assoc == side and op == parent_op and op not in @required_parens_even_when_parent -> binary_op_to_algebra(op, op_string, meta, left, right, context, state, nesting) - # If the parent requires parens or the precedence is inverted or - # it is in the wrong side, then we *need* parenthesis. + # If the operator requires parens (most of them do) or we are mixing logical operators + # or the precedence is inverted or it is in the wrong side, then we *need* parenthesis. (parent_op in @required_parens_on_binary_operands and op not in @no_space_binary_operators) or (op in @required_parens_logical_binary_operands and parent_op in @required_parens_logical_binary_operands) or parent_prec > prec or diff --git a/lib/elixir/src/elixir_parser.yrl b/lib/elixir/src/elixir_parser.yrl index b5cf9df191b..63d35540143 100644 --- a/lib/elixir/src/elixir_parser.yrl +++ b/lib/elixir/src/elixir_parser.yrl @@ -71,7 +71,7 @@ Left 120 or_op_eol. %% ||, |||, or Left 130 and_op_eol. %% &&, &&&, and Left 140 comp_op_eol. %% ==, !=, =~, ===, !== Left 150 rel_op_eol. %% <, >, <=, >= -Left 160 arrow_op_eol. %% |>, <<<, >>>, <<~, ~>>, <~, ~>, <~>, <|> +Left 160 arrow_op_eol. %% <<<, >>>, |>, <<~, ~>>, <~, ~>, <~>, <|> Left 170 in_op_eol. %% in, not in Left 180 xor_op_eol. %% ^^^ Right 190 ternary_op_eol. %% // diff --git a/lib/elixir/test/elixir/code_formatter/operators_test.exs b/lib/elixir/test/elixir/code_formatter/operators_test.exs index 69410ab2f61..e667bce0700 100644 --- a/lib/elixir/test/elixir/code_formatter/operators_test.exs +++ b/lib/elixir/test/elixir/code_formatter/operators_test.exs @@ -170,7 +170,7 @@ defmodule Code.Formatter.OperatorsTest do end test "bitwise precedence" do - assert_format "(crc >>> 8) ||| byte", "crc >>> 8 ||| byte" + assert_same "(crc >>> 8) ||| byte" assert_same "crc >>> (8 ||| byte)" end end @@ -423,6 +423,9 @@ defmodule Code.Formatter.OperatorsTest do test "with multiple of the same entry and right associative" do assert_same "foo ++ bar ++ baz" + assert_format "foo -- bar -- baz", "foo -- (bar -- baz)" + assert_same "foo +++ bar +++ baz" + assert_format "foo --- bar --- baz", "foo --- (bar --- baz)" bad = "a ++ b ++ c"