292 lines
10 KiB
Erlang
292 lines
10 KiB
Erlang
% Holds the logic responsible for functions definition (def, defp and defmacro).
|
|
-module(elixir_def).
|
|
-export([build_table/1,
|
|
delete_table/1,
|
|
reset_last/1,
|
|
wrap_definition/7,
|
|
handle_definition/8,
|
|
store_definition/8,
|
|
unwrap_stored_definitions/1,
|
|
format_error/1]).
|
|
-include("elixir.hrl").
|
|
|
|
%% Table management functions. Called internally.
|
|
|
|
table(Module) -> ?ELIXIR_ATOM_CONCAT([f, Module]).
|
|
|
|
build_table(Module) ->
|
|
FunctionTable = table(Module),
|
|
ets:new(FunctionTable, [set, named_table, private]),
|
|
ets:insert(FunctionTable, { public, [] }),
|
|
ets:insert(FunctionTable, { private, [] }),
|
|
ets:insert(FunctionTable, { macros, [] }),
|
|
ets:insert(FunctionTable, { last, [] }),
|
|
FunctionTable.
|
|
|
|
delete_table(Module) ->
|
|
ets:delete(table(Module)).
|
|
|
|
%% Reset the last item. Useful when evaling code.
|
|
reset_last(Module) ->
|
|
ets:insert(table(Module), { last, [] }).
|
|
|
|
%% Wraps the function into a call to store_definition once the function
|
|
%% definition is read. The function is compiled into a meta tree to ensure
|
|
%% we will receive the full function.
|
|
%%
|
|
%% We need to wrap functions instead of eagerly defining them to ensure
|
|
%% functions inside branches won't propagate, for example:
|
|
%%
|
|
%% ns Foo
|
|
%%
|
|
%% if false do
|
|
%% def bar: [], do: 1
|
|
%% else:
|
|
%% def bar: [], do: 2
|
|
%% end
|
|
%%
|
|
%% If we just analyzed the compiled structure (i.e. the function availables
|
|
%% before evaluating the function body), we would see both definitions.
|
|
wrap_definition(Kind, Line, Name, Args, Guards, Expr, S) ->
|
|
MetaS = elixir_variables:serialize_scope(S),
|
|
|
|
Invoke = [
|
|
{atom, Line, Kind},
|
|
{integer, Line, Line},
|
|
{var, Line, '_EXMODULE'},
|
|
Name,
|
|
Args,
|
|
Guards,
|
|
Expr,
|
|
MetaS
|
|
],
|
|
|
|
?ELIXIR_WRAP_CALL(Line, ?MODULE, handle_definition, Invoke).
|
|
|
|
% Invoked by the wrap definition with the function abstract tree.
|
|
% Each function is then added to the function table.
|
|
|
|
handle_definition(Kind, Line, nil, _Name, _Args, _Guards, _Expr, RawS) ->
|
|
S = elixir_variables:deserialize_scope(RawS),
|
|
elixir_errors:syntax_error(Line, S#elixir_scope.filename, "cannot define function outside module, invalid scope for ~s", [Kind]);
|
|
|
|
handle_definition(Kind, Line, Module, Name, Args, RawGuards, RawExpr, RawS) ->
|
|
Data = elixir_module:data(Module),
|
|
Arity = length(Args),
|
|
|
|
case orddict:find(overridable, Data) of
|
|
{ ok, true } ->
|
|
elixir_def_overridable:define(Module, { Name, Arity}, { Kind, Line, Module, Name, Args, RawGuards, RawExpr, RawS }),
|
|
elixir_module:data(Module, orddict:erase(overridable, Data));
|
|
_ ->
|
|
S1 = elixir_variables:deserialize_scope(RawS),
|
|
S2 = S1#elixir_scope{function={Name,Arity}, module=Module},
|
|
store_definition(Kind, Line, Module, Name, Args, RawGuards, RawExpr, S2)
|
|
end.
|
|
|
|
%% Store the definition after is is handled.
|
|
|
|
store_definition(Kind, Line, Module, Name, Args, RawGuards, RawExpr, S) ->
|
|
Guards = elixir_clauses:extract_guard_clauses(RawGuards),
|
|
|
|
case RawExpr of
|
|
skip_definition -> Expr = nil;
|
|
[{ do, Expr }] -> [];
|
|
_ -> Expr = { 'try', Line, [RawExpr] }
|
|
end,
|
|
|
|
{ Function, Defaults, TS } = translate_definition(Line, Name, Args, Guards, Expr, S),
|
|
|
|
Filename = TS#elixir_scope.filename,
|
|
Arity = element(4, Function),
|
|
FunctionTable = table(Module),
|
|
|
|
%% Normalize visibility and kind
|
|
{ Final, Visibility } = case Kind of
|
|
defmacro -> { defmacro, public };
|
|
defp -> { def, private };
|
|
def -> { def, public }
|
|
end,
|
|
|
|
%% Compile documentation
|
|
compile_docs(Kind, Line, Module, Name, Arity, TS),
|
|
|
|
%% Store function
|
|
case RawExpr of
|
|
skip_definition -> [];
|
|
_ ->
|
|
compile_super(Module, TS),
|
|
CheckClauses = S#elixir_scope.check_clauses,
|
|
store_each(CheckClauses, Final, FunctionTable, Visibility, length(Defaults), Filename, Function)
|
|
end,
|
|
|
|
%% Store defaults
|
|
[store_each(false, Final, FunctionTable, Visibility, 0, Filename,
|
|
function_for_clause(Name, Default)) || Default <- Defaults],
|
|
|
|
{ Name, Arity }.
|
|
|
|
%% Compile the documentation related to the module.
|
|
|
|
compile_super(Module, #elixir_scope{function=Function, super=true}) ->
|
|
elixir_def_overridable:store(Module, Function, true);
|
|
|
|
compile_super(_Module, _S) -> [].
|
|
|
|
compile_docs(Kind, Line, Module, Name, Arity, S) ->
|
|
case elixir_compiler:get_opt(internal) of
|
|
true -> [];
|
|
_ ->
|
|
case '__MAIN__.Module':compile_doc(Module, Line, Kind, { Name, Arity }) of
|
|
{ error, Message } -> elixir_errors:handle_file_warning(S#elixir_scope.filename,
|
|
{ Line, ?MODULE, { Message, { Name, Arity } } });
|
|
_ -> []
|
|
end
|
|
end.
|
|
|
|
%% Translate the given call and expression given
|
|
%% and then store it in memory.
|
|
|
|
translate_definition(Line, Name, Args, Guards, Expr, S) ->
|
|
Arity = length(Args),
|
|
{ Unpacked, Defaults } = elixir_def_defaults:unpack(Name, Args, S),
|
|
|
|
{ TClause, TS } = elixir_clauses:assigns_block(Line,
|
|
fun elixir_translator:translate/2, Unpacked, [Expr], Guards, S),
|
|
|
|
FClause = case TS#elixir_scope.name_args of
|
|
true ->
|
|
FArgs = elixir_def_overridable:assign_args(Line, element(3, TClause), TS),
|
|
setelement(3, TClause, FArgs);
|
|
false -> TClause
|
|
end,
|
|
|
|
Function = { function, Line, Name, Arity, [FClause] },
|
|
{ Function, Defaults, TS }.
|
|
|
|
% Unwrap the functions stored in the functions table.
|
|
% It returns a list of all functions to be exported, plus the macros,
|
|
% and the body of all functions.
|
|
unwrap_stored_definitions(Module) ->
|
|
Table = table(Module),
|
|
Public = ets:lookup_element(Table, public, 2),
|
|
Private = ets:lookup_element(Table, private, 2),
|
|
Macros = ets:lookup_element(Table, macros, 2),
|
|
ets:delete(Table, public),
|
|
ets:delete(Table, private),
|
|
ets:delete(Table, macros),
|
|
ets:delete(Table, last),
|
|
Functions = ets:foldl(fun(X, Acc) -> unwrap_stored_definition(X, Acc, Private) end, [], Table),
|
|
{ Public, Private, Macros, Functions }.
|
|
|
|
unwrap_stored_definition({{Name, Arity}, Line, _, Clauses}, Acc, _Private) ->
|
|
[{function, Line, Name, Arity, lists:reverse(Clauses) }|Acc].
|
|
|
|
%% Helpers
|
|
|
|
%% Generates a function for the given clause.
|
|
|
|
function_for_clause(Name, { clause, Line, Args, _Guards, _Exprs } = Clause) ->
|
|
{ function, Line, Name, length(Args), [Clause] }.
|
|
|
|
%% Store each definition in the table.
|
|
%% This function also checks and emit warnings in case
|
|
%% the kind, of the visibility of the function changes.
|
|
|
|
store_each(Check, Kind, Table, Visibility, Defaults, Filename, {function, Line, Name, Arity, Clauses}) ->
|
|
case ets:lookup(Table, {Name, Arity}) of
|
|
[{{Name, Arity}, _, StoredDefaults, StoredClauses}] ->
|
|
FinalDefaults = Defaults + StoredDefaults,
|
|
FinalClauses = Clauses ++ StoredClauses,
|
|
check_valid_visibility(Line, Filename, Name, Arity, Visibility, Table),
|
|
check_valid_kind(Line, Filename, Name, Arity, Kind, Table),
|
|
check_valid_defaults(Line, Filename, Name, Arity, Defaults),
|
|
Check andalso check_valid_clause(Line, Filename, Name, Arity, Table);
|
|
[] ->
|
|
FinalDefaults = Defaults,
|
|
FinalClauses = Clauses,
|
|
add_visibility_entry(Name, Arity, Visibility, Table),
|
|
add_function_entry(Name, Arity, Kind, Table),
|
|
Check andalso ets:insert(Table, { last, { Name, Arity } })
|
|
end,
|
|
ets:insert(Table, {{Name, Arity}, Line, FinalDefaults, FinalClauses}).
|
|
|
|
%% Handle kind (def/defmacro) entries in the table
|
|
|
|
add_function_entry(Name, Arity, defmacro, Table) ->
|
|
Tuple = { Name, Arity },
|
|
Current= ets:lookup_element(Table, macros, 2),
|
|
ets:insert(Table, { macros, [Tuple|Current] });
|
|
|
|
add_function_entry(_Name, _Arity, def, _Table) -> ok.
|
|
|
|
check_valid_kind(Line, Filename, Name, Arity, Kind, Table) ->
|
|
List = ets:lookup_element(Table, macros, 2),
|
|
|
|
Previous = case lists:member({Name, Arity}, List) of
|
|
true -> defmacro;
|
|
false -> def
|
|
end,
|
|
|
|
case Kind == Previous of
|
|
false -> elixir_errors:form_error(Line, Filename, ?MODULE, {changed_kind, {Name, Arity, Previous, Kind}});
|
|
true -> []
|
|
end.
|
|
|
|
%% Handle visibility (public/private) entries in the table
|
|
|
|
add_visibility_entry(Name, Arity, Visibility, Table) ->
|
|
Current= ets:lookup_element(Table, Visibility, 2),
|
|
ets:insert(Table, {Visibility, [{Name, Arity}|Current]}).
|
|
|
|
check_valid_visibility(Line, Filename, Name, Arity, Visibility, Table) ->
|
|
Available = [public, private],
|
|
Previous = find_visibility(Name, Arity, Available, Table),
|
|
case Visibility == Previous of
|
|
false -> elixir_errors:form_error(Line, Filename, ?MODULE, {changed_visibility, {Name, Arity, Previous}});
|
|
true -> []
|
|
end.
|
|
|
|
find_visibility(_Name, _Arity, [H|[]], _Table) ->
|
|
H;
|
|
|
|
find_visibility(Name, Arity, [Visibility|T], Table) ->
|
|
List = ets:lookup_element(Table, Visibility, 2),
|
|
case lists:member({Name, Arity}, List) of
|
|
true -> Visibility;
|
|
false -> find_visibility(Name, Arity, T, Table)
|
|
end.
|
|
|
|
%% Handle clause order
|
|
|
|
check_valid_clause(Line, Filename, Name, Arity, Table) ->
|
|
case ets:lookup_element(Table, last, 2) of
|
|
{Name,Arity} -> [];
|
|
[] -> [];
|
|
{ElseName, ElseArity} -> elixir_errors:form_error(Line, Filename, ?MODULE,
|
|
{ changed_clause, { {Name, Arity}, {ElseName, ElseArity} } })
|
|
end.
|
|
|
|
check_valid_defaults(_Line, _Filename, _Name, _Arity, 0) -> [];
|
|
check_valid_defaults(Line, Filename, Name, Arity, _) ->
|
|
elixir_errors:handle_file_warning(Filename, { Line, ?MODULE, { clauses_with_docs, { Name, Arity } } }).
|
|
|
|
%% Format errors
|
|
|
|
format_error({clauses_with_docs,{Name,Arity}}) ->
|
|
io_lib:format("function ~s/~B has default values and multiple clauses, it is recommended to use a separate clause for declaring defalts", [Name, Arity]);
|
|
|
|
format_error({private_doc,{Name,Arity}}) ->
|
|
io_lib:format("function ~s/~B is private, @doc's are always discarded for private functions", [Name, Arity]);
|
|
|
|
format_error({existing_doc,{Name,Arity}}) ->
|
|
io_lib:format("@doc's for function ~s/~B have been given more than once, the first version is being kept", [Name, Arity]);
|
|
|
|
format_error({changed_clause,{{Name,Arity},{ElseName,ElseArity}}}) ->
|
|
io_lib:format("function ~s/~B does not match previous clause ~s/~B", [Name, Arity, ElseName, ElseArity]);
|
|
|
|
format_error({changed_visibility,{Name,Arity,Previous}}) ->
|
|
io_lib:format("function ~s/~B already defined with visibility ~s", [Name, Arity, Previous]);
|
|
|
|
format_error({changed_kind,{Name,Arity,Previous,Current}}) ->
|
|
io_lib:format("~s ~s/~B already defined as ~s", [Current, Name, Arity, Previous]). |