Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion lib/elixir/src/elixir_expand.erl
Original file line number Diff line number Diff line change
Expand Up @@ -257,11 +257,12 @@ expand({quote, Meta, [Opts, Do]}, S, E) when is_list(Do) ->
{EContext, SC, EC} = expand(QContext, SP, EP),
Quoted = elixir_quote:quote(Exprs, Q),
{EQuoted, ES, EQ} = expand(Quoted, SC, EC),
BindingMeta = lists:keydelete(column, 1, Meta),

EBinding =
[{'{}', [],
['=', [], [
{'{}', [], [K, Meta, EContext]},
{'{}', [], [K, BindingMeta, EContext]},
V
]
]} || {K, V} <- Binding],
Expand Down
57 changes: 16 additions & 41 deletions lib/elixir/src/elixir_quote.erl
Original file line number Diff line number Diff line change
Expand Up @@ -141,16 +141,12 @@ do_tuple_linify(Fun, Meta, Left, Right, Var) ->
%% lines are kept and hygiene mechanisms are disabled.
escape(Expr, Op, Unquote) ->
try
Q = #elixir_quote{
do_quote(Expr, #elixir_quote{
line=true,
file=nil,
op=Op,
unquote=Unquote
},
case Unquote of
true -> do_quote(Expr, Q);
false -> do_escape(Expr, Q)
end
})
catch
Kind:Reason:Stacktrace ->
Pruned = lists:dropwhile(fun
Expand All @@ -162,14 +158,15 @@ escape(Expr, Op, Unquote) ->

do_escape({Left, Meta, Right}, #elixir_quote{op=escape_and_prune} = Q) when is_list(Meta) ->
TM = [{K, V} || {K, V} <- Meta, (K == no_parens) orelse (K == line) orelse (K == delimiter)],
TL = do_escape(Left, Q),
TR = do_escape(Right, Q),
TL = do_quote(Left, Q),
TR = do_quote(Right, Q),
{'{}', [], [TL, TM, TR]};

do_escape({Left, Right}, Q) ->
{do_escape(Left, Q), do_escape(Right, Q)};
{do_quote(Left, Q), do_quote(Right, Q)};

do_escape(Tuple, Q) when is_tuple(Tuple) ->
TT = do_escape(tuple_to_list(Tuple), Q),
TT = do_quote(tuple_to_list(Tuple), Q),
{'{}', [], TT};

do_escape(BitString, _) when is_bitstring(BitString) ->
Expand Down Expand Up @@ -206,19 +203,18 @@ do_escape(Map, Q) when is_map(Map) ->
end
else
_ ->
TT = [escape_map_key_value(K, V, Map, Q) || {K, V} <- lists:sort(maps:to_list(Map))],
TT = [
{do_quote(K, Q), do_quote(V, Q)}
|| {K, V} <- lists:sort(maps:to_list(Map))
],
{'%{}', [], TT}
end;

do_escape([], _) ->
[];

do_escape([H | T], #elixir_quote{unquote=false} = Q) ->
case is_list(T) of
true -> [do_escape(H, Q) | do_escape(T, Q)];
% improper list
false -> [{'|', [], [do_escape(H, Q), do_escape(T, Q)]}]
end;
do_quote_simple_list(T, do_escape(H, Q), Q);

do_escape([H | T], Q) ->
%% The improper case is inefficient, but improper lists are rare.
Expand All @@ -228,7 +224,7 @@ do_escape([H | T], Q) ->
_:_ ->
{L, R} = reverse_improper(T, [H]),
TL = do_quote_splice(L, Q, [], []),
TR = do_escape(R, Q),
TR = do_quote(R, Q),
update_last(TL, fun(X) -> {'|', [], [X, TR]} end)
end;

Expand All @@ -245,28 +241,6 @@ do_escape(Fun, _) when is_function(Fun) ->
do_escape(Other, _) ->
bad_escape(Other).

escape_map_key_value(K, V, Map, Q) ->
MaybeRef = if
is_reference(V) -> V;
is_tuple(V) -> find_tuple_ref(V, 1);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed this for now, since we added it exclusively for regexes. We can perhaps add it back but given it could also be inside lists and other places, not sure if it makes sense to special case it.

true -> nil
end,
if
is_reference(MaybeRef) ->
argument_error(<<('Elixir.Kernel':inspect(Map, []))/binary, " contains a reference (",
('Elixir.Kernel':inspect(MaybeRef, []))/binary, ") and therefore it cannot be escaped",
(bad_escape_hint())/binary>>);
true ->
{do_escape(K, Q), do_escape(V, Q)}
end.

find_tuple_ref(Tuple, Index) when Index > tuple_size(Tuple) -> nil;
find_tuple_ref(Tuple, Index) ->
case element(Index, Tuple) of
Ref when is_reference(Ref) -> Ref;
_ -> find_tuple_ref(Tuple, Index + 1)
end.

bad_escape(Arg) ->
argument_error(<<"cannot escape ", ('Elixir.Kernel':inspect(Arg, []))/binary,
(bad_escape_hint())/binary>>).
Expand Down Expand Up @@ -383,7 +357,6 @@ do_quote({quote, Meta, [Opts, Arg]}, Q) when is_list(Meta) ->

{'{}', [], [quote, meta(NewMeta, Q), [TOpts, TArg]]};

%
do_quote({unquote, Meta, [Expr]}, #elixir_quote{unquote=true, shallow_validate=Validate}) when is_list(Meta) ->
case Validate of
true -> {{'.', Meta, [?MODULE, shallow_validate_ast]}, Meta, [Expr]};
Expand Down Expand Up @@ -620,8 +593,10 @@ argument_error(Message) ->

%% Helpers

meta(Meta, #elixir_quote{op=quote} = Q) ->
generated(keep(keydelete(column, Meta), Q), Q);
meta(Meta, Q) ->
generated(keep(keydelete(column, Meta), Q), Q).
do_quote(Meta, Q).

generated(Meta, #elixir_quote{generated=true}) -> [{generated, true} | Meta];
generated(Meta, #elixir_quote{generated=false}) -> Meta.
Expand Down
13 changes: 7 additions & 6 deletions lib/elixir/test/elixir/macro_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ defmodule MacroTest do

contents = quote(unquote: false, do: unquote(x))
assert Macro.escape(contents, unquote: true) == {:x, [], MacroTest}

contents = %{foo: quote(unquote: false, do: unquote(1))}
assert Macro.escape(contents, unquote: true) == {:%{}, [], [foo: 1]}
end

test "with generated" do
Expand All @@ -97,6 +100,7 @@ defmodule MacroTest do
test "with remote unquote" do
contents = quote(unquote: false, do: Kernel.unquote(:is_atom)(:ok))
assert eval_escaped(contents) == quote(do: Kernel.is_atom(:ok))
assert eval_escaped(%{foo: contents}) == %{foo: quote(do: Kernel.is_atom(:ok))}
end

test "with nested unquote" do
Expand Down Expand Up @@ -140,6 +144,9 @@ defmodule MacroTest do
quote(unquote: false, do: [1, unquote_splicing([2]), 3, unquote_splicing([4]) | [5]])

assert eval_escaped(contents) == [1, 2, 3, 4, 5]

contents = %{foo: quote(unquote: false, do: [1, 2, unquote_splicing([3, 4, 5])])}
assert eval_escaped(contents) == %{foo: [1, 2, 3, 4, 5]}
end

test "does not add context to quote" do
Expand All @@ -154,12 +161,6 @@ defmodule MacroTest do
[:foo, {:{}, [], [:quote, [{:%{}, [], []}], [{:{}, [], []}]]}]
end

test "escape container when a reference cannot be escaped" do
assert_raise ArgumentError, ~r"contains a reference", fn ->
Macro.escape(%{re_pattern: {:re_pattern, 0, 0, 0, make_ref()}})
end
end

@tag :re_import
test "escape regex will remove references and replace it by a call to :re.import/1" do
assert {
Expand Down
Loading