-module(elixir_aliases). -export([inspect/1, last/1, concat/1, safe_concat/1, format_error/1, ensure_loaded/3, expand/2, expand_or_concat/2, store/5]). -include("elixir.hrl"). inspect(Atom) when is_atom(Atom) -> case elixir_config:is_bootstrap() of true -> atom_to_binary(Atom); false -> 'Elixir.Macro':inspect_atom(literal, Atom) end. %% Store an alias in the given scope store(Meta, New, New, _TOpts, #{aliases := Aliases, macro_aliases := MacroAliases}) -> {remove_alias(New, Aliases), remove_macro_alias(Meta, New, MacroAliases)}; store(Meta, New, Old, TOpts, #{aliases := Aliases, macro_aliases := MacroAliases} = E) -> elixir_env:trace({alias, Meta, Old, New, TOpts}, E), {store_alias(New, Old, Aliases), store_macro_alias(Meta, New, Old, MacroAliases)}. store_alias(New, Old, Aliases) -> lists:keystore(New, 1, Aliases, {New, Old}). store_macro_alias(Meta, New, Old, Aliases) -> case lists:keyfind(counter, 1, Meta) of {counter, Counter} -> lists:keystore(New, 1, Aliases, {New, {Counter, Old}}); false -> Aliases end. remove_alias(Atom, Aliases) -> lists:keydelete(Atom, 1, Aliases). remove_macro_alias(Meta, Atom, Aliases) -> case lists:keyfind(counter, 1, Meta) of {counter, _Counter} -> lists:keydelete(Atom, 1, Aliases); false -> Aliases end. %% Expand an alias. It returns an atom (meaning that there %% was an expansion) or a list of atoms. expand({'__aliases__', _Meta, ['Elixir' | _] = List}, _E) -> concat(List); expand({'__aliases__', Meta, _} = Alias, #{aliases := Aliases, macro_aliases := MacroAliases} = E) -> case lists:keyfind(alias, 1, Meta) of {alias, false} -> expand(Alias, MacroAliases, E); {alias, Atom} when is_atom(Atom) -> Atom; false -> expand(Alias, Aliases, E) end. expand({'__aliases__', Meta, [H | T]}, Aliases, E) when is_atom(H) -> Lookup = list_to_atom("Elixir." ++ atom_to_list(H)), Counter = case lists:keyfind(counter, 1, Meta) of {counter, C} -> C; _ -> nil end, case lookup(Lookup, Aliases, Counter) of Lookup -> [H | T]; Atom -> elixir_env:trace({alias_expansion, Meta, Lookup, Atom}, E), case T of [] -> Atom; _ -> concat([Atom | T]) end end; expand({'__aliases__', _Meta, List}, _Aliases, _E) -> List. %% Expands or concat if possible. expand_or_concat(Aliases, E) -> case expand(Aliases, E) of [H | T] when is_atom(H) -> concat([H | T]); AtomOrList -> AtomOrList end. %% Ensure a module is loaded before its usage. %% Skip Kernel verification for bootstrap purposes. ensure_loaded(_Meta, 'Elixir.Kernel', _E) -> ok; ensure_loaded(Meta, Module, #{module := Module} = E) -> elixir_errors:file_error(Meta, E, ?MODULE, {circular_module, Module}); ensure_loaded(Meta, Module, E) -> case code:ensure_loaded(Module) of {module, Module} -> ok; _ -> case wait_for_module(Module) of found -> ok; Wait -> Kind = case lists:member(Module, ?key(E, context_modules)) of true -> scheduled_module; false when Wait == deadlock -> deadlock_module; false -> unloaded_module end, elixir_errors:file_error(Meta, E, ?MODULE, {Kind, Module}) end end. wait_for_module(Module) -> case erlang:get(elixir_compiler_info) of undefined -> not_found; _ -> 'Elixir.Kernel.ErrorHandler':ensure_compiled(Module, module, hard) end. %% Receives an atom and returns the last bit as an alias. last(Atom) -> case atom_to_list(Atom) of ("Elixir." ++ [FirstLetter | _]) = List when FirstLetter >= $A, FirstLetter =< $Z -> Last = last(lists:reverse(List), []), {ok, list_to_atom("Elixir." ++ Last)}; _ -> error end. last([$. | _], Acc) -> Acc; last([H | T], Acc) -> last(T, [H | Acc]); last([], Acc) -> Acc. %% Receives a list of atoms, binaries or lists %% representing modules and concatenates them. concat(Args) -> binary_to_atom(do_concat(Args), utf8). safe_concat(Args) -> binary_to_existing_atom(do_concat(Args), utf8). do_concat([H | T]) when is_atom(H), H /= nil -> do_concat([atom_to_binary(H) | T]); do_concat([<<"Elixir.", _/binary>>=H | T]) -> do_concat(T, H); do_concat([<<"Elixir">>=H | T]) -> do_concat(T, H); do_concat(T) -> do_concat(T, <<"Elixir">>). do_concat([nil | T], Acc) -> do_concat(T, Acc); do_concat([H | T], Acc) when is_atom(H) -> do_concat(T, <>); do_concat([H | T], Acc) when is_binary(H) -> do_concat(T, <>); do_concat([], Acc) -> Acc. to_partial(<<"Elixir.", Arg/binary>>) -> Arg; to_partial(<<".", Arg/binary>>) -> Arg; to_partial(Arg) when is_binary(Arg) -> Arg. %% Lookup an alias in the current scope. lookup(Else, Dict, Counter) -> case lists:keyfind(Else, 1, Dict) of {Else, {Counter, Value}} -> lookup(Value, Dict, Counter); {Else, Value} when is_atom(Value) -> lookup(Value, Dict, Counter); _ -> Else end. %% Errors format_error({unloaded_module, Module}) -> io_lib:format("module ~ts is not loaded and could not be found", [inspect(Module)]); format_error({deadlock_module, Module}) -> io_lib:format("module ~ts is not loaded and could not be found. " "This may be happening because the module you are trying to load " "directly or indirectly depends on the current module", [inspect(Module)]); format_error({scheduled_module, Module}) -> io_lib:format( "module ~ts is not loaded but was defined. This happens when you depend on " "a module in the same context in which it is defined. For example:\n" "\n" " defmodule MyApp do\n" " defmodule Mod do\n" " end\n" "\n" " use Mod\n" " end\n" "\n" "Try defining the module outside the context that uses it:\n" "\n" " defmodule MyApp.Mod do\n" " end\n" "\n" " defmodule MyApp do\n" " use MyApp.Mod\n" " end\n" "\n" "If the module is defined at the top-level and you are trying to " "use it at the top-level, this is not supported by Elixir", [inspect(Module)]); format_error({circular_module, Module}) -> io_lib:format( "you are trying to use/import/require the module ~ts which is currently being defined.\n" "\n" "This may happen if you accidentally override the module you want to use. For example:\n" "\n" " defmodule MyApp do\n" " defmodule Supervisor do\n" " use Supervisor\n" " end\n" " end\n" "\n" "In the example above, the new Supervisor conflicts with Elixir's Supervisor. " "This may be fixed by using the fully qualified name in the definition:\n" "\n" " defmodule MyApp.Supervisor do\n" " use Supervisor\n" " end\n", [inspect(Module)]).