Skip to content

Commit 6d62983

Browse files
author
José Valim
committed
Capture now works with macros, closes #1534
1 parent 2e8cb67 commit 6d62983

File tree

2 files changed

+39
-28
lines changed

2 files changed

+39
-28
lines changed

lib/elixir/src/elixir_fn.erl

+32-28
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ translate_fn_match(Arg, S) ->
2525
{ TArg, TS } = elixir_translator:translate(Arg, S#elixir_scope{extra=fn_match}),
2626
{ TArg, TS#elixir_scope{extra=S#elixir_scope.extra} }.
2727

28-
capture(Meta, { '/', _, [{ { '.', _, [M, F] }, _ , [] }, A] }, S) when is_atom(F), is_integer(A) ->
29-
{ [MF, FF, AF], SF } = elixir_translator:translate_args([M, F, A], S),
30-
{ { 'fun', ?line(Meta), { function, MF, FF, AF } }, SF };
28+
capture(Meta, { '/', _, [{ { '.', _, [_, F] } = Dot, RequireMeta , [] }, A] }, S) when is_atom(F), is_integer(A) ->
29+
Args = [{ '&', [], [X] } || X <- lists:seq(1, A)],
30+
capture_require(Meta, { Dot, RequireMeta, Args }, S, true);
3131

3232
capture(Meta, { '/', _, [{ F, _, C }, A] }, S) when is_atom(F), is_integer(A), is_atom(C) ->
33-
WrappedMeta =
33+
ImportMeta =
3434
case lists:keyfind(import_fa, 1, Meta) of
3535
{ import_fa, { Receiver, Context } } ->
3636
lists:keystore(context, 1,
@@ -39,32 +39,18 @@ capture(Meta, { '/', _, [{ F, _, C }, A] }, S) when is_atom(F), is_integer(A), i
3939
);
4040
false -> Meta
4141
end,
42+
Args = [{ '&', [], [X] } || X <- lists:seq(1, A)],
43+
capture_import(Meta, { F, ImportMeta, Args }, S, true);
4244

43-
case elixir_dispatch:import_function(WrappedMeta, F, A, S) of
44-
false -> compile_error(WrappedMeta, S#elixir_scope.file,
45-
"expected ~ts/~B to be a function, but it is a macro", [F, A]);
46-
Else -> Else
47-
end;
48-
49-
capture(Meta, { { '.', _, [Left, Right] }, RemoteMeta, Args } = Expr, S) when is_atom(Right), is_list(Args) ->
50-
{ Mod, SE } = 'Elixir.Macro':expand_all(Left, elixir_scope:to_ex_env({ ?line(Meta), S }), S),
51-
52-
case is_atom(Mod) andalso is_sequential(Args) andalso
53-
elixir_dispatch:require_function(RemoteMeta, Mod, Right, length(Args), SE) of
54-
false -> do_capture(Meta, Expr, S);
55-
Else -> Else
56-
end;
45+
capture(Meta, { { '.', _, [_, Fun] }, _, Args } = Expr, S) when is_atom(Fun), is_list(Args) ->
46+
capture_require(Meta, Expr, S, is_sequential_and_not_empty(Args));
5747

5848
capture(Meta, { '__block__', _, _ } = Expr, S) ->
5949
Message = "invalid args for &, block expressions are not allowed, got: ~ts",
6050
syntax_error(Meta, S#elixir_scope.file, Message, ['Elixir.Macro':to_string(Expr)]);
6151

62-
capture(Meta, { Atom, ImportMeta, Args } = Expr, S) when is_atom(Atom), is_list(Args) ->
63-
case is_sequential(Args) andalso
64-
elixir_dispatch:import_function(ImportMeta, Atom, length(Args), S) of
65-
false -> do_capture(Meta, Expr, S);
66-
Else -> Else
67-
end;
52+
capture(Meta, { Atom, _, Args } = Expr, S) when is_atom(Atom), is_list(Args) ->
53+
capture_import(Meta, Expr, S, is_sequential_and_not_empty(Args));
6854

6955
capture(Meta, { Left, Right }, S) ->
7056
capture(Meta, { '{}', Meta, [Left, Right] }, S);
@@ -80,9 +66,25 @@ capture(Meta, Arg, S) ->
8066

8167
%% Helpers
8268

83-
do_capture(Meta, Expr, S) ->
69+
capture_import(Meta, { Atom, ImportMeta, Args } = Expr, S, Sequential) ->
70+
case Sequential andalso
71+
elixir_dispatch:import_function(ImportMeta, Atom, length(Args), S) of
72+
false -> do_capture(Meta, Expr, S, Sequential);
73+
Else -> Else
74+
end.
75+
76+
capture_require(Meta, { { '.', _, [Left, Right] }, RequireMeta, Args } = Expr, S, Sequential) ->
77+
{ Mod, SE } = 'Elixir.Macro':expand_all(Left, elixir_scope:to_ex_env({ ?line(Meta), S }), S),
78+
79+
case Sequential andalso is_atom(Mod) andalso
80+
elixir_dispatch:require_function(RequireMeta, Mod, Right, length(Args), SE) of
81+
false -> do_capture(Meta, Expr, S, Sequential);
82+
Else -> Else
83+
end.
84+
85+
do_capture(Meta, Expr, S, Sequential) ->
8486
case do_escape(Expr, S, []) of
85-
{ _, _, [] } ->
87+
{ _, _, [] } when not Sequential ->
8688
invalid_capture(Meta, Expr, S);
8789
{ TExpr, TS, TDict } ->
8890
TVars = validate(Meta, TDict, 1, S),
@@ -139,8 +141,10 @@ do_escape_list([H|T], S, Dict, Acc) ->
139141
do_escape_list([], S, Dict, Acc) ->
140142
{ lists:reverse(Acc), S, Dict }.
141143

142-
is_sequential(List) -> is_sequential(List, 1).
144+
is_sequential_and_not_empty([]) -> false;
145+
is_sequential_and_not_empty(List) -> is_sequential(List, 1).
146+
143147
is_sequential([{ '&', _, [Int] }|T], Int) ->
144148
is_sequential(T, Int + 1);
145-
is_sequential([], Int) when Int > 1 -> true;
149+
is_sequential([], _Int) -> true;
146150
is_sequential(_, _Int) -> false.

lib/elixir/test/elixir/kernel/fn_test.exs

+7
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ defmodule Kernel.FnTest do
3232
assert &atom_to_list(&1) == &atom_to_list/1
3333
end
3434

35+
test "capture macro" do
36+
assert (&to_binary/1).(:a) == "a"
37+
assert (&to_binary(&1)).(:a) == "a"
38+
assert (&Kernel.to_binary/1).(:a) == "a"
39+
assert (&Kernel.to_binary(&1)).(:a) == "a"
40+
end
41+
3542
test "local partial application" do
3643
assert (&atb(&1, :utf8)).(:a) == "a"
3744
assert (&atb(list_to_atom(&1), :utf8)).('a') == "a"

0 commit comments

Comments
 (0)