Skip to content

Enforce parens when formatting on more operators #13711

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

Merged
merged 1 commit into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading