Skip to content

Commit 03d412f

Browse files
committed
Handle bind_quoted with context correctly, closes #13590
1 parent f0ebedc commit 03d412f

File tree

3 files changed

+49
-39
lines changed

3 files changed

+49
-39
lines changed

lib/elixir/src/elixir_expand.erl

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,9 @@ expand({Unquote, Meta, [_]}, _S, E) when Unquote == unquote; Unquote == unquote_
161161
file_error(Meta, E, ?MODULE, {unquote_outside_quote, Unquote});
162162

163163
expand({quote, Meta, [Opts]}, S, E) when is_list(Opts) ->
164-
case lists:keyfind(do, 1, Opts) of
165-
{do, Do} ->
166-
expand({quote, Meta, [lists:keydelete(do, 1, Opts), [{do, Do}]]}, S, E);
164+
case lists:keytake(do, 1, Opts) of
165+
{value, {do, Do}, DoOpts} ->
166+
expand({quote, Meta, [DoOpts, [{do, Do}]]}, S, E);
167167
false ->
168168
file_error(Meta, E, ?MODULE, {missing_option, 'quote', [do]})
169169
end;
@@ -178,8 +178,20 @@ expand({quote, Meta, [Opts, Do]}, S, E) when is_list(Do) ->
178178
false -> file_error(Meta, E, ?MODULE, {missing_option, 'quote', [do]})
179179
end,
180180

181-
ValidOpts = [context, location, line, file, unquote, bind_quoted, generated],
182-
{EOpts, ST, ET} = expand_opts(Meta, quote, ValidOpts, Opts, S, E),
181+
{Binding, DefaultUnquote, ToExpandOpts} =
182+
case is_list(Opts) andalso lists:keytake(bind_quoted, 1, Opts) of
183+
{value, {bind_quoted, BQ}, BQOpts} ->
184+
case is_list(BQ) andalso
185+
lists:all(fun({Key, _}) when is_atom(Key) -> true; (_) -> false end, BQ) of
186+
true -> {BQ, false, BQOpts};
187+
false -> file_error(Meta, E, ?MODULE, {invalid_bind_quoted_for_quote, BQ})
188+
end;
189+
false ->
190+
{[], true, Opts}
191+
end,
192+
193+
ValidKeys = [context, location, line, file, unquote, bind_quoted, generated],
194+
{EOpts, ST, ET} = expand_opts(Meta, quote, ValidKeys, ToExpandOpts, S, E),
183195

184196
Context = proplists:get_value(context, EOpts, case ?key(E, module) of
185197
nil -> 'Elixir';
@@ -193,28 +205,12 @@ expand({quote, Meta, [Opts, Do]}, S, E) when is_list(Do) ->
193205
{proplists:get_value(file, EOpts, nil), proplists:get_value(line, EOpts, false)}
194206
end,
195207

196-
{Binding, DefaultUnquote} = case lists:keyfind(bind_quoted, 1, EOpts) of
197-
{bind_quoted, BQ} ->
198-
case is_list(BQ) andalso
199-
lists:all(fun({Key, _}) when is_atom(Key) -> true; (_) -> false end, BQ) of
200-
true -> {BQ, false};
201-
false -> file_error(Meta, E, ?MODULE, {invalid_bind_quoted_for_quote, BQ})
202-
end;
203-
false ->
204-
{[], true}
205-
end,
206-
207208
Unquote = proplists:get_value(unquote, EOpts, DefaultUnquote),
208209
Generated = proplists:get_value(generated, EOpts, false),
209210

210-
{Q, EBinding, Prelude} = elixir_quote:build(Meta, Line, File, Context, Unquote, Generated, Binding, ET),
211-
Quoted = elixir_quote:quote(Exprs, Q, Prelude),
212-
{EQuoted, ES, EQ} = expand(Quoted, ST, ET),
213-
214-
case EBinding of
215-
[] -> {EQuoted, ES, EQ};
216-
_ -> {{'{}', [], ['__block__', [], EBinding ++ [EQuoted]]}, ES, EQ}
217-
end;
211+
{Q, Prelude} = elixir_quote:build(Meta, Line, File, Context, Unquote, Generated, ET),
212+
Quoted = elixir_quote:quote(Meta, Exprs, Binding, Q, Prelude),
213+
expand(Quoted, ST, ET);
218214

219215
expand({quote, Meta, [_, _]}, _S, E) ->
220216
file_error(Meta, E, ?MODULE, {invalid_args, 'quote'});

lib/elixir/src/elixir_quote.erl

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
-module(elixir_quote).
2-
-export([escape/3, linify/3, linify_with_context_counter/3, build/8, quote/3, has_unquotes/1, fun_to_quoted/1]).
2+
-export([escape/3, linify/3, linify_with_context_counter/3, build/7, quote/5, has_unquotes/1, fun_to_quoted/1]).
33
-export([dot/5, tail_list/3, list/2, validate_runtime/2]). %% Quote callbacks
44

55
-include("elixir.hrl").
@@ -200,7 +200,7 @@ bad_escape(Arg) ->
200200

201201
%% Quote entry points
202202

203-
build(Meta, Line, File, Context, Unquote, Generated, Binding, E) ->
203+
build(Meta, Line, File, Context, Unquote, Generated, E) ->
204204
Acc0 = [],
205205
{ELine, Acc1} = validate_compile(Meta, line, Line, Acc0),
206206
{EFile, Acc2} = validate_compile(Meta, file, File, Acc1),
@@ -219,15 +219,7 @@ build(Meta, Line, File, Context, Unquote, Generated, Binding, E) ->
219219
generated=Generated
220220
},
221221

222-
Vars =
223-
[{'{}', [],
224-
['=', [], [
225-
{'{}', [], [K, Meta, EContext]},
226-
V
227-
]
228-
]} || {K, V} <- Binding],
229-
230-
{Q, Vars, Acc3}.
222+
{Q, Acc3}.
231223

232224
validate_compile(_Meta, line, Value, Acc) when is_boolean(Value) ->
233225
{Value, Acc};
@@ -263,16 +255,30 @@ is_valid(context, Context) -> is_atom(Context) andalso (Context /= nil);
263255
is_valid(generated, Generated) -> is_boolean(Generated);
264256
is_valid(unquote, Unquote) -> is_boolean(Unquote).
265257

266-
quote({unquote_splicing, _, [_]}, #elixir_quote{unquote=true}, _) ->
258+
quote(_Meta, {unquote_splicing, _, [_]}, _Binding, #elixir_quote{unquote=true}, _) ->
267259
argument_error(<<"unquote_splicing only works inside arguments and block contexts, "
268260
"wrap it in parens if you want it to work with one-liners">>);
269261

270-
quote(Expr, Q, Prelude) ->
262+
quote(Meta, Expr, Binding, Q, Prelude) ->
263+
Context = Q#elixir_quote.context,
264+
265+
Vars = [{'{}', [],
266+
['=', [], [
267+
{'{}', [], [K, Meta, Context]},
268+
V
269+
]]
270+
} || {K, V} <- Binding],
271+
271272
Quoted = do_quote(Expr, Q),
272273

273-
case Prelude of
274+
WithVars = case Vars of
274275
[] -> Quoted;
275-
_ -> {'__block__', [], Prelude ++ [Quoted]}
276+
_ -> {'{}', [], ['__block__', [], Vars ++ [Quoted]]}
277+
end,
278+
279+
case Prelude of
280+
[] -> WithVars;
281+
_ -> {'__block__', [], Prelude ++ [WithVars]}
276282
end.
277283

278284
%% quote/unquote

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,14 @@ defmodule Kernel.QuoteTest do
7171
end
7272
end
7373

74+
test "quote context bind_quoted" do
75+
assert {:__block__, _,
76+
[{:=, [], [{:some_var, _, :fallback}, 321]}, {:some_var, _, :fallback}]} =
77+
(quote bind_quoted: [some_var: 321], context: __ENV__.context || :fallback do
78+
some_var
79+
end)
80+
end
81+
7482
test "operator precedence" do
7583
assert {:+, _, [{:+, _, [1, _]}, 1]} = quote(do: 1 + Foo.l() + 1)
7684
assert {:+, _, [1, {_, _, [{:+, _, [1]}]}]} = quote(do: 1 + Foo.l(+1))

0 commit comments

Comments
 (0)