Skip to content

Commit c125636

Browse files
author
José Valim
committed
Ensure the macro system respects imports hygiene, closes #875
1 parent 531bd8d commit c125636

File tree

5 files changed

+62
-19
lines changed

5 files changed

+62
-19
lines changed

lib/elixir/include/elixir.hrl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@
2828
aliases, %% an orddict with aliases by new -> old names
2929
file, %% the current scope filename
3030
requires, %% a set with modules required
31-
macro_macros, %% a list with macros imported from module inside a macro
31+
macro_macros=[], %% a list with macros imported from module inside a macro
3232
macros, %% a list with macros imported from module
33-
macro_functions, %% a list with functions imported from module inside a macro
33+
macro_functions=[], %% a list with functions imported from module inside a macro
3434
functions %% a list with functions imported from module
3535
}).
3636

lib/elixir/src/elixir_dispatch.erl

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,16 +191,24 @@ translate_expansion(Meta, Tree, S) ->
191191
elixir_quote:linify(?line(Meta), Tree),
192192
S#elixir_scope{
193193
check_clauses=false,
194-
macro_aliases=[] }
194+
macro_functions=[],
195+
macro_macros=[],
196+
macro_aliases=[]
197+
}
195198
),
196199
{ TR, TS#elixir_scope{
197200
check_clauses=S#elixir_scope.check_clauses,
201+
macro_functions=merge_imports(S#elixir_scope.macro_functions, TS#elixir_scope.macro_functions),
202+
macro_macros=merge_imports(S#elixir_scope.macro_macros, TS#elixir_scope.macro_macros),
198203
macro_aliases=merge_aliases(S#elixir_scope.macro_aliases, TS#elixir_scope.macro_aliases)
199204
} }.
200205

201206
merge_aliases(A1, A2) ->
202207
orddict:merge(fun(_K,_V1,V2) -> V2 end, A1, A2).
203208

209+
merge_imports(A1, A2) ->
210+
A2 ++ lists:foldl(fun({ Key, _ }, Acc) -> lists:keydelete(Key, 1, Acc) end, A1, A2).
211+
204212
%% Helpers
205213

206214
skip_require(Meta) ->
@@ -210,7 +218,7 @@ find_dispatch(Meta, Tuple, S) ->
210218
find_dispatch(Meta, Tuple, [], S).
211219

212220
find_dispatch(Meta, Tuple, Extra, S) ->
213-
case lists:keyfind(import, 1, Meta) of
221+
case is_import(Meta, Tuple, S) of
214222
{ import, _ } = Import ->
215223
Import;
216224
false ->
@@ -235,6 +243,19 @@ find_dispatch(Meta, Tuple, Extra, S) ->
235243
find_dispatch(Tuple, List) ->
236244
[Receiver || { Receiver, Set } <- List, is_element(Tuple, Set)].
237245

246+
is_import(Meta, Tuple, S) ->
247+
case lists:keyfind(import, 1, Meta) of
248+
{ import, _ } = Import ->
249+
not_an_import(Tuple, S#elixir_scope.macro_functions)
250+
andalso not_an_import(Tuple, S#elixir_scope.macro_macros)
251+
andalso Import;
252+
false ->
253+
false
254+
end.
255+
256+
not_an_import(Tuple, Pairs) ->
257+
not lists:any(fun({ _, List }) -> lists:member(Tuple, List) end, Pairs).
258+
238259
munge_stacktrace(Info, [{ _, _, [S|_], _ }|_], S) ->
239260
[Info];
240261

lib/elixir/src/elixir_import.erl

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ import(Meta, Ref, Opts, Selector, S) ->
4141
false -> S;
4242
true ->
4343
FunctionsFun = fun(K) -> remove_underscored(K andalso Selector, get_functions(Ref)) end,
44-
Functions = calculate(Meta, Ref, Opts,
45-
S#elixir_scope.functions, FunctionsFun, S),
46-
S#elixir_scope{functions=Functions}
44+
{ Functions, TempF } = calculate(Meta, Ref, Opts,
45+
S#elixir_scope.functions, S#elixir_scope.macro_functions, FunctionsFun, S),
46+
S#elixir_scope{functions=Functions, macro_functions=TempF}
4747
end,
4848

4949
SM = case IncludeAll or (Selector == macros) of
@@ -55,9 +55,9 @@ import(Meta, Ref, Opts, Selector, S) ->
5555
false -> get_macros(Meta, Ref, SF)
5656
end
5757
end,
58-
Macros = calculate(Meta, Ref, Opts,
59-
SF#elixir_scope.macros, MacrosFun, SF),
60-
SF#elixir_scope{macros=Macros}
58+
{ Macros, TempM } = calculate(Meta, Ref, Opts,
59+
SF#elixir_scope.macros, SF#elixir_scope.macro_macros, MacrosFun, SF),
60+
SF#elixir_scope{macros=Macros, macro_macros=TempM}
6161
end,
6262

6363
SM.
@@ -66,9 +66,8 @@ import(Meta, Ref, Opts, Selector, S) ->
6666

6767
%% Calculates the imports based on only and except
6868

69-
calculate(Meta, Key, Opts, Old, AvailableFun, S) ->
69+
calculate(Meta, Key, Opts, Old, Temp, AvailableFun, S) ->
7070
File = S#elixir_scope.file,
71-
All = keydelete(Key, Old),
7271

7372
New = case keyfind(only, Opts) of
7473
{ only, RawOnly } ->
@@ -88,20 +87,20 @@ calculate(Meta, Key, Opts, Old, AvailableFun, S) ->
8887
Except = expand_fun_arity(Meta, except, RawExcept, S),
8988
case keyfind(Key, Old) of
9089
false -> AvailableFun(true) -- Except;
91-
{Key,ToRemove} -> ToRemove -- Except
90+
{Key,OldImports} -> OldImports -- Except
9291
end
9392
end
9493
end,
9594

9695
%% Normalize the data before storing it
97-
Set = ordsets:from_list(New),
98-
Final = remove_internals(Set),
96+
Set = ordsets:from_list(New),
97+
Final = remove_internals(Set),
9998

10099
case Final of
101-
[] -> All;
100+
[] -> { keydelete(Key, Old), keydelete(Key, Temp) };
102101
_ ->
103102
ensure_no_in_erlang_macro_conflict(Meta, File, Key, Final, internal_conflict),
104-
[{ Key, Final }|All]
103+
{ [{ Key, Final }|keydelete(Key, Old)], [{ Key, Final }|keydelete(Key, Temp)] }
105104
end.
106105

107106
%% Ensure we are expanding macros and stuff

lib/elixir/src/elixir_scope.erl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ serialize(S) ->
9393
elixir_tree_helpers:abstract_syntax(
9494
{ S#elixir_scope.file, S#elixir_scope.functions, S#elixir_scope.check_clauses,
9595
S#elixir_scope.requires, S#elixir_scope.macros, S#elixir_scope.aliases,
96-
S#elixir_scope.macro_aliases, S#elixir_scope.scheduled }
96+
S#elixir_scope.macro_functions, S#elixir_scope.macro_macros, S#elixir_scope.macro_aliases,
97+
S#elixir_scope.scheduled }
9798
).
9899

99100
serialize_with_vars(Line, S) when is_integer(Line) ->
@@ -111,14 +112,17 @@ serialize_with_vars(Line, S) when is_integer(Line) ->
111112

112113
deserialize(Tuple) -> deserialize_with_vars(Tuple, []).
113114

114-
deserialize_with_vars({ File, Functions, CheckClauses, Requires, Macros, Aliases, MacroAliases, Scheduled }, Vars) ->
115+
deserialize_with_vars({ File, Functions, CheckClauses, Requires, Macros,
116+
Aliases, MacroFunctions, MacroMacros, MacroAliases, Scheduled }, Vars) ->
115117
#elixir_scope{
116118
file=File,
117119
functions=Functions,
118120
check_clauses=CheckClauses,
119121
requires=Requires,
120122
macros=Macros,
121123
aliases=Aliases,
124+
macro_functions=MacroFunctions,
125+
macro_macros=MacroMacros,
122126
macro_aliases=MacroAliases,
123127
scheduled=Scheduled,
124128
vars=orddict:from_list(Vars),

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,4 +275,23 @@ defmodule Kernel.QuoteTest.ImportsHygieneTest do
275275
import Dict, only: [size: 1]
276276
assert get_dict_size == 2
277277
end
278+
279+
defmacrop with_size do
280+
quote do
281+
import Kernel, except: [size: 1]
282+
import Dict, only: [size: 1]
283+
size([a: 1, b: 2])
284+
end
285+
end
286+
287+
defmacrop with_nested_size do
288+
quote do
289+
with_size
290+
end
291+
end
292+
293+
test :explicitly_overriden_imports do
294+
assert with_size == 2
295+
assert with_nested_size == 2
296+
end
278297
end

0 commit comments

Comments
 (0)