Skip to content

Commit 212acf4

Browse files
author
José Valim
committed
Provide better exceptions for invalid unquote args, closes #1680
1 parent fc0af05 commit 212acf4

File tree

3 files changed

+47
-31
lines changed

3 files changed

+47
-31
lines changed

lib/elixir/src/elixir_errors.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ warn(Warning) ->
1515
elixir_code_server:cast(register_warning),
1616
io:put_chars(standard_error, Warning).
1717

18-
%% Handle inspecting for exceptions
18+
%% Handle inspecting for exceptions modules
1919

2020
inspect(Atom) when is_atom(Atom) ->
2121
'Elixir.Inspect.Atom':inspect(Atom).

lib/elixir/src/elixir_quote.erl

Lines changed: 45 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
%% Implements Elixir quote.
22
-module(elixir_quote).
3-
-export([escape/2, erl_escape/3, erl_quote/4, linify/2, unquote/6, join/5]).
3+
-export([escape/2, erl_escape/3, erl_quote/4,
4+
linify/2, unquote/4, tail_join/3, join/2]).
45
-include("elixir.hrl").
56

67
%% Apply the line from site call on quoted contents.
@@ -24,44 +25,59 @@ do_linify(_, Else) -> Else.
2425

2526
%% Some expressions cannot be unquoted at compilation time.
2627
%% This function is responsible for doing runtime unquoting.
27-
unquote(_File, _Line, Meta, Left, { '__aliases__', _, Args }, nil) ->
28+
unquote(Meta, Left, { '__aliases__', _, Args }, nil) ->
2829
{ '__aliases__', Meta, [Left|Args] };
2930

30-
unquote(_File, _Line, Meta, Left, Right, nil) when is_atom(Right) ->
31+
unquote(Meta, Left, Right, nil) when is_atom(Right) ->
3132
case atom_to_list(Right) of
3233
"Elixir." ++ _ ->
3334
{ '__aliases__', Meta, [Left, Right] };
3435
_ ->
3536
{ { '.', Meta, [Left, Right] }, Meta, [] }
3637
end;
3738

38-
unquote(_File, _Line, Meta, Left, { Right, _, Context }, nil) when is_atom(Right), is_atom(Context) ->
39+
unquote(Meta, Left, { Right, _, Context }, nil) when is_atom(Right), is_atom(Context) ->
3940
{ { '.', Meta, [Left, Right] }, Meta, [] };
4041

41-
unquote(_File, _Line, Meta, Left, { Right, _, Args }, nil) when is_atom(Right) ->
42+
unquote(Meta, Left, { Right, _, Args }, nil) when is_atom(Right) ->
4243
{ { '.', Meta, [Left, Right] }, Meta, Args };
4344

44-
unquote(File, Line, _Meta, _Left, Right, nil) ->
45-
elixir_errors:syntax_error(Line, File, "expected unquote after dot to return an atom, "
46-
"an alias or a quoted call, got: ~ts", ['Elixir.Macro':to_string(Right)]);
45+
unquote(_Meta, _Left, Right, nil) ->
46+
argument_error(<<"expected unquote after dot to return an atom, an alias or a quoted call, got: ",
47+
('Elixir.Macro':to_string(Right))/binary>>);
4748

48-
unquote(_File, _Line, Meta, Left, Right, Args) when is_atom(Right) ->
49+
unquote(Meta, Left, Right, Args) when is_atom(Right) ->
4950
{ { '.', Meta, [Left, Right] }, Meta, Args };
5051

51-
unquote(_File, _Line, Meta, Left, { Right, _, Context }, Args) when is_atom(Right), is_atom(Context) ->
52+
unquote(Meta, Left, { Right, _, Context }, Args) when is_atom(Right), is_atom(Context) ->
5253
{ { '.', Meta, [Left, Right] }, Meta, Args };
5354

54-
unquote(File, Line, _Meta, _Left, Right, _Args) ->
55-
elixir_errors:syntax_error(Line, File, "expected unquote after dot with args to return an atom "
56-
"or a quoted call, got: ~ts", ['Elixir.Macro':to_string(Right)]).
55+
unquote(_Meta, _Left, Right, _Args) ->
56+
argument_error(<<"expected unquote after dot with args to return an atom or a quoted call, got: ",
57+
('Elixir.Macro':to_string(Right))/binary>>).
5758

58-
join(_File, _Line, Left, Right, Rest) when is_list(Left), is_list(Right), is_list(Rest) ->
59-
Rest ++ Left ++ Right;
59+
join(Left, Right) when is_list(Right) ->
60+
validate_join(Left),
61+
Left ++ Right.
6062

61-
join(_File, _Line, Left, Right, Rest) ->
62-
[H|T] = lists:reverse(Rest ++ Left),
63+
tail_join(Left, Right, Tail) when is_list(Right), is_list(Tail) ->
64+
validate_join(Left),
65+
Tail ++ Left ++ Right;
66+
67+
tail_join(Left, Right, Tail) when is_list(Left) ->
68+
validate_join(Left),
69+
[H|T] = lists:reverse(Tail ++ Left),
6370
lists:reverse([{ '|', [], [H, Right] }|T]).
6471

72+
validate_join(List) when is_list(List) ->
73+
ok;
74+
validate_join(List) when not is_list(List) ->
75+
argument_error(<<"expected a list with quoted expressions in unquote_splicing/1, got: ",
76+
('Elixir.Kernel':inspect(List))/binary>>).
77+
78+
argument_error(Message) ->
79+
'Elixir.Kernel':raise('Elixir.ArgumentError', [{message,Message}]).
80+
6581
%% Escapes the given expression. It is similar to quote, but
6682
%% lines are kept and hygiene mechanisms are disabled.
6783
escape(Expr, Unquote) ->
@@ -214,9 +230,9 @@ do_quote(Other, Q, _) ->
214230
%% Quote helpers
215231

216232
do_quote_call(Left, Meta, Expr, Args, Q, S) ->
217-
All = [?line(Meta), meta(Meta, Q), Left, { unquote, Meta, [Expr] }, Args],
233+
All = [meta(Meta, Q), Left, { unquote, Meta, [Expr] }, Args],
218234
{ TAll, TQ } = lists:mapfoldl(fun(X, Acc) -> do_quote(X, Acc, S) end, Q, All),
219-
{ { { '.', Meta, [elixir_quote, unquote] }, Meta, [{ '__FILE__', [], nil }|TAll] }, TQ }.
235+
{ { { '.', Meta, [elixir_quote, unquote] }, Meta, TAll }, TQ }.
220236

221237
do_quote_fa(Target, Meta, Args, F, A, Q, S) ->
222238
NewMeta =
@@ -264,24 +280,24 @@ do_splice([{ '|', Meta, [{ unquote_splicing, _, [Left] }, Right] }|T], #elixir_q
264280
%% 1, 2 and 3, which could even be unquotes.
265281
{ TT, QT } = do_splice(T, Q, S, [], []),
266282
{ TR, QR } = do_quote(Right, QT, S),
267-
268-
%% Do the joining at runtime when we are aware of the values.
269-
Args = [{ '__FILE__', [], nil }, ?line(Meta), Left, TR, TT],
270-
{ { { '.', Meta, [elixir_quote, join] }, Meta, Args }, QR#elixir_quote{unquoted=true} };
283+
{ do_runtime_join(Meta, tail_join, [Left, TR, TT]), QR#elixir_quote{unquoted=true} };
271284

272285
do_splice(List, Q, S) ->
273286
do_splice(List, Q, S, [], []).
274287

275-
do_splice([{ unquote_splicing, _, [Expr] }|T], #elixir_quote{unquote=true} = Q, S, Buffer, Acc) ->
276-
do_splice(T, Q#elixir_quote{unquoted=true}, S, [], do_splice_join(do_splice_join(Expr, Buffer), Acc));
288+
do_splice([{ unquote_splicing, Meta, [Expr] }|T], #elixir_quote{unquote=true} = Q, S, Buffer, Acc) ->
289+
do_splice(T, Q#elixir_quote{unquoted=true}, S, [], do_runtime_join(Meta, join, [Expr, do_join(Buffer, Acc)]));
277290

278291
do_splice([H|T], Q, S, Buffer, Acc) ->
279292
{ TH, TQ } = do_quote(H, Q, S),
280293
do_splice(T, TQ, S, [TH|Buffer], Acc);
281294

282295
do_splice([], Q, _S, Buffer, Acc) ->
283-
{ do_splice_join(Buffer, Acc), Q }.
296+
{ do_join(Buffer, Acc), Q }.
297+
298+
do_join(Left, []) -> Left;
299+
do_join([], Right) -> Right;
300+
do_join(Left, Right) -> { { '.', [], ['Elixir.Kernel', '++'] }, [], [Left, Right] }.
284301

285-
do_splice_join(Left, []) -> Left;
286-
do_splice_join([], Right) -> Right;
287-
do_splice_join(Left, Right) -> { { '.', [], ['Elixir.Kernel', '++'] }, [], [Left, Right] }.
302+
do_runtime_join(Meta, Fun, Args) ->
303+
{ { '.', Meta, [elixir_quote, Fun] }, Meta, Args }.

lib/elixir/test/elixir/kernel/quote_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ defmodule Kernel.QuoteTest do
5959
assert Code.eval_quoted(quote(do: Foo.unquote(Bar))) == { Elixir.Foo.Bar, [] }
6060
assert Code.eval_quoted(quote(do: Foo.unquote(quote do: Bar))) == { Elixir.Foo.Bar, [] }
6161

62-
assert_raise SyntaxError, fn ->
62+
assert_raise ArgumentError, fn ->
6363
quote(do: foo.unquote(1))
6464
end
6565
end

0 commit comments

Comments
 (0)