Compare commits

...
Author SHA1 Message Date
José Valim 42fb7d2be3 Back to take 1 2026-06-25 21:31:19 +02:00
José Valim 332f2ca791 Take 2 2026-06-25 21:00:54 +02:00
José Valim 082e08664e Take 1 2026-06-25 20:35:34 +02:00
2 changed files with 413 additions and 1 deletions
+23 -1
View File
@@ -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