Skip to content
Closed
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
14 changes: 13 additions & 1 deletion lib/elixir/src/elixir_expand.erl
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ expand({quote, Meta, [Opts, Do]}, S, E) when is_list(Do) ->
file_error(Meta, E, ?MODULE, quote_in_pattern_with_unquote),

{Q, QContext, QPrelude} = elixir_quote:build(Meta, Line, File, Context, Unquote, Generated, ET),
{EPrelude, SP, EP} = expand(QPrelude, ST, ET),
{EPrelude, SP, EP} = expand_quote_prelude(QPrelude, ST, ET),
{EContext, SC, EC} = expand(QContext, SP, EP),
Quoted = elixir_quote:quote(Exprs, Q),
{EQuoted, ES, EQ} = expand(Quoted, SC, EC),
Expand Down Expand Up @@ -1016,6 +1016,18 @@ attach_runtime_module(Receiver, Meta, S, _E) ->
false -> Meta
end.

%% Quote helpers

expand_quote_prelude([{'=', Meta, [Var, {Dot, CallMeta, [Key, Value]}]} | Rest], S, E) ->
%% The option value is already expanded. Expand the generated validation and
%% assignment with a placeholder, then restore the value without expanding it.
{{'=', EMeta, [EVar, {EDot, ECallMeta, [Key, nil]}]}, SV, EV} =
expand({'=', Meta, [Var, {Dot, CallMeta, [Key, nil]}]}, S, E),
{ERest, SR, ER} = expand_quote_prelude(Rest, SV, EV),
{[{'=', EMeta, [EVar, {EDot, ECallMeta, [Key, Value]}]} | ERest], SR, ER};
expand_quote_prelude([], S, E) ->
{[], S, E}.

%% Lexical helpers

expand_opts(Meta, Kind, Allowed, Opts, S, E) ->
Expand Down
64 changes: 64 additions & 0 deletions lib/elixir/test/elixir/kernel/quote_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,70 @@ defmodule Kernel.QuoteTest do
end)
end

test "quote options with assignment" do
line = 10
quoted = quote line: line = line + 1, do: foo()
assert quoted == {:foo, [line: 11], []}
assert line == 11

file = "foo"
quoted = quote file: file = file <> ".ex", do: foo()
assert quoted == {:foo, [keep: {"foo.ex", 0}], []}
assert file == "foo.ex"

context = :foo
quoted = quote context: context = {context, :bar} |> elem(1), do: foo
assert quoted == {:foo, [], :bar}
assert context == :bar
end

test "quote options with assignment at top level" do
Code.compile_string("""
x = 10
q = quote line: (x = x + 1), do: foo()
send(self(), {q, x})
""")

assert_received {{:foo, [line: 11], []}, 11}

Code.compile_string("""
x = 10
q = quote line: (x = x + 1), bind_quoted: [x: x], do: x
send(self(), {q, x})
""")

assert_received {{:__block__, [],
[{:=, [], [{:x, _, Elixir}, 10]}, {:x, [line: 11], Elixir}]}, 11}
end

test "quote options with assignment and bind_quoted" do
x = 10
quoted = quote line: x = x + 1, bind_quoted: [x: x], do: x

assert {:__block__, [], [{:=, [], [{:x, _, __MODULE__}, 10]}, x_ast]} = quoted
assert x_ast == {:x, [line: 11], __MODULE__}
assert x == 11
end

test "quote option evaluation order" do
quoted =
quote line: elem(send(self(), {:line, 11}), 1),
file: elem(send(self(), {:file, "foo.ex"}), 1),
context: elem(send(self(), {:context, :foo}), 1),
bind_quoted: [x: elem(send(self(), {:bind_quoted, 10}), 1)] do
x
end

assert {:__block__, [], [{:=, [], [{:x, _, :foo}, 10]}, {:x, _, :foo}]} = quoted

for expected <- [context: :foo, file: "foo.ex", line: 11, bind_quoted: 10] do
assert_received actual
assert actual == expected
end

refute_received _
end

test "operator precedence" do
assert {:+, _, [{:+, _, [1, _]}, 1]} = quote(do: 1 + Foo.l() + 1)
assert {:+, _, [1, {_, _, [{:+, _, [1]}]}]} = quote(do: 1 + Foo.l(+1))
Expand Down
Loading