diff --git a/lib/elixir/src/elixir_parser.yrl b/lib/elixir/src/elixir_parser.yrl index d4d563b20e..0a54d5ec7f 100644 --- a/lib/elixir/src/elixir_parser.yrl +++ b/lib/elixir/src/elixir_parser.yrl @@ -13,7 +13,6 @@ Nonterminals expr container_expr block_expr access_expr no_parens_expr no_parens_zero_expr no_parens_one_expr no_parens_one_ambig_expr bracket_expr bracket_at_expr bracket_arg matched_expr unmatched_expr sub_matched_expr - at_stab_expr unmatched_op_expr matched_op_expr no_parens_op_expr no_parens_many_expr comp_op_eol at_op_eol unary_op_eol and_op_eol or_op_eol capture_op_eol dual_op_eol mult_op_eol power_op_eol concat_op_eol xor_op_eol pipe_op_eol @@ -113,7 +112,6 @@ expr_list -> expr_list eoe expr : ['$3' | annotate_eoe('$2', '$1')]. expr -> matched_expr : '$1'. expr -> no_parens_expr : '$1'. expr -> unmatched_expr : '$1'. -expr -> at_stab_expr : '$1'. %% In Elixir we have three main call syntaxes: with parentheses, %% without parentheses and with do blocks. They are represented @@ -251,13 +249,6 @@ no_parens_op_expr -> arrow_op_eol no_parens_expr : warn_pipe('$1', '$2'), {'$1', %% Allow when (and only when) with keywords no_parens_op_expr -> when_op_eol call_args_no_parens_kw : {'$1', '$2'}. -at_stab_expr -> at_op_eol no_parens_one_expr stab_op_eol expr : - build_at_stab('$1', '$2', {'$3', '$4'}). -at_stab_expr -> at_op_eol no_parens_one_ambig_expr stab_op_eol expr : - build_at_stab('$1', '$2', {'$3', '$4'}). -at_stab_expr -> at_op_eol no_parens_many_expr stab_op_eol expr : - build_at_stab('$1', '$2', {'$3', '$4'}). - no_parens_one_ambig_expr -> dot_op_identifier call_args_no_parens_ambig : build_no_parens('$1', '$2'). no_parens_one_ambig_expr -> dot_identifier call_args_no_parens_ambig : build_no_parens('$1', '$2'). @@ -364,6 +355,12 @@ stab_expr -> empty_paren stab_op_eol_and_expr : build_op_with_meta([], '$2', parens_meta('$1')). stab_expr -> empty_paren when_op expr stab_op_eol_and_expr : build_op_with_meta([{'when', meta_from_token('$2'), ['$3']}], '$4', parens_meta('$1')). +stab_expr -> at_op_eol no_parens_one_expr stab_op_eol_and_expr : + build_at_stab('$1', '$2', '$3'). +stab_expr -> at_op_eol no_parens_one_ambig_expr stab_op_eol_and_expr : + build_at_stab('$1', '$2', '$3'). +stab_expr -> at_op_eol no_parens_many_expr stab_op_eol_and_expr : + build_at_stab('$1', '$2', '$3'). stab_expr -> call_args_no_parens_all stab_op_eol_and_expr : build_op(unwrap_when(unwrap_splice('$1')), '$2'). stab_expr -> stab_parens_many stab_op_eol_and_expr : @@ -781,11 +778,16 @@ build_unary_op({_Kind, {Line, Column, _}, '//'}, Expr) -> {'/', [{line, Line} | Outer], [{'/', [{line, Line} | Inner], nil}, Expr]}; build_unary_op({_Kind, Location, Op}, Expr) -> - {Op, meta_from_location(Location), [Expr]}. + {Op, meta_from_location(Location), [normalize_unary_op(Op, Expr)]}. build_at_stab(At, {Call, Meta, Args}, {Op, Right}) -> build_unary_op(At, {Call, Meta, [build_op(Args, Op, Right)]}). +normalize_unary_op('@', {Call, Meta, [[{'->', _, [_, _]} = Stab]]}) when is_atom(Call), is_list(Meta) -> + {Call, Meta, [Stab]}; +normalize_unary_op(_, Expr) -> + Expr. + build_nullary_op({_Kind, Location, Op}) -> {Op, meta_from_location(Location), []}. diff --git a/lib/elixir/test/elixir/kernel/typing_syntax_test.exs b/lib/elixir/test/elixir/kernel/typing_syntax_test.exs index 4f8becbf70..e7e7c20248 100644 --- a/lib/elixir/test/elixir/kernel/typing_syntax_test.exs +++ b/lib/elixir/test/elixir/kernel/typing_syntax_test.exs @@ -7,8 +7,14 @@ defmodule Kernel.TypingSyntaxTest do use ExUnit.Case, async: true describe "@ with -> in quote" do - test "wraps the arrow as the attribute call argument at the root" do - assert parse_quote("@sig foo -> bar") == + test "does not parse arrows at the root" do + assert_raise SyntaxError, ~r/syntax error before: '->'/, fn -> + parse_quote("@sig foo -> bar") + end + end + + test "wraps parenthesized arrows as the attribute call argument at the root" do + assert parse_quote("(@sig foo -> bar)") == {:@, [line: 1], [ {:sig, [line: 1], @@ -16,8 +22,14 @@ defmodule Kernel.TypingSyntaxTest do ]} end - test "wraps multi-argument arrows as the attribute call argument at the root" do - assert parse_quote("@sig foo, bar -> baz") == + test "does not parse multi-argument arrows at the root" do + assert_raise SyntaxError, ~r/syntax error before: '->'/, fn -> + parse_quote("@sig foo, bar -> baz") + end + end + + test "wraps parenthesized multi-argument arrows as the attribute call argument at the root" do + assert parse_quote("(@sig foo, bar -> baz)") == {:@, [line: 1], [ {:sig, [line: 1], @@ -159,14 +171,29 @@ defmodule Kernel.TypingSyntaxTest do [ do: {:@, [line: 2], - [ - {:sig, [line: 2], [[{:->, [line: 2], [[], {:bar, [line: 2], nil}]}]]} - ]} + [{:sig, [line: 2], [{:->, [line: 2], [[], {:bar, [line: 2], nil}]}]}]} ] ]} end - test "allows explicit parenthesized arrows with arguments as attribute call arguments" do + test "normalizes parenthesized arrows to the same AST as arrow arguments" do + assert parse_quote(""" + quote do + pre + @sig foo -> bar + post + end + """) == + parse_quote(""" + quote do + pre + @sig (foo -> bar) + post + end + """) + end + + test "normalizes explicit parenthesized arrows with arguments as attribute call arguments" do assert parse_quote(""" quote do @sig (foo -> bar) @@ -179,11 +206,7 @@ defmodule Kernel.TypingSyntaxTest do {:@, [line: 2], [ {:sig, [line: 2], - [ - [ - {:->, [line: 2], [[{:foo, [line: 2], nil}], {:bar, [line: 2], nil}]} - ] - ]} + [{:->, [line: 2], [[{:foo, [line: 2], nil}], {:bar, [line: 2], nil}]}]} ]} ] ]}