This means the second item in the AST is no longer an integer (representing the line), but a keywords list. Code that relies on the line information from AST or that manually generate AST nodes need to be properly updated.
326 lines
9.4 KiB
Erlang
326 lines
9.4 KiB
Erlang
%% Module responsible for handling imports and conflicts.
|
|
%% For imports dispatch, please check elixir_dispatch.
|
|
-module(elixir_import).
|
|
-export([import/5, recorded_locals/1, format_error/1,
|
|
ensure_no_import_conflict/4, ensure_no_local_conflict/4,
|
|
build_table/1, delete_table/1, record/4]).
|
|
-include("elixir.hrl").
|
|
|
|
table(Module) -> ?atom_concat([i, Module]).
|
|
|
|
build_table(Module) ->
|
|
ets:new(table(Module), [set, named_table, public]).
|
|
|
|
delete_table(Module) ->
|
|
ets:delete(table(Module)).
|
|
|
|
record(_Kind, _Tuple, _Receiver, nil) ->
|
|
false;
|
|
|
|
record(import, Tuple, Receiver, Module) ->
|
|
try
|
|
ets:insert(table(Module), { Tuple, Receiver })
|
|
catch
|
|
error:badarg -> false
|
|
end.
|
|
|
|
recorded_locals(Module) ->
|
|
Table = table(Module),
|
|
Match = { '$1', Module },
|
|
Result = ets:match(Table, Match),
|
|
ets:match_delete(Table, Match),
|
|
lists:append(Result).
|
|
|
|
%% Update the scope to consider the imports for aliases
|
|
%% based on the given options and selector.
|
|
|
|
import(Meta, Ref, Opts, Selector, S) ->
|
|
IncludeAll = (Selector == all) or (Selector == default),
|
|
|
|
SF = case IncludeAll or (Selector == functions) of
|
|
false -> S;
|
|
true ->
|
|
FunctionsFun = fun(K) -> remove_underscored(K andalso Selector, get_functions(Ref)) end,
|
|
Functions = calculate(Meta, Ref, Opts,
|
|
S#elixir_scope.functions, FunctionsFun, S),
|
|
S#elixir_scope{functions=Functions}
|
|
end,
|
|
|
|
SM = case IncludeAll or (Selector == macros) of
|
|
false -> SF;
|
|
true ->
|
|
MacrosFun = fun(K) ->
|
|
case IncludeAll of
|
|
true -> remove_underscored(K andalso Selector, get_optional_macros(Ref));
|
|
false -> get_macros(Meta, Ref, SF)
|
|
end
|
|
end,
|
|
Macros = calculate(Meta, Ref, Opts,
|
|
SF#elixir_scope.macros, MacrosFun, SF),
|
|
SF#elixir_scope{macros=Macros}
|
|
end,
|
|
|
|
SM.
|
|
|
|
%% IMPORT FUNCTION RELATED HELPERS
|
|
|
|
%% Calculates the imports based on only and except
|
|
|
|
calculate(Meta, Key, Opts, Old, AvailableFun, S) ->
|
|
File = S#elixir_scope.file,
|
|
All = keydelete(Key, Old),
|
|
|
|
New = case keyfind(only, Opts) of
|
|
{ only, RawOnly } ->
|
|
Only = expand_fun_arity(Meta, only, RawOnly, S),
|
|
case Only -- get_exports(Key) of
|
|
[{Name,Arity}|_] ->
|
|
Tuple = { invalid_import, { Key, Name, Arity } },
|
|
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
|
|
_ ->
|
|
intersection(Only, AvailableFun(false))
|
|
end;
|
|
false ->
|
|
case keyfind(except, Opts) of
|
|
false -> AvailableFun(true);
|
|
{ except, [] } -> AvailableFun(true);
|
|
{ except, RawExcept } ->
|
|
Except = expand_fun_arity(Meta, except, RawExcept, S),
|
|
case keyfind(Key, Old) of
|
|
false -> AvailableFun(true) -- Except;
|
|
{Key,ToRemove} -> ToRemove -- Except
|
|
end
|
|
end
|
|
end,
|
|
|
|
%% Normalize the data before storing it
|
|
Set = ordsets:from_list(New),
|
|
Final = remove_internals(Set),
|
|
|
|
case Final of
|
|
[] -> All;
|
|
_ ->
|
|
ensure_no_conflicts(Meta, File, Final, keydelete(Key, S#elixir_scope.macros)),
|
|
ensure_no_conflicts(Meta, File, Final, keydelete(Key, S#elixir_scope.functions)),
|
|
ensure_no_in_erlang_macro_conflict(Meta, File, Key, Final, internal_conflict),
|
|
[{ Key, Final }|All]
|
|
end.
|
|
|
|
%% Ensure we are expanding macros and stuff
|
|
|
|
expand_fun_arity(Meta, Kind, Value, S) ->
|
|
{ TValue, _S } = elixir_translator:translate_each(Value, S),
|
|
cons_to_keywords(Meta, Kind, TValue, S).
|
|
|
|
cons_to_keywords(Meta, Kind, { cons, _, Left, { nil, _ } }, S) ->
|
|
[tuple_to_fun_arity(Meta, Kind, Left, S)];
|
|
|
|
cons_to_keywords(Meta, Kind, { cons, _, Left, Right }, S) ->
|
|
[tuple_to_fun_arity(Meta, Kind, Left, S)|cons_to_keywords(Meta, Kind, Right, S)];
|
|
|
|
cons_to_keywords(Meta, Kind, _, S) ->
|
|
elixir_errors:syntax_error(Meta, S#elixir_scope.file,
|
|
"invalid value for :~s, expected a list with functions and arities", [Kind]).
|
|
|
|
tuple_to_fun_arity(_Meta, _Kind, { tuple, _, [{ atom, _, Atom }, { integer, _, Integer }] }, _S) ->
|
|
{ Atom, Integer };
|
|
|
|
tuple_to_fun_arity(Meta, Kind, _, S) ->
|
|
elixir_errors:syntax_error(Meta, S#elixir_scope.file,
|
|
"invalid value for :~s, expected a list with functions and arities", [Kind]).
|
|
|
|
%% Retrieve functions and macros from modules
|
|
|
|
get_exports(Module) ->
|
|
try
|
|
Module:'__info__'(functions) ++ Module:'__info__'(macros)
|
|
catch
|
|
error:undef -> Module:module_info(exports)
|
|
end.
|
|
|
|
get_functions(Module) ->
|
|
try
|
|
Module:'__info__'(functions)
|
|
catch
|
|
error:undef -> Module:module_info(exports)
|
|
end.
|
|
|
|
get_macros(Meta, Module, S) ->
|
|
try
|
|
Module:'__info__'(macros)
|
|
catch
|
|
error:undef ->
|
|
Tuple = { no_macros, Module },
|
|
elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, Tuple)
|
|
end.
|
|
|
|
get_optional_macros(Module) ->
|
|
case code:ensure_loaded(Module) of
|
|
{ module, Module } ->
|
|
try
|
|
Module:'__info__'(macros)
|
|
catch
|
|
error:undef -> []
|
|
end;
|
|
{ error, _ } -> []
|
|
end.
|
|
|
|
%% VALIDATION HELPERS
|
|
|
|
%% Check if any of the locals defined conflicts with an invoked
|
|
%% Elixir "implemented in Erlang" macro. Checking if a local
|
|
%% conflicts with an import is automatically done by Erlang.
|
|
|
|
ensure_no_local_conflict(Meta, File, Module, AllDefined) ->
|
|
ensure_no_in_erlang_macro_conflict(Meta, File, Module, AllDefined, local_conflict).
|
|
|
|
%% Find conlicts in the given list of functions with
|
|
%% the recorded set of imports.
|
|
|
|
ensure_no_import_conflict(Meta, File, Module, AllDefined) ->
|
|
Table = table(Module),
|
|
Matches = [X || X <- AllDefined, ets:member(Table, X)],
|
|
|
|
case Matches of
|
|
[{Name,Arity}|_] ->
|
|
Key = ets:lookup_element(Table, {Name, Arity }, 2),
|
|
Tuple = { import_conflict, { Key, Name, Arity } },
|
|
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
|
|
[] ->
|
|
ok
|
|
end.
|
|
|
|
%% Ensure the given functions don't clash with any
|
|
%% of Elixir non overridable macros.
|
|
|
|
ensure_no_in_erlang_macro_conflict(Meta, File, Key, [{Name,Arity}|T], Reason) ->
|
|
Values = lists:filter(fun({X,Y}) ->
|
|
(Name == X) andalso ((Y == '*') orelse (Y == Arity))
|
|
end, non_overridable_macros()),
|
|
|
|
case Values /= [] of
|
|
true ->
|
|
Tuple = { Reason, { Key, Name, Arity } },
|
|
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
|
|
false -> ensure_no_in_erlang_macro_conflict(Meta, File, Key, T, Reason)
|
|
end;
|
|
|
|
ensure_no_in_erlang_macro_conflict(_Meta, _File, _Key, [], _) -> ok.
|
|
|
|
%% Find conlicts in the given list of functions with the set of imports.
|
|
%% Used internally to ensure a newly imported fun or macro does not
|
|
%% conflict with an already imported set.
|
|
|
|
ensure_no_conflicts(Meta, File, Functions, [{Key,Value}|T]) ->
|
|
Filtered = lists:filter(fun(X) -> lists:member(X, Functions) end, Value),
|
|
case Filtered of
|
|
[{Name,Arity}|_] ->
|
|
Tuple = { already_imported, { Key, Name, Arity } },
|
|
elixir_errors:form_error(Meta, File, ?MODULE, Tuple);
|
|
[] ->
|
|
ensure_no_conflicts(Meta, File, Functions, T)
|
|
end;
|
|
|
|
ensure_no_conflicts(_Meta, _File, _Functions, _S) -> ok.
|
|
|
|
%% ERROR HANDLING
|
|
|
|
format_error({already_imported,{Receiver, Name, Arity}}) ->
|
|
io_lib:format("function ~s/~B already imported from ~s", [Name, Arity, elixir_errors:inspect(Receiver)]);
|
|
|
|
format_error({invalid_import,{Receiver, Name, Arity}}) ->
|
|
io_lib:format("cannot import ~s.~s/~B because it doesn't exist",
|
|
[elixir_errors:inspect(Receiver), Name, Arity]);
|
|
|
|
format_error({import_conflict,{Receiver, Name, Arity}}) ->
|
|
io_lib:format("imported ~s.~s/~B conflicts with local function",
|
|
[elixir_errors:inspect(Receiver), Name, Arity]);
|
|
|
|
format_error({local_conflict,{_, Name, Arity}}) ->
|
|
io_lib:format("cannot define local ~s/~B because it conflicts with Elixir special forms", [Name, Arity]);
|
|
|
|
format_error({internal_conflict,{Receiver, Name, Arity}}) ->
|
|
io_lib:format("cannot import ~s.~s/~B because it conflicts with Elixir special forms",
|
|
[elixir_errors:inspect(Receiver), Name, Arity]);
|
|
|
|
format_error({ no_macros, Module }) ->
|
|
io_lib:format("could not load macros from module ~s", [elixir_errors: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) -> [].
|
|
|
|
%% INTROSPECTION
|
|
|
|
%% Internal funs that are never imported etc.
|
|
|
|
remove_underscored(default, List) -> remove_underscored(List);
|
|
remove_underscored(_, List) -> List.
|
|
|
|
remove_underscored([{ Name, _ } = H|T]) when Name < a ->
|
|
case atom_to_list(Name) of
|
|
[$_, $_, _, $_, $_] -> [H|remove_underscored(T)];
|
|
"_" ++ _ -> remove_underscored(T);
|
|
_ -> [H|remove_underscored(T)]
|
|
end;
|
|
|
|
remove_underscored(T) ->
|
|
T.
|
|
|
|
remove_internals(Set) ->
|
|
ordsets:del_element({ module_info, 1 },
|
|
ordsets:del_element({ module_info, 0 }, Set)).
|
|
|
|
%% Macros implemented in Erlang that are not overridable.
|
|
|
|
non_overridable_macros() ->
|
|
[
|
|
{'^',1},
|
|
{'=',2},
|
|
{'__op__',2},
|
|
{'__op__',3},
|
|
{'__ambiguousop__','*'},
|
|
{'__scope__',2},
|
|
{'__block__','*'},
|
|
{'->','2'},
|
|
{'<<>>','*'},
|
|
{'{}','*'},
|
|
{'[]','*'},
|
|
{'alias',1},
|
|
{'alias',2},
|
|
{'require',1},
|
|
{'require',2},
|
|
{'import',1},
|
|
{'import',2},
|
|
{'import',3},
|
|
{'__ENV__',0},
|
|
{'__CALLER__',0},
|
|
{'__MODULE__',0},
|
|
{'__FILE__',0},
|
|
{'__aliases__','*'},
|
|
{'quote',1},
|
|
{'quote',2},
|
|
{'unquote',1},
|
|
{'unquote_splicing',1},
|
|
{'fn','*'},
|
|
{'super','*'},
|
|
{'super?',0},
|
|
{'bc','*'},
|
|
{'lc','*'},
|
|
{'var!',1},
|
|
{'var!',2},
|
|
{'alias!',1}
|
|
].
|