Skip to content

Commit 7002554

Browse files
author
José Valim
committed
Ensure local captures work correctly on macro expansion
Closes #9245
1 parent 660a09b commit 7002554

File tree

5 files changed

+27
-10
lines changed

5 files changed

+27
-10
lines changed

lib/elixir/lib/exception.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ defmodule Exception do
208208
{_, kind, _, clauses} <- List.keyfind(defs, {function, arity}, 0) do
209209
clauses =
210210
for {meta, ex_args, guards, _block} <- clauses do
211-
scope = :elixir_erl.scope(meta)
211+
scope = :elixir_erl.scope(meta, true)
212212

213213
{erl_args, scope} =
214214
:elixir_erl_clauses.match(&:elixir_erl_pass.translate_args/2, ex_args, scope)

lib/elixir/src/elixir.hrl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
backup_vars=nil, %% a copy of vars to be used on ^var
1414
extra_guards=[], %% extra guards from args expansion
1515
counter=#{}, %% a map counting the variables defined
16+
expand_captures=false, %% a boolean to control if captures should be expanded
1617
stacktrace=false %% holds information about the stacktrace variable
1718
}).
1819

lib/elixir/src/elixir_erl.erl

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
%% Compiler backend to Erlang.
22
-module(elixir_erl).
33
-export([elixir_to_erl/1, definition_to_anonymous/4, compile/1, consolidate/3,
4-
get_ann/1, debug_info/4, scope/1, format_error/1]).
4+
get_ann/1, debug_info/4, scope/2, format_error/1]).
55
-include("elixir.hrl").
66
-define(typespecs, 'Elixir.Kernel.Typespec').
77

@@ -48,7 +48,7 @@ get_ann([], Gen, Line) -> erl_anno:set_generated(Gen, Line).
4848
%% Converts an Elixir definition to an anonymous function.
4949

5050
definition_to_anonymous(Module, Kind, Meta, Clauses) ->
51-
ErlClauses = [translate_clause(Kind, Clause) || Clause <- Clauses],
51+
ErlClauses = [translate_clause(Kind, Clause, true) || Clause <- Clauses],
5252
Fun = {'fun', ?ann(Meta), {clauses, ErlClauses}},
5353
LocalHandler = fun(LocalName, LocalArgs) -> invoke_local(Module, LocalName, LocalArgs) end,
5454
{value, Result, _Binding} = erl_eval:expr(Fun, [], {value, LocalHandler}),
@@ -117,8 +117,8 @@ elixir_to_erl_cons(T) -> elixir_to_erl(T).
117117

118118
%% Returns a scope for translation.
119119

120-
scope(_Meta) ->
121-
#elixir_erl{}.
120+
scope(_Meta, ExpandCaptures) ->
121+
#elixir_erl{expand_captures=ExpandCaptures}.
122122

123123
%% Static compilation hook, used in protocol consolidation
124124

@@ -220,15 +220,15 @@ add_definition(Meta, Body, {Head, Tail}) ->
220220
end.
221221

222222
translate_definition(Kind, Meta, {Name, Arity}, Clauses) ->
223-
ErlClauses = [translate_clause(Kind, Clause) || Clause <- Clauses],
223+
ErlClauses = [translate_clause(Kind, Clause, false) || Clause <- Clauses],
224224

225225
case is_macro(Kind) of
226226
true -> {function, ?ann(Meta), elixir_utils:macro_name(Name), Arity + 1, ErlClauses};
227227
false -> {function, ?ann(Meta), Name, Arity, ErlClauses}
228228
end.
229229

230-
translate_clause(Kind, {Meta, Args, Guards, Body}) ->
231-
S = scope(Meta),
230+
translate_clause(Kind, {Meta, Args, Guards, Body}, ExpandCaptures) ->
231+
S = scope(Meta, ExpandCaptures),
232232

233233
{TClause, TS} = elixir_erl_clauses:clause(Meta,
234234
fun elixir_erl_pass:translate_args/2, Args, Body, Guards, S),

lib/elixir/src/elixir_erl_pass.erl

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,15 @@ translate({'&', Meta, [{'/', _, [{{'.', _, [Remote, Fun]}, _, []}, Arity]}]}, S)
9696
{{'fun', Ann, {function, TRemote, TFun, TArity}}, SR};
9797
translate({'&', Meta, [{'/', _, [{Fun, _, Atom}, Arity]}]}, S)
9898
when is_atom(Fun), is_atom(Atom), is_integer(Arity) ->
99-
{{'fun', ?ann(Meta), {function, Fun, Arity}}, S};
99+
case S of
100+
#elixir_erl{expand_captures=true} ->
101+
Vars = [{list_to_atom("arg" ++ integer_to_list(Counter)), [], ?MODULE}
102+
|| Counter <- tl(lists:seq(0, Arity))],
103+
translate({'fn', Meta, [{'->', Meta, [Vars, {Fun, Meta, Vars}]}]}, S);
104+
105+
#elixir_erl{expand_captures=false} ->
106+
{{'fun', ?ann(Meta), {function, Fun, Arity}}, S}
107+
end;
100108

101109
translate({fn, Meta, Clauses}, S) ->
102110
Transformer = fun({'->', CMeta, [ArgsWithGuards, Expr]}, Acc) ->

lib/elixir/test/elixir/kernel/macros_test.exs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ defmodule Kernel.MacrosTest do
3434
quote(do: 1 + unquote(value))
3535
end
3636

37+
defmacro my_macro_with_capture(value) do
38+
Enum.map(value, &by_two/1)
39+
end
40+
3741
test "require" do
3842
assert Kernel.MacrosTest.Nested.value() == 1
3943
end
@@ -42,7 +46,7 @@ defmodule Kernel.MacrosTest do
4246
assert Nested.value() == 1
4347
end
4448

45-
test "local but private macro" do
49+
test "local with private macro" do
4650
assert my_private_macro() == 4
4751
end
4852

@@ -54,6 +58,10 @@ defmodule Kernel.MacrosTest do
5458
assert my_macro_with_local(4) == 17
5559
end
5660

61+
test "local with capture" do
62+
assert my_macro_with_capture([1, 2, 3]) == [2, 4, 6]
63+
end
64+
5765
test "macros cannot be called dynamically" do
5866
x = Nested
5967
assert_raise UndefinedFunctionError, fn -> x.value end

0 commit comments

Comments
 (0)