%% Module responsible for handling imports and conflicts %% between local functions and imports. %% For imports dispatch, please check elixir_dispatch. -module(elixir_import). -export([import/4, special_form/2, format_error/1]). -include("elixir.hrl"). import(Meta, Ref, Opts, E) -> {Functions, Macros, Added} = case keyfind(only, Opts) of {only, functions} -> {Added1, Funs} = import_functions(Meta, Ref, Opts, E), {Funs, keydelete(Ref, ?key(E, macros)), Added1}; {only, macros} -> {Added2, Macs} = import_macros(true, Meta, Ref, Opts, E), {keydelete(Ref, ?key(E, functions)), Macs, Added2}; {only, sigils} -> {Added1, Funs} = import_sigil_functions(Meta, Ref, Opts, E), {Added2, Macs} = import_sigil_macros(Meta, Ref, Opts, E), {Funs, Macs, Added1 or Added2}; {only, List} when is_list(List) -> {Added1, Funs} = import_functions(Meta, Ref, Opts, E), {Added2, Macs} = import_macros(false, Meta, Ref, Opts, E), {Funs, Macs, Added1 or Added2}; {only, Other} -> elixir_errors:file_error(Meta, E, ?MODULE, {invalid_option, only, Other}); false -> {Added1, Funs} = import_functions(Meta, Ref, Opts, E), {Added2, Macs} = import_macros(false, Meta, Ref, Opts, E), {Funs, Macs, Added1 or Added2} end, elixir_env:trace({import, [{imported, Added} | Meta], Ref, Opts}, E), {Functions, Macros}. import_functions(Meta, Ref, Opts, E) -> calculate(Meta, Ref, Opts, ?key(E, functions), ?key(E, file), fun() -> get_functions(Ref) end). import_macros(Force, Meta, Ref, Opts, E) -> calculate(Meta, Ref, Opts, ?key(E, macros), ?key(E, file), fun() -> case fetch_macros(Ref) of {ok, Macros} -> Macros; error when Force -> elixir_errors:file_error(Meta, E, ?MODULE, {no_macros, Ref}); error -> [] end end). import_sigil_functions(Meta, Ref, Opts, E) -> calculate(Meta, Ref, Opts, ?key(E, functions), ?key(E, file), fun() -> filter_sigils(get_functions(Ref)) end). import_sigil_macros(Meta, Ref, Opts, E) -> calculate(Meta, Ref, Opts, ?key(E, macros), ?key(E, file), fun() -> case fetch_macros(Ref) of {ok, Macros} -> filter_sigils(Macros); error -> [] end end). filter_sigils(Key) -> lists:filter(fun({Atom, _}) -> case atom_to_list(Atom) of "sigil_" ++ [L] when L >= $a, L =< $z; L >= $A, L =< $Z -> true; _ -> false end end, Key). %% Calculates the imports based on only and except calculate(Meta, Key, Opts, Old, File, Existing) -> New = case keyfind(only, Opts) of {only, DupOnly} when is_list(DupOnly) -> ensure_keyword_list(Meta, File, DupOnly, only), Only = ensure_no_duplicates(Meta, File, DupOnly, only), case keyfind(except, Opts) of false -> ok; _ -> elixir_errors:file_error(Meta, File, ?MODULE, only_and_except_given) end, [elixir_errors:file_warn(Meta, File, ?MODULE, {invalid_import, {Key, Name, Arity}}) || {Name, Arity} <- Only -- get_exports(Key)], intersection(Only, Existing()); _ -> case keyfind(except, Opts) of false -> remove_underscored(Existing()); {except, DupExcept} when is_list(DupExcept) -> ensure_keyword_list(Meta, File, DupExcept, except), Except = ensure_no_duplicates(Meta, File, DupExcept, except), %% We are not checking existence of exports listed in :except option %% on purpose: to support backwards compatible code. %% For example, "import String, except: [trim: 1]" %% should work across all Elixir versions. case keyfind(Key, Old) of false -> remove_underscored(Existing()) -- Except; {Key, OldImports} -> OldImports -- Except end; {except, Other} -> elixir_errors:file_error(Meta, File, ?MODULE, {invalid_option, except, Other}) end end, %% Normalize the data before storing it case ordsets:from_list(New) of [] -> {false, keydelete(Key, Old)}; Set -> ensure_no_special_form_conflict(Meta, File, Key, Set), {true, [{Key, Set} | keydelete(Key, Old)]} end. %% Retrieve functions and macros from modules get_exports(Module) -> get_functions(Module) ++ get_macros(Module). get_functions(Module) -> try Module:'__info__'(functions) catch error:undef -> remove_internals(Module:module_info(exports)) end. get_macros(Module) -> case fetch_macros(Module) of {ok, Macros} -> Macros; error -> [] end. fetch_macros(Module) -> try {ok, Module:'__info__'(macros)} catch error:undef -> error end. %% VALIDATION HELPERS ensure_no_special_form_conflict(Meta, File, Key, [{Name, Arity} | T]) -> case special_form(Name, Arity) of true -> elixir_errors:file_error(Meta, File, ?MODULE, {special_form_conflict, {Key, Name, Arity}}); false -> ensure_no_special_form_conflict(Meta, File, Key, T) end; ensure_no_special_form_conflict(_Meta, _File, _Key, []) -> ok. ensure_keyword_list(_Meta, _File, [], _Kind) -> ok; ensure_keyword_list(Meta, File, [{Key, Value} | Rest], Kind) when is_atom(Key), is_integer(Value) -> ensure_keyword_list(Meta, File, Rest, Kind); ensure_keyword_list(Meta, File, _Other, Kind) -> elixir_errors:file_error(Meta, File, ?MODULE, {invalid_option, Kind}). ensure_no_duplicates(Meta, File, Option, Kind) -> lists:foldl(fun({Name, Arity}, Acc) -> case lists:member({Name, Arity}, Acc) of true -> elixir_errors:file_warn(Meta, File, ?MODULE, {duplicated_import, {Kind, Name, Arity}}), Acc; false -> [{Name, Arity} | Acc] end end, [], Option). %% ERROR HANDLING format_error(only_and_except_given) -> ":only and :except can only be given together to import " "when :only is :functions, :macros, or :sigils"; format_error({duplicated_import, {Option, Name, Arity}}) -> io_lib:format("invalid :~s option for import, ~ts/~B is duplicated", [Option, Name, Arity]); format_error({invalid_import, {Receiver, Name, Arity}}) -> io_lib:format("cannot import ~ts.~ts/~B because it is undefined or private", [elixir_aliases:inspect(Receiver), Name, Arity]); format_error({invalid_option, Option}) -> Message = "invalid :~s option for import, expected a keyword list with integer values", io_lib:format(Message, [Option]); format_error({invalid_option, only, Value}) -> Message = "invalid :only option for import, expected value to be an atom :functions, :macros" ", or a list literal, got: ~s", io_lib:format(Message, ['Elixir.Macro':to_string(Value)]); format_error({invalid_option, except, Value}) -> Message = "invalid :except option for import, expected value to be a list literal, got: ~s", io_lib:format(Message, ['Elixir.Macro':to_string(Value)]); format_error({special_form_conflict, {Receiver, Name, Arity}}) -> io_lib:format("cannot import ~ts.~ts/~B because it conflicts with Elixir special forms", [elixir_aliases:inspect(Receiver), Name, Arity]); format_error({no_macros, Module}) -> io_lib:format("could not load macros from module ~ts", [elixir_aliases:inspect(Module)]). %% LIST HELPERS keyfind(Key, List) -> lists:keyfind(Key, 1, List). keydelete(Key, List) -> lists:keydelete(Key, 1, List). intersection([H | T], All) -> case lists:member(H, All) of true -> [H | intersection(T, All)]; false -> intersection(T, All) end; intersection([], _All) -> []. %% Internal funs that are never imported, and the like remove_underscored(List) -> lists:filter(fun({Name, _}) -> case atom_to_list(Name) of "_" ++ _ -> false; _ -> true end end, List). remove_internals(Set) -> Set -- [{behaviour_info, 1}, {module_info, 1}, {module_info, 0}]. %% Special forms special_form('&', 1) -> true; special_form('^', 1) -> true; special_form('=', 2) -> true; special_form('%', 2) -> true; special_form('|', 2) -> true; special_form('.', 2) -> true; special_form('::', 2) -> true; special_form('__block__', _) -> true; special_form('->', _) -> true; special_form('<<>>', _) -> true; special_form('{}', _) -> true; special_form('%{}', _) -> true; special_form('alias', 1) -> true; special_form('alias', 2) -> true; special_form('require', 1) -> true; special_form('require', 2) -> true; special_form('import', 1) -> true; special_form('import', 2) -> true; special_form('__ENV__', 0) -> true; special_form('__CALLER__', 0) -> true; special_form('__STACKTRACE__', 0) -> true; special_form('__MODULE__', 0) -> true; special_form('__DIR__', 0) -> true; special_form('__aliases__', _) -> true; special_form('quote', 1) -> true; special_form('quote', 2) -> true; special_form('unquote', 1) -> true; special_form('unquote_splicing', 1) -> true; special_form('fn', _) -> true; special_form('super', _) -> true; special_form('for', _) -> true; special_form('with', _) -> true; special_form('cond', 1) -> true; special_form('case', 2) -> true; special_form('try', 1) -> true; special_form('receive', 1) -> true; special_form(_, _) -> false.