Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42fb7d2be3 | ||
|
|
332f2ca791 | ||
|
|
082e08664e |
@@ -355,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 :
|
||||
@@ -772,7 +778,15 @@ 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), []}.
|
||||
@@ -1110,6 +1124,9 @@ check_stab([{'->', _, [_, _]}], _) -> stab;
|
||||
check_stab([], none) -> block;
|
||||
check_stab([_], none) -> block;
|
||||
check_stab([_], Meta) -> error_invalid_stab(Meta);
|
||||
check_stab([{'->', Meta, [[{'@', _, [{Call, CallMeta, nil}]}], _]} | T], _)
|
||||
when is_atom(Call) andalso is_list(CallMeta) ->
|
||||
check_stab(T, {ambiguous_at_stab, Meta});
|
||||
check_stab([{'->', Meta, [_, _]} | T], _) -> check_stab(T, Meta);
|
||||
check_stab([_ | T], MaybeMeta) -> check_stab(T, MaybeMeta).
|
||||
|
||||
@@ -1188,6 +1205,11 @@ return_error({Line, Column, _}, ErrorMessage, ErrorToken) ->
|
||||
return_error_with_meta(Meta, ErrorMessage, ErrorToken) ->
|
||||
return_error(Meta, [ErrorMessage, ErrorToken]).
|
||||
|
||||
error_invalid_stab({ambiguous_at_stab, MetaStab}) ->
|
||||
return_error_with_meta(MetaStab,
|
||||
"ambiguous use of @ with ->. If you want a nullary signature, write @sig () -> bar or @sig (-> bar). "
|
||||
"Syntax error before: ", "'->'");
|
||||
|
||||
error_invalid_stab(MetaStab) ->
|
||||
return_error_with_meta(MetaStab,
|
||||
"unexpected operator ->. If you want to define multiple clauses, the first expression must use ->. "
|
||||
|
||||
@@ -0,0 +1,390 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: 2021 The Elixir Team
|
||||
|
||||
Code.require_file("../test_helper.exs", __DIR__)
|
||||
|
||||
defmodule Kernel.TypingSyntaxTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
describe "@ with -> in quote" do
|
||||
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],
|
||||
[{:->, [line: 1], [[{:foo, [line: 1], nil}], {:bar, [line: 1], nil}]}]}
|
||||
]}
|
||||
end
|
||||
|
||||
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],
|
||||
[
|
||||
{:->, [line: 1],
|
||||
[[{:foo, [line: 1], nil}, {:bar, [line: 1], nil}], {:baz, [line: 1], nil}]}
|
||||
]}
|
||||
]}
|
||||
end
|
||||
|
||||
test "does not parse nullary arrows at the root" do
|
||||
assert_raise SyntaxError, ~r/syntax error before: '->'/, fn ->
|
||||
parse_quote("@sig -> bar")
|
||||
end
|
||||
end
|
||||
|
||||
test "wraps the arrow as the attribute call argument" do
|
||||
assert parse_quote("""
|
||||
quote do
|
||||
pre
|
||||
@sig foo -> bar
|
||||
post
|
||||
end
|
||||
""") ==
|
||||
{:quote, [line: 1],
|
||||
[
|
||||
[
|
||||
do:
|
||||
{:__block__, [line: 1],
|
||||
[
|
||||
{:pre, [line: 2], nil},
|
||||
{:@, [line: 3],
|
||||
[
|
||||
{:sig, [line: 3],
|
||||
[
|
||||
{:->, [line: 3],
|
||||
[[{:foo, [line: 3], nil}], {:bar, [line: 3], nil}]}
|
||||
]}
|
||||
]},
|
||||
{:post, [line: 4], nil}
|
||||
]}
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "wraps multi-argument arrows as the attribute call argument" do
|
||||
assert parse_quote("""
|
||||
quote do
|
||||
pre
|
||||
@sig foo, bar -> baz
|
||||
post
|
||||
end
|
||||
""") ==
|
||||
{:quote, [line: 1],
|
||||
[
|
||||
[
|
||||
do:
|
||||
{:__block__, [line: 1],
|
||||
[
|
||||
{:pre, [line: 2], nil},
|
||||
{:@, [line: 3],
|
||||
[
|
||||
{:sig, [line: 3],
|
||||
[
|
||||
{:->, [line: 3],
|
||||
[
|
||||
[{:foo, [line: 3], nil}, {:bar, [line: 3], nil}],
|
||||
{:baz, [line: 3], nil}
|
||||
]}
|
||||
]}
|
||||
]},
|
||||
{:post, [line: 4], nil}
|
||||
]}
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "raises on ambiguous nullary arrows" do
|
||||
assert_raise SyntaxError, ~r/ambiguous use of @ with ->/, fn ->
|
||||
parse_quote("""
|
||||
quote do
|
||||
pre
|
||||
@sig -> bar
|
||||
post
|
||||
end
|
||||
""")
|
||||
end
|
||||
end
|
||||
|
||||
test "keeps nullary arrows as do-block stabs without surrounding expressions" do
|
||||
assert parse_quote("""
|
||||
quote do
|
||||
@sig -> bar
|
||||
end
|
||||
""") ==
|
||||
{:quote, [line: 1],
|
||||
[
|
||||
[
|
||||
do: [
|
||||
{:->, [line: 2],
|
||||
[[{:@, [line: 2], [{:sig, [line: 2], nil}]}], {:bar, [line: 2], nil}]}
|
||||
]
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "wraps explicit empty parentheses as the attribute call argument" do
|
||||
assert parse_quote(
|
||||
"""
|
||||
quote do
|
||||
@sig () -> bar
|
||||
end
|
||||
""",
|
||||
emit_warnings: false
|
||||
) ==
|
||||
{:quote, [line: 1],
|
||||
[
|
||||
[
|
||||
do:
|
||||
{:@, [line: 2],
|
||||
[
|
||||
{:sig, [line: 2],
|
||||
[
|
||||
{:->, [line: 2], [[{:__block__, [], []}], {:bar, [line: 2], nil}]}
|
||||
]}
|
||||
]}
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "allows explicit parenthesized arrows as attribute call arguments" do
|
||||
assert parse_quote("""
|
||||
quote do
|
||||
@sig (-> bar)
|
||||
end
|
||||
""") ==
|
||||
{:quote, [line: 1],
|
||||
[
|
||||
[
|
||||
do:
|
||||
{:@, [line: 2],
|
||||
[{:sig, [line: 2], [{:->, [line: 2], [[], {:bar, [line: 2], nil}]}]}]}
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
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)
|
||||
end
|
||||
""") ==
|
||||
{:quote, [line: 1],
|
||||
[
|
||||
[
|
||||
do:
|
||||
{:@, [line: 2],
|
||||
[
|
||||
{:sig, [line: 2],
|
||||
[{:->, [line: 2], [[{:foo, [line: 2], nil}], {:bar, [line: 2], nil}]}]}
|
||||
]}
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "does not allow arrows inside parenthesized attribute calls" do
|
||||
assert_raise SyntaxError, ~r/syntax error before: '->'/, fn ->
|
||||
parse_quote("""
|
||||
quote do
|
||||
@sig(foo -> bar)
|
||||
end
|
||||
""")
|
||||
end
|
||||
end
|
||||
|
||||
test "applies to attributes other than sig" do
|
||||
assert parse_quote("""
|
||||
quote do
|
||||
@foo bar -> baz
|
||||
end
|
||||
""") ==
|
||||
{:quote, [line: 1],
|
||||
[
|
||||
[
|
||||
do:
|
||||
{:@, [line: 2],
|
||||
[
|
||||
{:foo, [line: 2],
|
||||
[{:->, [line: 2], [[{:bar, [line: 2], nil}], {:baz, [line: 2], nil}]}]}
|
||||
]}
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "does not apply to regular calls without @" do
|
||||
assert_raise SyntaxError, ~r/unexpected operator ->/, fn ->
|
||||
parse_quote("""
|
||||
quote do
|
||||
pre
|
||||
omg bar -> baz
|
||||
post
|
||||
end
|
||||
""")
|
||||
end
|
||||
end
|
||||
|
||||
test "does not apply to parenthesized attribute calls with pre/post expressions" do
|
||||
assert_raise SyntaxError, ~r/unexpected operator ->/, fn ->
|
||||
parse_quote("""
|
||||
quote do
|
||||
pre
|
||||
(@sig foo, bar) -> baz
|
||||
post
|
||||
end
|
||||
""")
|
||||
end
|
||||
end
|
||||
|
||||
test "keeps parenthesized attribute calls as do-block stabs without pre/post expressions" do
|
||||
assert parse_quote("""
|
||||
quote do
|
||||
(@sig foo, bar) -> baz
|
||||
end
|
||||
""") ==
|
||||
{:quote, [line: 1],
|
||||
[
|
||||
[
|
||||
do: [
|
||||
{:->, [line: 2],
|
||||
[
|
||||
[
|
||||
{:@, [line: 2],
|
||||
[
|
||||
{:sig, [line: 2], [{:foo, [line: 2], nil}, {:bar, [line: 2], nil}]}
|
||||
]}
|
||||
],
|
||||
{:baz, [line: 2], nil}
|
||||
]}
|
||||
]
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "keeps ambiguous nullary arrows as separate do-block stabs inside stabs" do
|
||||
assert parse_quote("""
|
||||
quote do
|
||||
subject ->
|
||||
setup
|
||||
@sig -> result
|
||||
end
|
||||
""") ==
|
||||
{:quote, [line: 1],
|
||||
[
|
||||
[
|
||||
do: [
|
||||
{:->, [line: 2], [[{:subject, [line: 2], nil}], {:setup, [line: 3], nil}]},
|
||||
{:->, [line: 4],
|
||||
[
|
||||
[{:@, [line: 4], [{:sig, [line: 4], nil}]}],
|
||||
{:result, [line: 4], nil}
|
||||
]}
|
||||
]
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "keeps explicit empty parentheses inside stab bodies" do
|
||||
assert parse_quote(
|
||||
"""
|
||||
quote do
|
||||
subject ->
|
||||
setup
|
||||
@sig () -> result
|
||||
end
|
||||
""",
|
||||
emit_warnings: false
|
||||
) ==
|
||||
{:quote, [line: 1],
|
||||
[
|
||||
[
|
||||
do: [
|
||||
{:->, [line: 2],
|
||||
[
|
||||
[{:subject, [line: 2], nil}],
|
||||
{:__block__, [],
|
||||
[
|
||||
{:setup, [line: 3], nil},
|
||||
{:@, [line: 4],
|
||||
[
|
||||
{:sig, [line: 4],
|
||||
[
|
||||
{:->, [line: 4],
|
||||
[[{:__block__, [], []}], {:result, [line: 4], nil}]}
|
||||
]}
|
||||
]}
|
||||
]}
|
||||
]}
|
||||
]
|
||||
]
|
||||
]}
|
||||
end
|
||||
|
||||
test "keeps typed arrows inside stab bodies" do
|
||||
assert parse_quote("""
|
||||
quote do
|
||||
subject ->
|
||||
setup
|
||||
@sig input -> output
|
||||
end
|
||||
""") ==
|
||||
{:quote, [line: 1],
|
||||
[
|
||||
[
|
||||
do: [
|
||||
{:->, [line: 2],
|
||||
[
|
||||
[{:subject, [line: 2], nil}],
|
||||
{:__block__, [],
|
||||
[
|
||||
{:setup, [line: 3], nil},
|
||||
{:@, [line: 4],
|
||||
[
|
||||
{:sig, [line: 4],
|
||||
[
|
||||
{:->, [line: 4],
|
||||
[
|
||||
[{:input, [line: 4], nil}],
|
||||
{:output, [line: 4], nil}
|
||||
]}
|
||||
]}
|
||||
]}
|
||||
]}
|
||||
]}
|
||||
]
|
||||
]
|
||||
]}
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_quote(source, opts \\ []), do: Code.string_to_quoted!(source, opts)
|
||||
end
|
||||
Reference in New Issue
Block a user