516 lines
15 KiB
Erlang
516 lines
15 KiB
Erlang
%% Helpers related to dispatching to imports and references.
|
|
%% This module access the information stored on the scope
|
|
%% by elixir_import and therefore assumes it is normalized (ordsets)
|
|
-module(elixir_dispatch).
|
|
-export([default_macros/0, default_functions/0, default_requires/0,
|
|
dispatch_require/6, dispatch_import/5,
|
|
require_function/5, import_function/4,
|
|
expand_import/6, expand_require/6, find_import/4,
|
|
format_error/1, in_erlang_functions/0, in_erlang_macros/0]).
|
|
-include("elixir.hrl").
|
|
|
|
-import(ordsets, [is_element/2]).
|
|
-define(builtin, 'Elixir.Kernel').
|
|
|
|
default_functions() ->
|
|
[ { ?builtin, elixir_imported_functions() } ].
|
|
default_macros() ->
|
|
[ { ?builtin, elixir_imported_macros() } ].
|
|
default_requires() ->
|
|
[ ?builtin, 'Elixir.Kernel.Typespec', 'Elixir.Record' ].
|
|
|
|
find_import(Meta, Name, Arity, S) ->
|
|
Tuple = { Name, Arity },
|
|
|
|
case find_dispatch(Meta, Tuple, S) of
|
|
{ function, Receiver } ->
|
|
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
|
Receiver;
|
|
{ macro, Receiver } ->
|
|
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
|
Receiver;
|
|
_ ->
|
|
false
|
|
end.
|
|
|
|
%% Function retrieval
|
|
|
|
import_function(Meta, Name, Arity, S) ->
|
|
Tuple = { Name, Arity },
|
|
case find_dispatch(Meta, Tuple, S) of
|
|
{ function, Receiver } ->
|
|
elixir_tracker:record_import(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
|
remote_function(Meta, Receiver, Name, Arity, S);
|
|
{ macro, _Receiver } ->
|
|
false;
|
|
{ import, Receiver } ->
|
|
require_function(Meta, Receiver, Name, Arity, S);
|
|
false ->
|
|
case elixir_import:special_form(Name, Arity) of
|
|
true -> false;
|
|
false ->
|
|
elixir_tracker:record_local(Tuple, S#elixir_scope.module, S#elixir_scope.function),
|
|
{ { 'fun', ?line(Meta), { function, Name, Arity } }, S }
|
|
end
|
|
end.
|
|
|
|
require_function(Meta, Receiver, Name, Arity, S) ->
|
|
Tuple = { Name, Arity },
|
|
|
|
case is_element(Tuple, get_optional_macros(Receiver)) of
|
|
true -> false;
|
|
false ->
|
|
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
|
remote_function(Meta, Receiver, Name, Arity, S)
|
|
end.
|
|
|
|
%% Function dispatch
|
|
|
|
dispatch_import(Meta, Name, Args, S, Callback) ->
|
|
Module = S#elixir_scope.module,
|
|
Arity = length(Args),
|
|
Tuple = { Name, Arity },
|
|
|
|
case find_dispatch(Meta, Tuple, S) of
|
|
{ function, Receiver } ->
|
|
elixir_tracker:record_import(Tuple, Receiver, Module, S#elixir_scope.function),
|
|
Endpoint = case (Receiver == ?builtin) andalso is_element(Tuple, in_erlang_functions()) of
|
|
true -> erlang;
|
|
false -> Receiver
|
|
end,
|
|
elixir_translator:translate_each({ { '.', Meta, [Endpoint, Name] }, Meta, Args }, S);
|
|
{ import, Receiver } ->
|
|
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
|
elixir_translator:translate_each({ { '.', Meta, [Receiver, Name] }, [{require,false}|Meta], Args }, S);
|
|
Result ->
|
|
case do_expand_import(Meta, Tuple, Args, Module, S, Result) of
|
|
{ error, noexpansion } ->
|
|
Callback();
|
|
{ error, internal } ->
|
|
elixir_tracker:record_import(Tuple, ?builtin, Module, S#elixir_scope.function),
|
|
elixir_macros:translate({ Name, Meta, Args }, S);
|
|
{ ok, _Receiver, Tree } ->
|
|
translate_expansion(Meta, Tree, S)
|
|
end
|
|
end.
|
|
|
|
dispatch_require(Meta, Receiver, Name, Args, S, Callback) ->
|
|
Module = S#elixir_scope.module,
|
|
Arity = length(Args),
|
|
Tuple = { Name, Arity },
|
|
|
|
case (Receiver == ?builtin) andalso is_element(Tuple, in_erlang_functions()) of
|
|
true ->
|
|
elixir_tracker:record_remote(Tuple, Receiver, S#elixir_scope.module, S#elixir_scope.function),
|
|
{ TArgs, SA } = elixir_translator:translate_args(Args, S),
|
|
{ ?wrap_call(?line(Meta), erlang, Name, TArgs), SA };
|
|
false ->
|
|
case expand_require(Meta, Receiver, Tuple, Args, Module, S) of
|
|
{ error, noexpansion } ->
|
|
Callback();
|
|
{ error, internal } ->
|
|
elixir_tracker:record_remote(Tuple, ?builtin, S#elixir_scope.module, S#elixir_scope.function),
|
|
elixir_macros:translate({ Name, Meta, Args }, S);
|
|
{ ok, _Receiver, Tree } ->
|
|
translate_expansion(Meta, Tree, S)
|
|
end
|
|
end.
|
|
|
|
%% Macros expansion
|
|
|
|
expand_import(Meta, Tuple, Args, Module, Extra, S) ->
|
|
Result = find_dispatch(Meta, Tuple, Extra, S),
|
|
do_expand_import(Meta, Tuple, Args, Module, S, Result).
|
|
|
|
do_expand_import(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
|
|
Function = S#elixir_scope.function,
|
|
|
|
Fun = (Function /= nil) andalso (Function /= Tuple) andalso
|
|
elixir_def_local:macro_for(Tuple, true, S),
|
|
|
|
case Fun of
|
|
false ->
|
|
do_expand_import_no_local(Meta, Tuple, Args, Module, S, Result);
|
|
_ ->
|
|
case Result of
|
|
{ Kind, Receiver } when Kind == import; Receiver == Module ->
|
|
do_expand_import_no_local(Meta, Tuple, Args, Module, S, Result);
|
|
{ _, Receiver } ->
|
|
Error = { macro_conflict, { Receiver, Name, Arity } },
|
|
elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, Error);
|
|
false ->
|
|
elixir_tracker:record_local(Tuple, Module, S#elixir_scope.function),
|
|
{ ok, Module, expand_macro_fun(Meta, Fun(), Module, Name, Args, Module, S) }
|
|
end
|
|
end.
|
|
|
|
do_expand_import_no_local(Meta, { Name, Arity } = Tuple, Args, Module, S, Result) ->
|
|
case Result of
|
|
{ macro, ?builtin } ->
|
|
case is_element(Tuple, in_erlang_macros()) of
|
|
true -> { error, internal };
|
|
false ->
|
|
elixir_tracker:record_import(Tuple, ?builtin, Module, S#elixir_scope.function),
|
|
{ ok, ?builtin, expand_macro_named(Meta, ?builtin, Name, Arity, Args, Module, S) }
|
|
end;
|
|
{ macro, Receiver } ->
|
|
elixir_tracker:record_import(Tuple, Receiver, Module, S#elixir_scope.function),
|
|
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
|
|
{ import, Receiver } ->
|
|
expand_require(Meta, Receiver, Tuple, Args, Module, S);
|
|
_ ->
|
|
{ error, noexpansion }
|
|
end.
|
|
|
|
expand_require(Meta, ?builtin, { Name, Arity } = Tuple, Args, Module, S) ->
|
|
case is_element(Tuple, in_erlang_macros()) of
|
|
true -> { error, internal };
|
|
false ->
|
|
case is_element(Tuple, in_elixir_macros()) of
|
|
true ->
|
|
elixir_tracker:record_remote(Tuple, ?builtin, S#elixir_scope.module, S#elixir_scope.function),
|
|
{ ok, ?builtin, expand_macro_named(Meta, ?builtin, Name, Arity, Args, Module, S) };
|
|
false ->
|
|
{ error, noexpansion }
|
|
end
|
|
end;
|
|
|
|
expand_require(Meta, Receiver, { Name, Arity } = Tuple, Args, Module, S) ->
|
|
Function = S#elixir_scope.function,
|
|
|
|
Fun = (Module == Receiver) andalso (Function /= nil) andalso
|
|
(Function /= Tuple) andalso elixir_def_local:macro_for(Tuple, false, S),
|
|
|
|
case Fun of
|
|
false ->
|
|
case is_element(Tuple, get_optional_macros(Receiver)) of
|
|
true ->
|
|
elixir_tracker:record_remote(Tuple, Receiver, Module, Function),
|
|
{ ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) };
|
|
false -> { error, noexpansion }
|
|
end;
|
|
_ ->
|
|
elixir_tracker:record_local(Tuple, Module, Function),
|
|
{ ok, Receiver, expand_macro_fun(Meta, Fun(), Receiver, Name, Args, Module, S) }
|
|
end.
|
|
|
|
%% Expansion helpers
|
|
|
|
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S) ->
|
|
Requires = S#elixir_scope.requires,
|
|
case (Receiver == Module) or is_element(Receiver, Requires) or skip_require(Meta) of
|
|
true -> ok;
|
|
false ->
|
|
Tuple = { unrequired_module, { Receiver, Name, length(Args), Requires } },
|
|
elixir_errors:form_error(Meta, S#elixir_scope.file, ?MODULE, Tuple)
|
|
end,
|
|
|
|
Line = ?line(Meta),
|
|
SArg = {Line,S},
|
|
|
|
try
|
|
apply(Fun, [SArg|Args])
|
|
catch
|
|
Kind:Reason ->
|
|
Info = { Receiver, Name, length(Args), location(Line, S) },
|
|
erlang:raise(Kind, Reason, munge_stacktrace(Info, erlang:get_stacktrace(), SArg))
|
|
end.
|
|
|
|
expand_macro_named(Meta, Receiver, Name, Arity, Args, Module, S) ->
|
|
ProperName = ?elixir_macro(Name),
|
|
ProperArity = Arity + 1,
|
|
Fun = fun Receiver:ProperName/ProperArity,
|
|
expand_macro_fun(Meta, Fun, Receiver, Name, Args, Module, S).
|
|
|
|
translate_expansion(Meta, Tree, S) ->
|
|
Line = ?line(Meta),
|
|
|
|
try
|
|
elixir_translator:translate_each(
|
|
elixir_quote:linify(Line, Tree),
|
|
S
|
|
)
|
|
catch
|
|
Kind:Reason ->
|
|
erlang:raise(Kind, Reason, munge_stacktrace(mfa(Line, S), erlang:get_stacktrace(), nil))
|
|
end.
|
|
|
|
mfa(Line, #elixir_scope{module=nil} = S) ->
|
|
{ elixir_compiler, '__FILE__', 2, location(Line, S) };
|
|
|
|
mfa(Line, #elixir_scope{module=Module,function=nil} = S) ->
|
|
{ Module, '__MODULE__', 0, location(Line, S) };
|
|
|
|
mfa(Line, #elixir_scope{module=Module,function={ Name, Arity }} = S) ->
|
|
{ Module, Name, Arity, location(Line, S) }.
|
|
|
|
location(Line, S) ->
|
|
[{ file, S#elixir_scope.file }, { line, Line }].
|
|
|
|
%% Helpers
|
|
|
|
skip_require(Meta) ->
|
|
lists:keyfind(require, 1, Meta) == { require, false }.
|
|
|
|
find_dispatch(Meta, Tuple, S) ->
|
|
find_dispatch(Meta, Tuple, [], S).
|
|
|
|
find_dispatch(Meta, Tuple, Extra, S) ->
|
|
case is_import(Meta, Tuple, S) of
|
|
{ import, _ } = Import ->
|
|
Import;
|
|
false ->
|
|
Functions = S#elixir_scope.functions,
|
|
Macros = Extra ++ S#elixir_scope.macros,
|
|
File = S#elixir_scope.file,
|
|
FunMatch = find_dispatch(Tuple, Functions),
|
|
MacMatch = find_dispatch(Tuple, Macros),
|
|
|
|
case { FunMatch, MacMatch } of
|
|
{ [], [Receiver] } -> { macro, Receiver };
|
|
{ [Receiver], [] } -> { function, Receiver };
|
|
{ [], [] } -> false;
|
|
_ ->
|
|
{ Name, Arity } = Tuple,
|
|
[First, Second|_] = FunMatch ++ MacMatch,
|
|
Error = { ambiguous_call, { First, Second, Name, Arity } },
|
|
elixir_errors:form_error(Meta, File, ?MODULE, Error)
|
|
end
|
|
end.
|
|
|
|
find_dispatch(Tuple, List) ->
|
|
[Receiver || { Receiver, Set } <- List, is_element(Tuple, Set)].
|
|
|
|
is_import(Meta, Tuple, S) ->
|
|
case lists:keyfind(import, 1, Meta) of
|
|
{ import, _ } = Import ->
|
|
case lists:keyfind(context, 1, Meta) of
|
|
{ context, Context } ->
|
|
not_an_import(Tuple, Context, S#elixir_scope.macro_functions)
|
|
andalso not_an_import(Tuple, Context, S#elixir_scope.macro_macros)
|
|
andalso Import;
|
|
false ->
|
|
false
|
|
end;
|
|
false ->
|
|
false
|
|
end.
|
|
|
|
not_an_import(Tuple, Context, Dict) ->
|
|
case orddict:find(Context, Dict) of
|
|
{ ok, Pairs } ->
|
|
not lists:any(fun({ _, List }) -> lists:member(Tuple, List) end, Pairs);
|
|
error ->
|
|
true
|
|
end.
|
|
|
|
%% We've reached the invoked macro, skip it with the rest
|
|
munge_stacktrace(Info, [{ _, _, [S|_], _ }|_], S) ->
|
|
[Info];
|
|
|
|
%% We've reached the elixir_dispatch internals, skip it with the rest
|
|
munge_stacktrace(Info, [{ elixir_dispatch, _, _, _ }|_], _) ->
|
|
[Info];
|
|
|
|
munge_stacktrace(Info, [H|T], S) ->
|
|
[H|munge_stacktrace(Info, T, S)];
|
|
|
|
munge_stacktrace(Info, [], _) ->
|
|
[Info].
|
|
|
|
remote_function(Meta, Receiver, Name, Arity, S) ->
|
|
Line = ?line(Meta),
|
|
|
|
Final =
|
|
case (Receiver == ?builtin) andalso is_element({ Name, Arity }, in_erlang_functions()) of
|
|
true -> erlang;
|
|
false -> Receiver
|
|
end,
|
|
|
|
{ { 'fun', Line, { function,
|
|
{ atom, Line, Final },
|
|
{ atom, Line, Name },
|
|
{ integer, Line, Arity}
|
|
} }, S }.
|
|
|
|
|
|
%% ERROR HANDLING
|
|
|
|
format_error({ unrequired_module, { Receiver, Name, Arity, Required }}) ->
|
|
String = 'Elixir.Enum':join([elixir_errors:inspect(R) || R <- Required], <<", ">>),
|
|
io_lib:format("tried to invoke macro ~ts.~ts/~B but module was not required. Required: ~ts",
|
|
[elixir_errors:inspect(Receiver), Name, Arity, String]);
|
|
|
|
format_error({ macro_conflict, { Receiver, Name, Arity } }) ->
|
|
io_lib:format("call to local macro ~ts/~B conflicts with imported ~ts.~ts/~B",
|
|
[Name, Arity, elixir_errors:inspect(Receiver), Name, Arity]);
|
|
|
|
format_error({ ambiguous_call, { Mod1, Mod2, Name, Arity }}) ->
|
|
io_lib:format("function ~ts/~B imported from both ~ts and ~ts, call is ambiguous",
|
|
[Name, Arity, elixir_errors:inspect(Mod1), elixir_errors:inspect(Mod2)]).
|
|
|
|
%% INTROSPECTION
|
|
|
|
%% Do not try to get macros from Erlang. Speeds up compilation a bit.
|
|
get_optional_macros(erlang) -> [];
|
|
|
|
get_optional_macros(Receiver) ->
|
|
case code:ensure_loaded(Receiver) of
|
|
{ module, Receiver } ->
|
|
try
|
|
Receiver:'__info__'(macros)
|
|
catch
|
|
error:undef -> []
|
|
end;
|
|
{ error, _ } -> []
|
|
end.
|
|
|
|
elixir_imported_functions() ->
|
|
try
|
|
?builtin:'__info__'(functions)
|
|
catch
|
|
error:undef -> in_erlang_functions()
|
|
end.
|
|
|
|
elixir_imported_macros() ->
|
|
try
|
|
?builtin:'__info__'(macros)
|
|
catch
|
|
error:undef -> in_erlang_macros()
|
|
end.
|
|
|
|
%% Macros imported from Kernel module. Sorted on compilation.
|
|
|
|
in_elixir_macros() ->
|
|
try
|
|
?builtin:'__info__'(macros)
|
|
catch
|
|
error:undef -> []
|
|
end.
|
|
|
|
%% Functions imported from Erlang module. MUST BE SORTED.
|
|
in_erlang_functions() ->
|
|
[
|
|
{ abs, 1 },
|
|
{ atom_to_binary, 2 },
|
|
{ atom_to_list, 1 },
|
|
{ binary_part, 3 },
|
|
{ binary_to_atom, 2 },
|
|
{ binary_to_existing_atom, 2 },
|
|
{ binary_to_float, 1 },
|
|
{ binary_to_float, 2 },
|
|
{ binary_to_integer, 1 },
|
|
{ binary_to_integer, 2 },
|
|
{ binary_to_term, 1 },
|
|
{ binary_to_term, 2 },
|
|
{ bit_size, 1 },
|
|
{ bitstring_to_list, 1 },
|
|
{ byte_size, 1 },
|
|
% { date, 0 },
|
|
{ exit, 1 },
|
|
% { float, 1 },
|
|
{ float_to_binary, 1 },
|
|
{ float_to_list, 1 },
|
|
{ hd, 1 },
|
|
{ integer_to_binary, 1 },
|
|
{ integer_to_binary, 2 },
|
|
{ integer_to_list, 1 },
|
|
{ integer_to_list, 2 },
|
|
{ iolist_size, 1 },
|
|
{ iolist_to_binary, 1 },
|
|
{ is_alive, 0 },
|
|
{ is_atom, 1 },
|
|
{ is_binary, 1 },
|
|
{ is_bitstring, 1 },
|
|
{ is_boolean, 1 },
|
|
{ is_float, 1 },
|
|
{ is_function, 1 },
|
|
{ is_function, 2 },
|
|
{ is_integer, 1 },
|
|
{ is_list, 1 },
|
|
{ is_number, 1 },
|
|
{ is_pid, 1 },
|
|
{ is_port, 1 },
|
|
{ is_reference, 1 },
|
|
{ is_tuple, 1 },
|
|
{ length, 1 },
|
|
{ list_to_atom, 1 },
|
|
{ list_to_bitstring, 1 },
|
|
{ list_to_existing_atom, 1 },
|
|
{ list_to_float, 1 },
|
|
{ list_to_integer, 1 },
|
|
{ list_to_integer, 2 },
|
|
{ list_to_pid, 1 },
|
|
{ list_to_tuple, 1 },
|
|
{ make_ref, 0 },
|
|
{ max, 2 },
|
|
{ min, 2 },
|
|
{ node, 0 },
|
|
{ node, 1 },
|
|
% { now, 0 },
|
|
{ pid_to_list, 1 },
|
|
{ round, 1 },
|
|
{ self, 0 },
|
|
{ size, 1 },
|
|
{ spawn, 1 },
|
|
{ spawn, 3 },
|
|
{ spawn_link, 1 },
|
|
{ spawn_link, 3 },
|
|
% { split_binary, 2 },
|
|
{ term_to_binary, 1 },
|
|
{ term_to_binary, 2 },
|
|
{ throw, 1 },
|
|
% { time, 0 },
|
|
{ tl, 1 },
|
|
{ trunc, 1 },
|
|
{ tuple_size, 1 },
|
|
{ tuple_to_list, 1 }
|
|
].
|
|
|
|
%% Macros implemented in Erlang. MUST BE SORTED.
|
|
in_erlang_macros() ->
|
|
[
|
|
{'!',1},
|
|
{'!=',2},
|
|
{'!==',2},
|
|
{'*',2},
|
|
{'+',1},
|
|
{'+',2},
|
|
{'++',2},
|
|
{'-',1},
|
|
{'-',2},
|
|
{'--',2},
|
|
{'/',2},
|
|
{'<',2},
|
|
{'<-',2},
|
|
{'<=',2},
|
|
{'==',2},
|
|
{'===',2},
|
|
{'>',2},
|
|
{'>=',2},
|
|
{'@',1},
|
|
{'and',2},
|
|
{apply,2},
|
|
{apply,3},
|
|
{'case',2},
|
|
{def,1},
|
|
{def,2},
|
|
{def,4},
|
|
{defmacro,1},
|
|
{defmacro,2},
|
|
{defmacro,4},
|
|
{defmacrop,1},
|
|
{defmacrop,2},
|
|
{defmacrop,4},
|
|
{defmodule,2},
|
|
{defp,1},
|
|
{defp,2},
|
|
{defp,4},
|
|
{in,2},
|
|
{'not',1},
|
|
{'or',2},
|
|
{'receive',1},
|
|
{'try',1},
|
|
{'xor',2}
|
|
].
|