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')).
|
build_op_with_meta([], '$2', parens_meta('$1')).
|
||||||
stab_expr -> empty_paren when_op expr stab_op_eol_and_expr :
|
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')).
|
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 :
|
stab_expr -> call_args_no_parens_all stab_op_eol_and_expr :
|
||||||
build_op(unwrap_when(unwrap_splice('$1')), '$2').
|
build_op(unwrap_when(unwrap_splice('$1')), '$2').
|
||||||
stab_expr -> stab_parens_many stab_op_eol_and_expr :
|
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]};
|
{'/', [{line, Line} | Outer], [{'/', [{line, Line} | Inner], nil}, Expr]};
|
||||||
|
|
||||||
build_unary_op({_Kind, Location, Op}, 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}) ->
|
build_nullary_op({_Kind, Location, Op}) ->
|
||||||
{Op, meta_from_location(Location), []}.
|
{Op, meta_from_location(Location), []}.
|
||||||
@@ -1110,6 +1124,9 @@ check_stab([{'->', _, [_, _]}], _) -> stab;
|
|||||||
check_stab([], none) -> block;
|
check_stab([], none) -> block;
|
||||||
check_stab([_], none) -> block;
|
check_stab([_], none) -> block;
|
||||||
check_stab([_], Meta) -> error_invalid_stab(Meta);
|
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([{'->', Meta, [_, _]} | T], _) -> check_stab(T, Meta);
|
||||||
check_stab([_ | T], MaybeMeta) -> check_stab(T, MaybeMeta).
|
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_with_meta(Meta, ErrorMessage, ErrorToken) ->
|
||||||
return_error(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) ->
|
error_invalid_stab(MetaStab) ->
|
||||||
return_error_with_meta(MetaStab,
|
return_error_with_meta(MetaStab,
|
||||||
"unexpected operator ->. If you want to define multiple clauses, the first expression must use ->. "
|
"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