Skip to content

Commit a4d8fc2

Browse files
author
José Valim
committed
Ensure hygiene applies to function and partial applications
1 parent b96c3c5 commit a4d8fc2

File tree

5 files changed

+77
-43
lines changed

5 files changed

+77
-43
lines changed

lib/elixir/lib/macro.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -569,8 +569,8 @@ defmodule Macro do
569569
expand = :elixir_dispatch.expand_require(line, receiver, { right, length(args) },
570570
args, env.module, to_erl_env(env, cache))
571571
case expand do
572-
{ :ok, expanded } -> expanded
573-
{ :error, _ } -> original
572+
{ :ok, _receiver, expanded } -> expanded
573+
{ :error, _ } -> original
574574
end
575575
end
576576
end

lib/elixir/src/elixir_dispatch.erl

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ find_import(Meta, Name, Arity, S) ->
2525

2626
case find_dispatch(Meta, Tuple, S) of
2727
{ function, Receiver } -> Receiver;
28+
{ import, Receiver } -> Receiver;
2829
{ macro, Receiver } -> Receiver;
2930
nomatch -> false
3031
end.
@@ -39,6 +40,8 @@ import_function(Meta, Name, Arity, S) ->
3940
remote_function(Meta, Receiver, Name, Arity, S);
4041
{ macro, _Receiver } ->
4142
false;
43+
{ import, Receiver } ->
44+
require_function(Meta, Receiver, Name, Arity, S);
4245
nomatch ->
4346
{ { 'fun', ?line(Meta), { function, Name, Arity } }, S }
4447
end.
@@ -66,6 +69,8 @@ dispatch_import(Meta, Name, Args, S, Callback) ->
6669
false -> Receiver
6770
end,
6871
elixir_translator:translate_each({ { '.', Meta, [Endpoint, Name] }, Meta, Args }, S);
72+
{ import, Receiver } ->
73+
elixir_translator:translate_each({ { '.', Meta, [Receiver, Name] }, [{require,false}|Meta], Args }, S);
6974
Result ->
7075
case do_expand_import(Meta, Tuple, Args, Module, S, Result) of
7176
{ error, noexpansion } ->
@@ -85,14 +90,15 @@ dispatch_require(Meta, Receiver, Name, Args, S, Callback) ->
8590

8691
case (Receiver == ?BUILTIN) andalso is_element(Tuple, in_erlang_functions()) of
8792
true ->
88-
elixir_translator:translate_each({ { '.', Meta, [erlang, Name] }, Meta, Args }, S);
93+
{ TArgs, SA } = elixir_translator:translate_args(Args, S),
94+
{ ?wrap_call(?line(Meta), erlang, Name, TArgs), SA };
8995
false ->
9096
case expand_require(Meta, Receiver, Tuple, Args, Module, S) of
9197
{ error, noexpansion } ->
9298
Callback();
9399
{ error, internal } ->
94100
elixir_macros:translate({ Name, Meta, Args }, S);
95-
{ ok, Tree } ->
101+
{ ok, _Receiver, Tree } ->
96102
translate_expansion(Meta, Tree, S)
97103
end
98104
end.
@@ -115,6 +121,8 @@ do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
115121
{ macro, Receiver } ->
116122
elixir_import:record(import, Tuple, Receiver, Module),
117123
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
124+
{ import, Receiver } ->
125+
expand_require(Meta, Receiver, Tuple, Args, Module, S);
118126
_ ->
119127
Fun = (S#elixir_scope.function /= Tuple) andalso
120128
elixir_def_local:macro_for(Tuple, true, Module),
@@ -131,7 +139,7 @@ expand_require(Meta, ?BUILTIN, { Name, Arity } = Tuple, Args, Module, S) ->
131139
true -> { error, internal };
132140
false ->
133141
case is_element(Tuple, in_elixir_macros()) of
134-
true -> { ok, expand_macro_named(Meta, ?BUILTIN, Name, Arity, Args, Module, S) };
142+
true -> { ok, ?BUILTIN, expand_macro_named(Meta, ?BUILTIN, Name, Arity, Args, Module, S) };
135143
false -> { error, noexpansion }
136144
end
137145
end;
@@ -143,12 +151,12 @@ expand_require(Meta, Receiver, { Name, Arity } = Tuple, Args, Module, S) ->
143151
case Fun of
144152
false ->
145153
case is_element(Tuple, get_optional_macros(Receiver)) of
146-
true -> { ok, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
154+
true -> { ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
147155
false -> { error, noexpansion }
148156
end;
149157
_ ->
150158
elixir_import:record(import, Tuple, Receiver, Module),
151-
{ ok, expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S) }
159+
{ ok, Receiver, expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S) }
152160
end.
153161

154162
%% Expansion helpers
@@ -203,21 +211,26 @@ find_dispatch(Meta, Tuple, S) ->
203211
find_dispatch(Meta, Tuple, [], S).
204212

205213
find_dispatch(Meta, Tuple, Extra, S) ->
206-
Functions = S#elixir_scope.functions,
207-
Macros = Extra ++ S#elixir_scope.macros,
208-
File = S#elixir_scope.file,
209-
FunMatch = find_dispatch(Tuple, Functions),
210-
MacMatch = find_dispatch(Tuple, Macros),
211-
212-
case { FunMatch, MacMatch } of
213-
{ [], [Receiver] } -> { macro, Receiver };
214-
{ [Receiver], [] } -> { function, Receiver };
215-
{ [], [] } -> nomatch;
216-
_ ->
217-
{ Name, Arity } = Tuple,
218-
[First, Second|_] = FunMatch ++ MacMatch,
219-
Err = { ambiguous_call, { First, Second, Name, Arity } },
220-
elixir_errors:form_error(Meta, File, ?MODULE, Err)
214+
case lists:keyfind(import, 1, Meta) of
215+
{ import, _ } = Import ->
216+
Import;
217+
false ->
218+
Functions = S#elixir_scope.functions,
219+
Macros = Extra ++ S#elixir_scope.macros,
220+
File = S#elixir_scope.file,
221+
FunMatch = find_dispatch(Tuple, Functions),
222+
MacMatch = find_dispatch(Tuple, Macros),
223+
224+
case { FunMatch, MacMatch } of
225+
{ [], [Receiver] } -> { macro, Receiver };
226+
{ [Receiver], [] } -> { function, Receiver };
227+
{ [], [] } -> nomatch;
228+
_ ->
229+
{ Name, Arity } = Tuple,
230+
[First, Second|_] = FunMatch ++ MacMatch,
231+
Error = { ambiguous_call, { First, Second, Name, Arity } },
232+
elixir_errors:form_error(Meta, File, ?MODULE, Error)
233+
end
221234
end.
222235

223236
find_dispatch(Tuple, List) ->
@@ -252,7 +265,7 @@ remote_function(Meta, Receiver, Name, Arity, S) ->
252265
Line = ?line(Meta),
253266

254267
Final =
255-
case Receiver == ?BUILTIN andalso is_element({ Name, Arity }, in_erlang_functions()) of
268+
case (Receiver == ?BUILTIN) andalso is_element({ Name, Arity }, in_erlang_functions()) of
256269
true -> erlang;
257270
false -> Receiver
258271
end,

lib/elixir/src/elixir_quote.erl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,18 @@ do_quote({ quote, _, Args } = Tuple, #elixir_quote{unquote=true} = Q, S) when le
6565
do_quote({ unquote, _Meta, [Expr] }, #elixir_quote{unquote=true}, S) ->
6666
elixir_translator:translate_each(Expr, S);
6767

68+
do_quote({ function, Meta1, [{ '/', Meta2, [{F, Meta3, C}, A]}] } = Function,
69+
#elixir_quote{imports_hygiene=true} = Q, S) when is_atom(F), is_integer(A), is_atom(C) ->
70+
71+
case (lists:keyfind(import, 1, Meta3) == false) andalso
72+
elixir_dispatch:find_import(Meta3, F, A, S) of
73+
false ->
74+
do_quote_tuple(Function, Q, S);
75+
Receiver ->
76+
New = { function, Meta1, [{ '/', Meta2, [{F, [{import,Receiver}|Meta3], C}, A]}] },
77+
do_quote_tuple(New, Q, S)
78+
end;
79+
6880
do_quote({ 'alias!', _Meta, [Expr] }, Q, S) ->
6981
do_quote(Expr, Q#elixir_quote{aliases_hygiene=false}, S);
7082

lib/elixir/src/elixir_translator.erl

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -427,26 +427,21 @@ translate_each({ Atom, Meta, Args } = Original, S) when is_atom(Atom) ->
427427
false ->
428428
case elixir_partials:handle(Original, S) of
429429
error ->
430-
case lists:keyfind(import, 1, Meta) of
431-
{ import, Receiver } ->
432-
translate_each({ { '.', [{require,false}|Meta], [Receiver, Atom] }, Meta, Args }, S);
433-
false ->
434-
Callback = fun() ->
435-
case S#elixir_scope.context of
436-
match ->
437-
Arity = length(Args),
438-
File = S#elixir_scope.file,
439-
case Arity of
440-
0 -> syntax_error(Meta, File, "unknown variable ~ts or cannot invoke local ~ts/~B inside guard", [Atom, Atom, Arity]);
441-
_ -> syntax_error(Meta, File, "cannot invoke local ~ts/~B inside guard", [Atom, Arity])
442-
end;
443-
_ ->
444-
translate_local(Meta, Atom, Args, S)
445-
end
446-
end,
447-
448-
elixir_dispatch:dispatch_import(Meta, Atom, Args, S, Callback)
449-
end;
430+
Callback = fun() ->
431+
case S#elixir_scope.context of
432+
match ->
433+
Arity = length(Args),
434+
File = S#elixir_scope.file,
435+
case Arity of
436+
0 -> syntax_error(Meta, File, "unknown variable ~ts or cannot invoke local ~ts/~B inside guard", [Atom, Atom, Arity]);
437+
_ -> syntax_error(Meta, File, "cannot invoke local ~ts/~B inside guard", [Atom, Arity])
438+
end;
439+
_ ->
440+
translate_local(Meta, Atom, Args, S)
441+
end
442+
end,
443+
444+
elixir_dispatch:dispatch_import(Meta, Atom, Args, S, Callback);
450445
Else -> Else
451446
end;
452447
Else -> Else

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,9 +236,23 @@ defmodule Kernel.QuoteTest.ImportsHygieneTest do
236236
end
237237
end
238238

239+
defmacrop get_bin_size_with_partial do
240+
quote do
241+
size(&1).("hello")
242+
end
243+
end
244+
245+
defmacrop get_bin_size_with_function do
246+
quote do
247+
function(size/1).("hello")
248+
end
249+
end
250+
239251
test :expand_imports do
240252
import Kernel, except: [size: 1]
241253
assert get_bin_size == 5
254+
assert get_bin_size_with_partial == 5
255+
assert get_bin_size_with_function == 5
242256
end
243257

244258
defmacrop get_dict_size do

0 commit comments

Comments
 (0)