Skip to content

Commit

Permalink
Enforce parens when formatting on more operators (#13711)
Browse files Browse the repository at this point in the history
* On bitwise operators

* On right precedence operators which are semantically
  non-associative (such as -- and potentially ---)
  • Loading branch information
josevalim authored Jul 8, 2024
1 parent 18bbfed commit d9cf285
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 16 deletions.
36 changes: 22 additions & 14 deletions lib/elixir/lib/code/formatter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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 [
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/elixir/src/elixir_parser.yrl
Original file line number Diff line number Diff line change
Expand Up @@ -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. %% //
Expand Down
5 changes: 4 additions & 1 deletion lib/elixir/test/elixir/code_formatter/operators_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"

Expand Down

0 comments on commit d9cf285

Please sign in to comment.