Skip to content

Commit f62c26b

Browse files
author
José Valim
committed
Ensure record aliases in the same quote can be accessed, closes #1915
This commit also simplifies macro expansion for both imports and aliases to always give higher preference to the macro environment rather than the caller one, if such is available. The deal is to simplify both the code and understanding of how macros work.
1 parent 1d7c56d commit f62c26b

File tree

14 files changed

+132
-187
lines changed

14 files changed

+132
-187
lines changed

lib/elixir/include/elixir.hrl

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,7 @@
3030
aliases, %% an orddict with aliases by new -> old names
3131
file, %% the current scope filename
3232
requires, %% a set with modules required
33-
macro_macros=[], %% a list with macros imported from module inside a macro
3433
macros, %% a list with macros imported from module
35-
macro_functions=[], %% a list with functions imported from module inside a macro
3634
functions %% a list with functions imported from module
3735
}).
3836

@@ -47,8 +45,7 @@
4745
functions,
4846
macros,
4947
macro_aliases=[],
50-
macro_functions=[],
51-
macro_macros=[],
48+
macro_counter=0,
5249
context_modules=[],
5350
vars=[],
5451
lexical_tracker=nil

lib/elixir/lib/kernel.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2787,7 +2787,7 @@ defmodule Kernel do
27872787

27882788
# Generate the alias for this module definition
27892789
{ new, old } = module_nesting(env_module(env), full)
2790-
meta = [defined: true] ++ alias_meta(alias)
2790+
meta = [defined: true, context: true] ++ alias_meta(alias)
27912791

27922792
{ full, { :alias, meta, [old, [as: new, warn: false]] } }
27932793
false ->
@@ -3775,7 +3775,7 @@ defmodule Kernel do
37753775
defp env_module(env), do: :erlang.element(2, env)
37763776
defp env_function(env), do: :erlang.element(5, env)
37773777
defp env_context(env), do: :erlang.element(6, env)
3778-
defp env_vars(env), do: :erlang.element(15, env)
3778+
defp env_vars(env), do: :erlang.element(14, env)
37793779

37803780
defp expand_compact([{ :compact, false }|t]), do: expand_compact(t)
37813781
defp expand_compact([{ :compact, true }|t]), do: [:compact|expand_compact(t)]

lib/elixir/lib/kernel/special_forms.ex

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -752,9 +752,8 @@ defmodule Kernel.SpecialForms do
752752
alias SomethingElse, as: D
753753
Hygiene.no_interference #=> #HashDict<[]>
754754
755-
In some particular cases you may want to access an alias
756-
or a module defined in the caller. In such scenarios, you
757-
can access it by using the `alias!` macro inside the quote:
755+
In some cases, you want to access an alias or a module defined
756+
in the caller. For such, you can use the `alias!` macro:
758757
759758
defmodule Hygiene do
760759
# This will expand to Elixir.Nested.hello

lib/elixir/lib/macro.ex

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,16 @@ defmodule Macro do
9898
9999
iex> Macro.decompose_call(quote do: foo)
100100
{ :foo, [] }
101+
101102
iex> Macro.decompose_call(quote do: foo())
102103
{ :foo, [] }
104+
103105
iex> Macro.decompose_call(quote do: foo(1, 2, 3))
104106
{ :foo, [1, 2, 3] }
105-
iex> Macro.decompose_call(quote do: M.N.foo(1, 2, 3))
106-
{ { :__aliases__, [alias: false], [:M, :N] }, :foo, [1, 2, 3] }
107+
108+
iex> Macro.decompose_call(quote do: Elixir.M.foo(1, 2, 3))
109+
{ { :__aliases__, [], [:Elixir, :M] }, :foo, [1, 2, 3] }
110+
107111
iex> Macro.decompose_call(quote do: 42)
108112
:error
109113

lib/elixir/lib/macro/env.ex

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,23 +28,25 @@ defmodule Macro.Env do
2828
@type file :: binary
2929
@type line :: non_neg_integer
3030
@type aliases :: [{ module, module }]
31+
@type macro_aliases :: [{ module, { integer, module } }]
3132
@type context :: :match | :guard | nil
3233
@type requires :: [module]
3334
@type functions :: [{ module, [name_arity] }]
3435
@type macros :: [{ module, [name_arity] }]
3536
@type context_modules :: [module]
3637
@type vars :: [{ atom, atom | non_neg_integer }]
3738
@type lexical_tracker :: pid
39+
@type counter :: non_neg_integer
3840

3941
fields = [:module, :file, :line, :function, :context, :requires, :aliases, :functions,
40-
:macros, :macro_aliases, :macro_functions, :macro_macros, :context_modules,
41-
:vars, :lexical_tracker]
42+
:macros, :macro_aliases, :macro_counter,
43+
:context_modules, :vars, :lexical_tracker]
4244

4345
types = quote do: [module: module, file: file, line: line,
4446
function: name_arity, context: context, requires: requires, aliases: aliases,
4547
functions: functions, macros: macros, macro_aliases: aliases,
46-
macro_functions: functions, macro_macros: macros,
47-
context_modules: context_modules, vars: vars, lexical_tracker: lexical_tracker]
48+
macro_counter: counter, context_modules: context_modules, vars: vars,
49+
lexical_tracker: lexical_tracker]
4850

4951
Record.deffunctions(fields, __MODULE__)
5052
Record.deftypes(fields, types, __MODULE__)

lib/elixir/src/elixir_aliases.erl

Lines changed: 33 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,18 @@ store(_Meta, New, New, _TKV, Aliases, MacroAliases, _Lexical) ->
88
{ Aliases, MacroAliases };
99
store(Meta, New, Old, TKV, Aliases, MacroAliases, Lexical) ->
1010
record_warn(Meta, New, TKV, Lexical),
11-
NewAliases = orddict:store(New, Old, Aliases),
12-
13-
case lists:keymember(context, 1, Meta) of
14-
true -> { NewAliases, orddict:store(New, Old, MacroAliases) };
15-
false -> { NewAliases, MacroAliases }
11+
{ store_alias(New, Old, Aliases),
12+
store_macro_alias(Meta, New, Old, MacroAliases) }.
13+
14+
store_alias(New, Old, Aliases) ->
15+
lists:keystore(New, 1, Aliases, { New, Old }).
16+
store_macro_alias(Meta, New, Old, Aliases) ->
17+
case lists:keymember(context, 1, Meta) andalso
18+
lists:keyfind(counter, 1, Meta) of
19+
{ counter, Counter } when is_integer(Counter) ->
20+
lists:keystore(New, 1, Aliases, { New, { Counter, Old } });
21+
_ ->
22+
Aliases
1623
end.
1724

1825
record_warn(Meta, Ref, Opts, Lexical) ->
@@ -27,48 +34,38 @@ record_warn(Meta, Ref, Opts, Lexical) ->
2734
%% Expand an alias. It returns an atom (meaning that there
2835
%% was an expansion) or a list of atoms.
2936

37+
expand({ '__aliases__', _Meta, ['Elixir'|_] = List }, _Aliases, _MacroAliases, _LexicalTracker) ->
38+
concat(List);
39+
3040
expand({ '__aliases__', Meta, _ } = Alias, Aliases, MacroAliases, LexicalTracker) ->
3141
case lists:keyfind(alias, 1, Meta) of
3242
{ alias, false } ->
3343
expand(Alias, MacroAliases, LexicalTracker);
3444
{ alias, Atom } when is_atom(Atom) ->
35-
case expand(Alias, MacroAliases, LexicalTracker) of
36-
OtherAtom when is_atom(OtherAtom) -> OtherAtom;
37-
OtherAliases when is_list(OtherAliases) -> Atom
38-
end;
45+
Atom;
3946
false ->
4047
expand(Alias, Aliases, LexicalTracker)
4148
end.
4249

43-
expand({ '__aliases__', _Meta, [H] }, Aliases, LexicalTracker) when H /= 'Elixir' ->
44-
case expand_one(H, Aliases, LexicalTracker) of
45-
false -> [H];
46-
Atom -> Atom
47-
end;
48-
49-
expand({ '__aliases__', _Meta, [H|T] }, Aliases, LexicalTracker) when is_atom(H) ->
50-
case H of
51-
'Elixir' ->
52-
concat(T);
53-
_ ->
54-
case expand_one(H, Aliases, LexicalTracker) of
55-
false -> [H|T];
56-
Atom -> concat([Atom|T])
50+
expand({ '__aliases__', Meta, [H|T] }, Aliases, LexicalTracker) when is_atom(H) ->
51+
Lookup = list_to_atom("Elixir." ++ atom_to_list(H)),
52+
Counter = case lists:keyfind(counter, 1, Meta) of
53+
{ counter, C } -> C;
54+
_ -> nil
55+
end,
56+
case lookup(Lookup, Aliases, Counter) of
57+
Lookup -> [H|T];
58+
Atom ->
59+
elixir_lexical:record_alias(Lookup, LexicalTracker),
60+
case T of
61+
[] -> Atom;
62+
_ -> concat([Atom|T])
5763
end
5864
end;
5965

6066
expand({ '__aliases__', _Meta, List }, _Aliases, _LexicalTracker) ->
6167
List.
6268

63-
expand_one(H, Aliases, LexicalTracker) ->
64-
Lookup = list_to_atom("Elixir." ++ atom_to_list(H)),
65-
case lookup(Lookup, Aliases) of
66-
Lookup -> false;
67-
Else ->
68-
elixir_lexical:record_alias(Lookup, LexicalTracker),
69-
Else
70-
end.
71-
7269
%% Ensure a module is loaded before its usage.
7370

7471
ensure_loaded(Line, Ref, S) ->
@@ -129,9 +126,10 @@ to_partial(Arg) when is_binary(Arg) -> Arg.
129126

130127
%% Lookup an alias in the current scope.
131128

132-
lookup(Else, Dict) ->
133-
case orddict:find(Else, Dict) of
134-
{ ok, Value } when Value /= Else -> lookup(Value, Dict);
129+
lookup(Else, Dict, Counter) ->
130+
case lists:keyfind(Else, 1, Dict) of
131+
{ Else, { Counter, Value } } -> lookup(Value, Dict, Counter);
132+
{ Else, Value } when is_atom(Value) -> lookup(Value, Dict, Counter);
135133
_ -> Else
136134
end.
137135

lib/elixir/src/elixir_dispatch.erl

Lines changed: 5 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -218,29 +218,15 @@ translate_expansion(Meta, Receiver, Tree, S) ->
218218
New = S#elixir_scope.macro_counter + 1,
219219

220220
try
221-
{ TE, TS } = elixir_translator:translate_each(
221+
elixir_translator:translate_each(
222222
elixir_quote:linify_with_context_counter(Line, { Receiver, New }, Tree),
223223
S#elixir_scope{macro_counter=New}
224-
),
225-
{ TE,
226-
TS#elixir_scope{
227-
macro_macros=S#elixir_scope.macro_macros,
228-
macro_aliases=merge_aliases(TS, S),
229-
macro_functions=S#elixir_scope.macro_functions
230-
} }
224+
)
231225
catch
232226
Kind:Reason ->
233227
erlang:raise(Kind, Reason, prune_stacktrace(mfa(Line, S), erlang:get_stacktrace(), nil))
234228
end.
235229

236-
%% We only keep aliases from module definitions.
237-
%% All others are discarded straight-away.
238-
merge_aliases(#elixir_scope{context_modules=Context, macro_aliases=New},
239-
#elixir_scope{macro_aliases=Old}) ->
240-
ContextAliases = [{ N, O } || { N, O } <- New, lists:member(O, Context)],
241-
lists:foldl(fun({ N, O }, Acc) -> orddict:store(N, O, Acc) end,
242-
ContextAliases, Old).
243-
244230
mfa(Line, #elixir_scope{module=nil} = S) ->
245231
{ elixir_compiler, '__FILE__', 2, location(Line, S) };
246232

@@ -262,7 +248,7 @@ find_dispatch(Meta, Tuple, S) ->
262248
find_dispatch(Meta, Tuple, [], S).
263249

264250
find_dispatch(Meta, Tuple, Extra, S) ->
265-
case is_import(Meta, Tuple, S) of
251+
case is_import(Meta) of
266252
{ import, _ } = Import ->
267253
Import;
268254
false ->
@@ -287,29 +273,18 @@ find_dispatch(Meta, Tuple, Extra, S) ->
287273
find_dispatch(Tuple, List) ->
288274
[Receiver || { Receiver, Set } <- List, is_element(Tuple, Set)].
289275

290-
is_import(Meta, Tuple, S) ->
276+
is_import(Meta) ->
291277
case lists:keyfind(import, 1, Meta) of
292278
{ import, _ } = Import ->
293279
case lists:keyfind(context, 1, Meta) of
294-
{ context, Context } ->
295-
not_an_import(Tuple, Context, S#elixir_scope.macro_functions)
296-
andalso not_an_import(Tuple, Context, S#elixir_scope.macro_macros)
297-
andalso Import;
280+
{ context, _ } -> Import;
298281
false ->
299282
false
300283
end;
301284
false ->
302285
false
303286
end.
304287

305-
not_an_import(Tuple, Context, Dict) ->
306-
case orddict:find(Context, Dict) of
307-
{ ok, Pairs } ->
308-
not lists:any(fun({ _, List }) -> lists:member(Tuple, List) end, Pairs);
309-
error ->
310-
true
311-
end.
312-
313288
%% We've reached the invoked macro, skip it with the rest
314289
prune_stacktrace(Info, [{ _, _, [S|_], _ }|_], S) ->
315290
[Info];

lib/elixir/src/elixir_env.erl

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,13 @@ ex_to_env(Env) when element(1, Env) == 'Elixir.Macro.Env' ->
1111
env_to_scope(#elixir_env{module=Module,file=File,
1212
function=Function,aliases=Aliases,context=Context,
1313
requires=Requires,macros=Macros,functions=Functions,
14-
macro_functions=MacroFunctions,macro_macros=MacroMacros,
1514
context_modules=ContextModules,macro_aliases=MacroAliases,
16-
lexical_tracker=Lexical}) ->
15+
macro_counter=MacroCounter,lexical_tracker=Lexical}) ->
1716
#elixir_scope{module=Module,file=File,
1817
function=Function,aliases=Aliases,context=Context,
1918
requires=Requires,macros=Macros,functions=Functions,
20-
macro_functions=MacroFunctions,macro_macros=MacroMacros,
2119
context_modules=ContextModules,macro_aliases=MacroAliases,
22-
lexical_tracker=Lexical}.
20+
macro_counter=MacroCounter,lexical_tracker=Lexical}.
2321

2422
env_to_scope_with_vars(#elixir_env{} = Env, Vars) ->
2523
(env_to_scope(Env))#elixir_scope{
@@ -31,10 +29,9 @@ scope_to_ex({ Line, #elixir_scope{module=Module,file=File,
3129
function=Function,aliases=Aliases,context=Context,
3230
requires=Requires,macros=Macros,functions=Functions,
3331
context_modules=ContextModules,macro_aliases=MacroAliases,
34-
macro_functions=MacroFunctions, macro_macros=MacroMacros,
35-
vars=Vars,lexical_tracker=Lexical} }) when is_integer(Line) ->
32+
macro_counter=MacroCounter,vars=Vars,lexical_tracker=Lexical} }) when is_integer(Line) ->
3633
{ 'Elixir.Macro.Env', Module, File, Line, Function, Context, Requires, Aliases,
37-
Functions, Macros, MacroAliases, MacroFunctions, MacroMacros, ContextModules,
34+
Functions, Macros, MacroAliases, MacroCounter, ContextModules,
3835
[Pair || { Pair, _ } <- Vars], Lexical }.
3936

4037
ex_to_scope(Env) ->

lib/elixir/src/elixir_import.erl

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ import(Meta, Ref, Opts, S) ->
2323
SI.
2424

2525
import_functions(Meta, Ref, Opts, S) ->
26-
{ Functions, Temp } = calculate(Meta, Ref, Opts, S#elixir_scope.functions,
27-
S#elixir_scope.macro_functions, fun() -> get_functions(Ref) end, S),
28-
S#elixir_scope{functions=Functions, macro_functions=Temp}.
26+
Functions = calculate(Meta, Ref, Opts, S#elixir_scope.functions,
27+
fun() -> get_functions(Ref) end, S),
28+
S#elixir_scope{functions=Functions}.
2929

3030
import_macros(Force, Meta, Ref, Opts, S) ->
3131
Existing = fun() ->
@@ -34,9 +34,8 @@ import_macros(Force, Meta, Ref, Opts, S) ->
3434
false -> get_optional_macros(Ref)
3535
end
3636
end,
37-
{ Macros, Temp } = calculate(Meta, Ref, Opts, S#elixir_scope.macros,
38-
S#elixir_scope.macro_macros, Existing, S),
39-
S#elixir_scope{macros=Macros, macro_macros=Temp}.
37+
Macros = calculate(Meta, Ref, Opts, S#elixir_scope.macros, Existing, S),
38+
S#elixir_scope{macros=Macros}.
4039

4140
record_warn(Meta, Ref, Opts, S) ->
4241
Warn =
@@ -49,7 +48,7 @@ record_warn(Meta, Ref, Opts, S) ->
4948

5049
%% Calculates the imports based on only and except
5150

52-
calculate(Meta, Key, Opts, Old, Temp, Existing, S) ->
51+
calculate(Meta, Key, Opts, Old, Existing, S) ->
5352
New = case keyfind(only, Opts) of
5453
{ only, Only } when is_list(Only) ->
5554
case Only -- get_exports(Key) of
@@ -77,24 +76,10 @@ calculate(Meta, Key, Opts, Old, Temp, Existing, S) ->
7776

7877
case Final of
7978
[] ->
80-
{ keydelete(Key, Old),
81-
if_quoted(Meta, Temp, fun(Value) -> keydelete(Key, Value) end) };
79+
keydelete(Key, Old);
8280
_ ->
8381
ensure_no_special_form_conflict(Meta, S#elixir_scope.file, Key, Final),
84-
{ [{ Key, Final }|keydelete(Key, Old)],
85-
if_quoted(Meta, Temp, fun(Value) -> [{ Key, Final }|keydelete(Key, Value)] end) }
86-
end.
87-
88-
if_quoted(Meta, Temp, Callback) ->
89-
case lists:keyfind(context, 1, Meta) of
90-
{ context, Context } ->
91-
Current = case orddict:find(Context, Temp) of
92-
{ ok, Value } -> Value;
93-
error -> []
94-
end,
95-
orddict:store(Context, Callback(Current), Temp);
96-
_ ->
97-
Temp
82+
[{ Key, Final }|keydelete(Key, Old)]
9883
end.
9984

10085
%% Retrieve functions and macros from modules

0 commit comments

Comments
 (0)