1-module(elixir_aliases).
2-export([inspect/1, last/1, concat/1, safe_concat/1, format_error/1,
3         ensure_loaded/3, expand/2, store/5]).
4-include("elixir.hrl").
5
6inspect(Atom) when is_atom(Atom) ->
7  case elixir_config:get(bootstrap) of
8    true  -> atom_to_binary(Atom, utf8);
9    false -> 'Elixir.Code.Identifier':inspect_as_atom(Atom)
10  end.
11
12%% Store an alias in the given scope
13store(Meta, New, New, _TOpts, #{aliases := Aliases, macro_aliases := MacroAliases}) ->
14  {remove_alias(New, Aliases), remove_macro_alias(Meta, New, MacroAliases)};
15store(Meta, New, Old, TOpts, #{aliases := Aliases, macro_aliases := MacroAliases} = E) ->
16  elixir_env:trace({alias, Meta, Old, New, TOpts}, E),
17  {store_alias(New, Old, Aliases), store_macro_alias(Meta, New, Old, MacroAliases)}.
18
19store_alias(New, Old, Aliases) ->
20  lists:keystore(New, 1, Aliases, {New, Old}).
21
22store_macro_alias(Meta, New, Old, Aliases) ->
23  case lists:keyfind(counter, 1, Meta) of
24    {counter, Counter} ->
25      lists:keystore(New, 1, Aliases, {New, {Counter, Old}});
26    false ->
27      Aliases
28  end.
29
30remove_alias(Atom, Aliases) ->
31  lists:keydelete(Atom, 1, Aliases).
32
33remove_macro_alias(Meta, Atom, Aliases) ->
34  case lists:keyfind(counter, 1, Meta) of
35    {counter, _Counter} ->
36      lists:keydelete(Atom, 1, Aliases);
37    false ->
38      Aliases
39  end.
40
41%% Expand an alias. It returns an atom (meaning that there
42%% was an expansion) or a list of atoms.
43
44expand({'__aliases__', _Meta, ['Elixir' | _] = List}, _E) ->
45  concat(List);
46
47expand({'__aliases__', Meta, _} = Alias, #{aliases := Aliases, macro_aliases := MacroAliases} = E) ->
48  case lists:keyfind(alias, 1, Meta) of
49    {alias, false} ->
50      expand(Alias, MacroAliases, E);
51    {alias, Atom} when is_atom(Atom) ->
52      Atom;
53    false ->
54      expand(Alias, Aliases, E)
55  end.
56
57expand({'__aliases__', Meta, [H | T]}, Aliases, E) when is_atom(H) ->
58  Lookup  = list_to_atom("Elixir." ++ atom_to_list(H)),
59  Counter = case lists:keyfind(counter, 1, Meta) of
60    {counter, C} -> C;
61    _ -> nil
62  end,
63  case lookup(Lookup, Aliases, Counter) of
64    Lookup -> [H | T];
65    Atom ->
66      elixir_env:trace({alias_expansion, Meta, Lookup, Atom}, E),
67      case T of
68        [] -> Atom;
69        _  -> concat([Atom | T])
70      end
71  end;
72
73expand({'__aliases__', _Meta, List}, _Aliases, _E) ->
74  List.
75
76%% Ensure a module is loaded before its usage.
77
78ensure_loaded(_Meta, 'Elixir.Kernel', _E) -> ok;
79ensure_loaded(Meta, Module, E) ->
80  case code:ensure_loaded(Module) of
81    {module, Module} ->
82      ok;
83
84    _ ->
85      case wait_for_module(Module) of
86        found ->
87          ok;
88
89        Wait ->
90          Kind = case lists:member(Module, ?key(E, context_modules)) of
91            true ->
92              case ?key(E, module) of
93                Module -> circular_module;
94                _ -> scheduled_module
95              end;
96            false when Wait == deadlock ->
97              deadlock_module;
98            false ->
99              unloaded_module
100          end,
101
102          elixir_errors:form_error(Meta, E, ?MODULE, {Kind, Module})
103      end
104  end.
105
106wait_for_module(Module) ->
107  case is_pid(erlang:get(elixir_compiler_pid)) of
108    true -> 'Elixir.Kernel.ErrorHandler':ensure_compiled(Module, module, hard);
109    false -> not_found
110  end.
111
112%% Receives an atom and returns the last bit as an alias.
113
114last(Atom) ->
115  Last = last(lists:reverse(atom_to_list(Atom)), []),
116  list_to_atom("Elixir." ++ Last).
117
118last([$. | _], Acc) -> Acc;
119last([H | T], Acc)  -> last(T, [H | Acc]);
120last([], Acc)       -> Acc.
121
122%% Receives a list of atoms, binaries or lists
123%% representing modules and concatenates them.
124
125concat(Args)      -> binary_to_atom(do_concat(Args), utf8).
126safe_concat(Args) -> binary_to_existing_atom(do_concat(Args), utf8).
127
128do_concat([H | T]) when is_atom(H), H /= nil ->
129  do_concat([atom_to_binary(H, utf8) | T]);
130do_concat([<<"Elixir.", _/binary>>=H | T]) ->
131  do_concat(T, H);
132do_concat([<<"Elixir">>=H | T]) ->
133  do_concat(T, H);
134do_concat(T) ->
135  do_concat(T, <<"Elixir">>).
136
137do_concat([nil | T], Acc) ->
138  do_concat(T, Acc);
139do_concat([H | T], Acc) when is_atom(H) ->
140  do_concat(T, <<Acc/binary, $., (to_partial(atom_to_binary(H, utf8)))/binary>>);
141do_concat([H | T], Acc) when is_binary(H) ->
142  do_concat(T, <<Acc/binary, $., (to_partial(H))/binary>>);
143do_concat([], Acc) ->
144  Acc.
145
146to_partial(<<"Elixir.", Arg/binary>>) -> Arg;
147to_partial(<<".", Arg/binary>>)       -> Arg;
148to_partial(Arg) when is_binary(Arg)   -> Arg.
149
150%% Lookup an alias in the current scope.
151
152lookup(Else, Dict, Counter) ->
153  case lists:keyfind(Else, 1, Dict) of
154    {Else, {Counter, Value}} -> lookup(Value, Dict, Counter);
155    {Else, Value} when is_atom(Value) -> lookup(Value, Dict, Counter);
156    _ -> Else
157  end.
158
159%% Errors
160
161format_error({unloaded_module, Module}) ->
162  io_lib:format("module ~ts is not loaded and could not be found", [inspect(Module)]);
163
164format_error({deadlock_module, Module}) ->
165  io_lib:format("module ~ts is not loaded and could not be found. "
166                "This may be happening because the module you are trying to load "
167                "directly or indirectly depends on the current module",
168                [inspect(Module)]);
169
170format_error({scheduled_module, Module}) ->
171  io_lib:format(
172    "module ~ts is not loaded but was defined. This happens when you depend on "
173    "a module in the same context in which it is defined. For example:\n"
174    "\n"
175    "    defmodule MyApp do\n"
176    "      defmodule Mod do\n"
177    "      end\n"
178    "\n"
179    "      use Mod\n"
180    "    end\n"
181    "\n"
182    "Try defining the module outside the context that uses it:\n"
183    "\n"
184    "    defmodule MyApp.Mod do\n"
185    "    end\n"
186    "\n"
187    "    defmodule MyApp do\n"
188    "      use MyApp.Mod\n"
189    "    end\n"
190    "\n"
191    "If the module is defined at the top-level and you are trying to "
192    "use it at the top-level, this is not supported by Elixir",
193    [inspect(Module)]);
194
195format_error({circular_module, Module}) ->
196  io_lib:format(
197    "you are trying to use the module ~ts which is currently being defined.\n"
198    "\n"
199    "This may happen if you accidentally override the module you want to use. For example:\n"
200    "\n"
201    "    defmodule MyApp do\n"
202    "      defmodule Supervisor do\n"
203    "        use Supervisor\n"
204    "      end\n"
205    "    end\n"
206    "\n"
207    "In the example above, the new Supervisor conflicts with Elixir's Supervisor. "
208    "This may be fixed by using the fully qualified name in the definition:\n"
209    "\n"
210    "    defmodule MyApp.Supervisor do\n"
211    "      use Supervisor\n"
212    "    end\n",
213    [inspect(Module)]).
214