Skip to content

Commit dea8456

Browse files
authored
Escape meta within existing quote extensions (#14832)
Closes #14829 Closes #14830
1 parent 544a10c commit dea8456

File tree

3 files changed

+25
-48
lines changed

3 files changed

+25
-48
lines changed

lib/elixir/src/elixir_expand.erl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,11 +257,12 @@ expand({quote, Meta, [Opts, Do]}, S, E) when is_list(Do) ->
257257
{EContext, SC, EC} = expand(QContext, SP, EP),
258258
Quoted = elixir_quote:quote(Exprs, Q),
259259
{EQuoted, ES, EQ} = expand(Quoted, SC, EC),
260+
BindingMeta = lists:keydelete(column, 1, Meta),
260261

261262
EBinding =
262263
[{'{}', [],
263264
['=', [], [
264-
{'{}', [], [K, Meta, EContext]},
265+
{'{}', [], [K, BindingMeta, EContext]},
265266
V
266267
]
267268
]} || {K, V} <- Binding],

lib/elixir/src/elixir_quote.erl

Lines changed: 16 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -141,16 +141,12 @@ do_tuple_linify(Fun, Meta, Left, Right, Var) ->
141141
%% lines are kept and hygiene mechanisms are disabled.
142142
escape(Expr, Op, Unquote) ->
143143
try
144-
Q = #elixir_quote{
144+
do_quote(Expr, #elixir_quote{
145145
line=true,
146146
file=nil,
147147
op=Op,
148148
unquote=Unquote
149-
},
150-
case Unquote of
151-
true -> do_quote(Expr, Q);
152-
false -> do_escape(Expr, Q)
153-
end
149+
})
154150
catch
155151
Kind:Reason:Stacktrace ->
156152
Pruned = lists:dropwhile(fun
@@ -162,14 +158,15 @@ escape(Expr, Op, Unquote) ->
162158

163159
do_escape({Left, Meta, Right}, #elixir_quote{op=escape_and_prune} = Q) when is_list(Meta) ->
164160
TM = [{K, V} || {K, V} <- Meta, (K == no_parens) orelse (K == line) orelse (K == delimiter)],
165-
TL = do_escape(Left, Q),
166-
TR = do_escape(Right, Q),
161+
TL = do_quote(Left, Q),
162+
TR = do_quote(Right, Q),
167163
{'{}', [], [TL, TM, TR]};
168164

169165
do_escape({Left, Right}, Q) ->
170-
{do_escape(Left, Q), do_escape(Right, Q)};
166+
{do_quote(Left, Q), do_quote(Right, Q)};
167+
171168
do_escape(Tuple, Q) when is_tuple(Tuple) ->
172-
TT = do_escape(tuple_to_list(Tuple), Q),
169+
TT = do_quote(tuple_to_list(Tuple), Q),
173170
{'{}', [], TT};
174171

175172
do_escape(BitString, _) when is_bitstring(BitString) ->
@@ -206,19 +203,18 @@ do_escape(Map, Q) when is_map(Map) ->
206203
end
207204
else
208205
_ ->
209-
TT = [escape_map_key_value(K, V, Map, Q) || {K, V} <- lists:sort(maps:to_list(Map))],
206+
TT = [
207+
{do_quote(K, Q), do_quote(V, Q)}
208+
|| {K, V} <- lists:sort(maps:to_list(Map))
209+
],
210210
{'%{}', [], TT}
211211
end;
212212

213213
do_escape([], _) ->
214214
[];
215215

216216
do_escape([H | T], #elixir_quote{unquote=false} = Q) ->
217-
case is_list(T) of
218-
true -> [do_escape(H, Q) | do_escape(T, Q)];
219-
% improper list
220-
false -> [{'|', [], [do_escape(H, Q), do_escape(T, Q)]}]
221-
end;
217+
do_quote_simple_list(T, do_escape(H, Q), Q);
222218

223219
do_escape([H | T], Q) ->
224220
%% The improper case is inefficient, but improper lists are rare.
@@ -228,7 +224,7 @@ do_escape([H | T], Q) ->
228224
_:_ ->
229225
{L, R} = reverse_improper(T, [H]),
230226
TL = do_quote_splice(L, Q, [], []),
231-
TR = do_escape(R, Q),
227+
TR = do_quote(R, Q),
232228
update_last(TL, fun(X) -> {'|', [], [X, TR]} end)
233229
end;
234230

@@ -245,28 +241,6 @@ do_escape(Fun, _) when is_function(Fun) ->
245241
do_escape(Other, _) ->
246242
bad_escape(Other).
247243

248-
escape_map_key_value(K, V, Map, Q) ->
249-
MaybeRef = if
250-
is_reference(V) -> V;
251-
is_tuple(V) -> find_tuple_ref(V, 1);
252-
true -> nil
253-
end,
254-
if
255-
is_reference(MaybeRef) ->
256-
argument_error(<<('Elixir.Kernel':inspect(Map, []))/binary, " contains a reference (",
257-
('Elixir.Kernel':inspect(MaybeRef, []))/binary, ") and therefore it cannot be escaped",
258-
(bad_escape_hint())/binary>>);
259-
true ->
260-
{do_escape(K, Q), do_escape(V, Q)}
261-
end.
262-
263-
find_tuple_ref(Tuple, Index) when Index > tuple_size(Tuple) -> nil;
264-
find_tuple_ref(Tuple, Index) ->
265-
case element(Index, Tuple) of
266-
Ref when is_reference(Ref) -> Ref;
267-
_ -> find_tuple_ref(Tuple, Index + 1)
268-
end.
269-
270244
bad_escape(Arg) ->
271245
argument_error(<<"cannot escape ", ('Elixir.Kernel':inspect(Arg, []))/binary,
272246
(bad_escape_hint())/binary>>).
@@ -383,7 +357,6 @@ do_quote({quote, Meta, [Opts, Arg]}, Q) when is_list(Meta) ->
383357

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

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

621594
%% Helpers
622595

596+
meta(Meta, #elixir_quote{op=quote} = Q) ->
597+
generated(keep(keydelete(column, Meta), Q), Q);
623598
meta(Meta, Q) ->
624-
generated(keep(keydelete(column, Meta), Q), Q).
599+
do_quote(Meta, Q).
625600

626601
generated(Meta, #elixir_quote{generated=true}) -> [{generated, true} | Meta];
627602
generated(Meta, #elixir_quote{generated=false}) -> Meta.

lib/elixir/test/elixir/macro_test.exs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ defmodule MacroTest do
8282

8383
contents = quote(unquote: false, do: unquote(x))
8484
assert Macro.escape(contents, unquote: true) == {:x, [], MacroTest}
85+
86+
contents = %{foo: quote(unquote: false, do: unquote(1))}
87+
assert Macro.escape(contents, unquote: true) == {:%{}, [], [foo: 1]}
8588
end
8689

8790
test "with generated" do
@@ -97,6 +100,7 @@ defmodule MacroTest do
97100
test "with remote unquote" do
98101
contents = quote(unquote: false, do: Kernel.unquote(:is_atom)(:ok))
99102
assert eval_escaped(contents) == quote(do: Kernel.is_atom(:ok))
103+
assert eval_escaped(%{foo: contents}) == %{foo: quote(do: Kernel.is_atom(:ok))}
100104
end
101105

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

142146
assert eval_escaped(contents) == [1, 2, 3, 4, 5]
147+
148+
contents = %{foo: quote(unquote: false, do: [1, 2, unquote_splicing([3, 4, 5])])}
149+
assert eval_escaped(contents) == %{foo: [1, 2, 3, 4, 5]}
143150
end
144151

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

157-
test "escape container when a reference cannot be escaped" do
158-
assert_raise ArgumentError, ~r"contains a reference", fn ->
159-
Macro.escape(%{re_pattern: {:re_pattern, 0, 0, 0, make_ref()}})
160-
end
161-
end
162-
163164
@tag :re_import
164165
test "escape regex will remove references and replace it by a call to :re.import/1" do
165166
assert {

0 commit comments

Comments
 (0)