Files
n8n-openai-adapter/lib/elixir/src/elixir_def.erl
T
José Valim 4fa892208e Properly change __ENV__.file when @file is set
Note that @file may be set from the :file meta as well.
2015-11-21 05:02:38 +08:00

450 lines
18 KiB
Erlang

% Holds the logic responsible for function definitions (def(p) and defmacro(p)).
-module(elixir_def).
-export([setup/1, reset_last/1, lookup_definition/2,
delete_definition/2, store_definition/6, unwrap_definitions/2,
store_each/7, format_error/1]).
-include("elixir.hrl").
-define(last_def, {elixir, last_def}).
-define(attr, {elixir, def_table}).
-define(clauses_attr, {elixir, clauses_table}).
%% Table management functions. Called internally.
setup(Module) ->
reset_last(Module),
ok.
reset_last(Module) ->
ets:insert(elixir_module:data_table(Module), {?last_def, []}).
lookup_definition(Module, Tuple) ->
case ets:lookup(elixir_module:defs_table(Module), Tuple) of
[Result] ->
CTable = elixir_module:clas_table(Module),
{Result, [Clause || {_, Clause} <- ets:lookup(CTable, Tuple)]};
_ ->
false
end.
delete_definition(Module, Tuple) ->
ets:delete(elixir_module:defs_table(Module), Tuple),
ets:delete(elixir_module:clas_table(Module), Tuple).
% Invoked by the wrap definition with the function abstract tree.
% Each function is then added to the function table.
store_definition(Line, Kind, CheckClauses, Call, Body, Pos) when is_integer(Line) ->
E = (elixir_locals:get_cached_env(Pos))#{line := Line},
{NameAndArgs, Guards} = elixir_clauses:extract_guards(Call),
{Name, Args} = case NameAndArgs of
{N, _, A} when is_atom(N), is_atom(A) -> {N, []};
{N, _, A} when is_atom(N), is_list(A) -> {N, A};
_ -> elixir_errors:form_error([{line, Line}], ?m(E, file), ?MODULE, {invalid_def, Kind, NameAndArgs})
end,
%% Now that we have verified the call format,
%% extract meta information like file and context.
{_, Meta, _} = Call,
DoCheckClauses = (not lists:keymember(context, 1, Meta)) andalso (CheckClauses),
%% Check if there is a file information in the definition.
%% If so, we assume this come from another source and
%% we need to linify taking into account keep line numbers.
%%
%% Line and File will always point to the caller. __ENV__.line
%% will always point to the quoted one and __ENV__.file will
%% always point to the one at @file or the quoted one.
{Location, Key} =
case elixir_utils:meta_location(Meta) of
{_, _} = KeepLocation -> {KeepLocation, keep};
nil -> {nil, line}
end,
LinifyArgs = elixir_quote:linify(Line, Key, Args),
LinifyGuards = elixir_quote:linify(Line, Key, Guards),
LinifyBody = elixir_quote:linify(Line, Key, Body),
assert_no_aliases_name(Line, Name, Args, E),
assert_valid_name(Line, Kind, Name, Args, E),
store_definition(Line, Kind, DoCheckClauses, Name,
LinifyArgs, LinifyGuards, LinifyBody, Location, E).
store_definition(Line, Kind, CheckClauses, Name, Args, Guards, Body, KeepLocation, #{module := Module} = ER) ->
Arity = length(Args),
Tuple = {Name, Arity},
Location = retrieve_location(KeepLocation, Module),
E = case Location of
{F, _} -> ER#{function := Tuple, file := elixir_utils:characters_to_binary(F)};
nil -> ER#{function := Tuple}
end,
elixir_locals:record_definition(Tuple, Kind, Module),
{Function, Defaults, Super} = translate_definition(Kind, Line, Name, Args, Guards, Body, E),
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, expr_from_body(Line, Body), E),
DefaultsLength = length(Defaults),
elixir_locals:record_defaults(Tuple, Kind, Module, DefaultsLength),
compile_super(Module, Super, E),
check_previous_defaults(Line, Module, Name, Arity, Kind, DefaultsLength, E),
%% Retrieve the file before we changed it based on @file
File = ?m(ER, file),
store_each(CheckClauses, Kind, File, Location, Module, DefaultsLength, Function),
[store_each(false, Kind, File, Location, Module, 0,
default_function_for(Kind, Name, Default)) || Default <- Defaults],
make_struct_available(Kind, Module, Name, Args),
{Name, Arity}.
%% @on_definition
run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, E) ->
Env = elixir_env:linify({Line, E}),
Callbacks = elixir_module:get_attribute(Module, on_definition),
_ = [Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || {Mod, Fun} <- Callbacks],
ok.
make_struct_available(def, Module, '__struct__', []) ->
case erlang:get(elixir_compiler_pid) of
undefined -> ok;
Pid ->
Pid ! {struct_available, Module},
ok
end;
make_struct_available(_, _, _, _) ->
ok.
%% Retrieve location from meta file (if Key == keep)
%% or @file, otherwise nil
retrieve_location(Location, Module) ->
case get_location_attribute(Module) of
nil when is_tuple(Location) ->
{File, Line} = Location,
{normalize_location(File), Line};
nil ->
nil;
File when is_binary(File) ->
'Elixir.Module':delete_attribute(Module, file),
{normalize_location(File), 0};
{File, Line} when is_binary(File) andalso is_integer(Line) ->
'Elixir.Module':delete_attribute(Module, file),
{normalize_location(File), Line}
end.
get_location_attribute(Module) ->
elixir_module:get_attribute(Module, file).
normalize_location(File) ->
elixir_utils:characters_to_list(elixir_utils:relative_to_cwd(File)).
%% Compile super
compile_super(Module, true, #{function := Function}) ->
elixir_def_overridable:super(Module, Function);
compile_super(_Module, _, _E) -> ok.
%% Translate the given call and expression given
%% and then store it in memory.
translate_definition(Kind, Line, Name, Args, Guards, Body, E) when is_integer(Line) ->
Arity = length(Args),
{EArgs, EGuards, EBody, _} = elixir_exp_clauses:def(fun elixir_def_defaults:expand/2,
Args, Guards, expr_from_body(Line, Body), E),
case Body of
nil -> check_args_for_bodyless_clause(Line, EArgs, E);
_ -> ok
end,
S = elixir_env:env_to_scope(E),
{Unpacked, Defaults} = elixir_def_defaults:unpack(Kind, Name, EArgs, S),
{Clauses, Super} = translate_clause(Body, Line, Kind, Unpacked, EGuards, EBody, S),
Function = {function, Line, Name, Arity, Clauses},
{Function, Defaults, Super}.
translate_clause(nil, _Line, _Kind, _Args, [], _Body, _S) ->
{[], false};
translate_clause(nil, Line, Kind, _Args, _Guards, _Body, #elixir_scope{file=File}) ->
elixir_errors:form_error([{line, Line}], File, ?MODULE, {missing_do, Kind});
translate_clause(_, Line, Kind, Args, Guards, Body, S) ->
{TClause, TS} = elixir_clauses:clause([{line, Line}],
fun elixir_translator:translate_args/2, Args, Body, Guards, S),
FClause = case is_macro(Kind) of
true ->
FArgs = {var, Line, '_@CALLER'},
MClause = setelement(3, TClause, [FArgs|element(3, TClause)]),
case TS#elixir_scope.caller of
true ->
FBody = {'match', Line,
{'var', Line, '__CALLER__'},
elixir_utils:erl_call(Line, elixir_env, linify, [{var, Line, '_@CALLER'}])
},
setelement(5, MClause, [FBody|element(5, TClause)]);
false ->
MClause
end;
false ->
TClause
end,
{[FClause], TS#elixir_scope.super}.
expr_from_body(_Line, nil) -> nil;
expr_from_body(_Line, [{do, Expr}]) -> Expr;
expr_from_body(Line, Else) -> {'try', [{line, Line}], [Else]}.
is_macro(defmacro) -> true;
is_macro(defmacrop) -> true;
is_macro(_) -> false.
% 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_definitions(File, Module) ->
Table = elixir_module:defs_table(Module),
CTable = elixir_module:clas_table(Module),
{All, Private} = unwrap_definition(ets:tab2list(Table), File, Module, CTable, [], []),
Unreachable = elixir_locals:warn_unused_local(File, Module, Private),
split_definition(All, Unreachable, [], [], [], [], [], {[], []}).
unwrap_definition([Fun|T], File, Module, CTable, All, Private) ->
{Tuple, Kind, Line, _, Check, Location, {Defaults, _, _}} = Fun,
Export = export(Kind, Tuple),
case [Clause || {_, Clause} <- ets:lookup(CTable, Tuple)] of
[] ->
warn_bodyless_function(Line, File, Module, Kind, Tuple),
unwrap_definition(T, File, Module, CTable, All, Private);
Clauses ->
Unwrapped = {Tuple, Kind, Line, Location,
function_for_stored_definition(Line, Export, Clauses)},
NewPrivate =
if
Kind == defp; Kind == defmacrop ->
[{Tuple, Kind, Line, Check, Defaults}|Private];
true ->
Private
end,
unwrap_definition(T, File, Module, CTable, [Unwrapped|All], NewPrivate)
end;
unwrap_definition([], _File, _Module, _CTable, All, Private) ->
{All, Private}.
split_definition([{Tuple, def, Line, Location, Body}|T], Unreachable,
Def, Defp, Defmacro, Defmacrop, Exports, Functions) ->
split_definition(T, Unreachable, [Tuple|Def], Defp, Defmacro, Defmacrop,
[export(def, Tuple)|Exports],
add_definition(Line, Location, Body, Functions));
split_definition([{Tuple, defp, Line, Location, Body}|T], Unreachable,
Def, Defp, Defmacro, Defmacrop, Exports, Functions) ->
case lists:member(Tuple, Unreachable) of
false ->
split_definition(T, Unreachable, Def, [Tuple|Defp], Defmacro, Defmacrop,
Exports, add_definition(Line, Location, Body, Functions));
true ->
split_definition(T, Unreachable, Def, [Tuple|Defp], Defmacro, Defmacrop,
Exports, Functions)
end;
split_definition([{Tuple, defmacro, Line, Location, Body}|T], Unreachable,
Def, Defp, Defmacro, Defmacrop, Exports, Functions) ->
split_definition(T, Unreachable, Def, Defp, [Tuple|Defmacro], Defmacrop,
[export(defmacro, Tuple)|Exports],
add_definition(Line, Location, Body, Functions));
split_definition([{Tuple, defmacrop, _Line, _Location, _Body}|T], Unreachable,
Def, Defp, Defmacro, Defmacrop, Exports, Functions) ->
split_definition(T, Unreachable, Def, Defp, Defmacro, [Tuple|Defmacrop],
Exports, Functions);
split_definition([], Unreachable, Def, Defp, Defmacro, Defmacrop, Exports, {Head, Tail}) ->
{Def, Defp, Defmacro, Defmacrop, Exports, Head ++ Tail, Unreachable}.
%% Helpers
export(Kind, {Name, Arity}) when Kind == defmacro; Kind == defmacrop ->
{elixir_utils:macro_name(Name), Arity + 1};
export(Kind, {Name, Arity}) when Kind == def; Kind == defp ->
{Name, Arity}.
function_for_stored_definition(Line, {Name, Arity}, Clauses) ->
{function, Line, Name, Arity, Clauses}.
add_definition(_Line, nil, Body, {Head, Tail}) ->
{[Body|Head], Tail};
add_definition(Line, Location, Body, {Head, Tail}) ->
{Head,
[{attribute, Line, file, Location}, Body|Tail]}.
default_function_for(Kind, Name, {clause, Line, Args, _Guards, _Exprs} = Clause)
when Kind == defmacro; Kind == defmacrop ->
{function, Line, Name, length(Args) - 1, [Clause]};
default_function_for(_, Name, {clause, Line, Args, _Guards, _Exprs} = Clause) ->
{function, Line, Name, length(Args), [Clause]}.
warn_bodyless_function(_Line, _File, Special, _Kind, _Tuple)
when Special == 'Elixir.Kernel.SpecialForms'; Special == 'Elixir.Module' ->
ok;
warn_bodyless_function(Line, File, _Module, Kind, Tuple) ->
elixir_errors:form_warn([{line, Line}], File, ?MODULE, {bodyless_fun, Kind, Tuple}),
ok.
%% 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, File, Location, Module, Defaults, {function, Line, Name, Arity, Clauses}) ->
Data = elixir_module:data_table(Module),
Defs = elixir_module:defs_table(Module),
Clas = elixir_module:clas_table(Module),
Tuple = {Name, Arity},
HasBody = Clauses =/= [],
case ets:lookup(Defs, Tuple) of
[{Tuple, StoredKind, StoredLine, StoredFile, StoredCheck,
StoredLocation, {StoredDefaults, LastHasBody, LastDefaults}}] ->
FinalLine = StoredLine,
FinalLocation = StoredLocation,
FinalDefaults = {max(Defaults, StoredDefaults), HasBody, Defaults},
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind),
(Check and StoredCheck) andalso
check_valid_clause(Line, File, Name, Arity, Kind, Data, StoredLine, StoredFile),
check_valid_defaults(Line, File, Name, Arity, Kind, Defaults, StoredDefaults, LastDefaults, LastHasBody);
[] ->
FinalLine = Line,
FinalLocation = Location,
FinalDefaults = {Defaults, HasBody, Defaults}
end,
Check andalso ets:insert(Data, {?last_def, {Name, Arity}}),
ets:insert(Clas, [{Tuple, Clause} || Clause <- Clauses]),
ets:insert(Defs, {Tuple, Kind, FinalLine, File, Check, FinalLocation, FinalDefaults}).
%% Validations
check_valid_kind(_Line, _File, _Name, _Arity, Kind, Kind) -> [];
check_valid_kind(Line, File, Name, Arity, Kind, StoredKind) ->
elixir_errors:form_error([{line, Line}], File, ?MODULE,
{changed_kind, {Name, Arity, StoredKind, Kind}}).
check_valid_clause(Line, File, Name, Arity, Kind, Data, StoredLine, StoredFile) ->
case ets:lookup_element(Data, ?last_def, 2) of
{Name, Arity} -> [];
[] -> [];
_ ->
Relative = elixir_utils:relative_to_cwd(elixir_utils:relative_to_cwd(StoredFile)),
elixir_errors:form_warn([{line, Line}], File, ?MODULE,
{ungrouped_clause, {Kind, Name, Arity, StoredLine, Relative}})
end.
% Clause with defaults after clause with defaults
check_valid_defaults(Line, File, Name, Arity, Kind, Defaults, StoredDefaults, _, _) when Defaults > 0, StoredDefaults > 0 ->
elixir_errors:form_error([{line, Line}], File, ?MODULE,
{clauses_with_defaults, {Kind, Name, Arity}});
% Clause with defaults after clause(s) without defaults
check_valid_defaults(Line, File, Name, Arity, Kind, Defaults, 0, 0, _) when Defaults > 0 ->
elixir_errors:form_warn([{line, Line}], File, ?MODULE, {out_of_order_defaults, {Kind, Name, Arity}});
% Clause without defaults directly after clause with defaults (body less does not count)
check_valid_defaults(Line, File, Name, Arity, Kind, 0, _, LastDefaults, true) when LastDefaults > 0 ->
elixir_errors:form_warn([{line, Line}], File, ?MODULE,
{out_of_order_defaults, {Kind, Name, Arity}});
% Clause without defaults
check_valid_defaults(_Line, _File, _Name, _Arity, _Kind, 0, _, _, _) -> [].
check_previous_defaults(Line, Module, Name, Arity, Kind, Defaults, E) ->
Matches = ets:match(elixir_module:defs_table(Module), {{Name, '$2'}, '$1', '_', '_', '_', '_', {'$3', '_', '_'}}),
[ begin
elixir_errors:form_error([{line, Line}], ?m(E, file), ?MODULE,
{defs_with_defaults, Name, {Kind, Arity}, {K, A}})
end || [K, A, D] <- Matches, A /= Arity, D /= 0, defaults_conflict(A, D, Arity, Defaults)].
defaults_conflict(A, D, Arity, Defaults) ->
((Arity >= (A - D)) andalso (Arity < A)) orelse
((A >= (Arity - Defaults)) andalso (A < Arity)).
check_args_for_bodyless_clause(Line, Args, E) ->
[ begin
elixir_errors:form_error([{line, Line}], ?m(E, file), ?MODULE,
invalid_args_for_bodyless_clause)
end || Arg <- Args, invalid_arg(Arg) ].
invalid_arg({Name, _, Kind}) when is_atom(Name), is_atom(Kind) ->
false;
invalid_arg({'\\\\', _, [{Name, _, Kind}, _]}) when is_atom(Name), is_atom(Kind) ->
false;
invalid_arg(_) ->
true.
assert_no_aliases_name(Line, '__aliases__', [Atom], #{file := File}) when is_atom(Atom) ->
elixir_errors:form_error([{line, Line}], File, ?MODULE, {no_alias, Atom});
assert_no_aliases_name(_Line, _Aliases, _Args, _S) ->
ok.
assert_valid_name(Line, Kind, is_record, [_, _], #{file := File}) when Kind == defp; Kind == def ->
elixir_errors:form_error([{line, Line}], File, ?MODULE, {is_record, Kind});
assert_valid_name(_Line, _Kind, _Name, _Args, _S) ->
ok.
%% Format errors
format_error({bodyless_fun, Kind, {Name, Arity}}) ->
io_lib:format("bodyless clause provided for nonexistent ~ts ~ts/~B", [Kind, Name, Arity]);
format_error({no_module, {Kind, Name, Arity}}) ->
io_lib:format("cannot define function outside module, invalid scope for ~ts ~ts/~B", [Kind, Name, Arity]);
format_error({defs_with_defaults, Name, {Kind, Arity}, {K, A}}) when Arity > A ->
io_lib:format("~ts ~ts/~B defaults conflicts with ~ts ~ts/~B",
[Kind, Name, Arity, K, Name, A]);
format_error({defs_with_defaults, Name, {Kind, Arity}, {K, A}}) when Arity < A ->
io_lib:format("~ts ~ts/~B conflicts with defaults from ~ts ~ts/~B",
[Kind, Name, Arity, K, Name, A]);
format_error({clauses_with_defaults, {Kind, Name, Arity}}) ->
io_lib:format("~ts ~ts/~B has default values and multiple clauses, "
"define a function head with the defaults", [Kind, Name, Arity]);
format_error({out_of_order_defaults, {Kind, Name, Arity}}) ->
io_lib:format("multiple clauses with default values should define a function head with the defaults, "
"~ts ~ts/~B has multiple clauses and defines defaults in a clause with a body", [Kind, Name, Arity]);
format_error({ungrouped_clause, {Kind, Name, Arity, OrigLine, OrigFile}}) ->
io_lib:format("clauses for the same ~ts should be grouped together, ~ts ~ts/~B was previously defined (~ts:~B)",
[Kind, Kind, Name, Arity, OrigFile, OrigLine]);
format_error({changed_kind, {Name, Arity, Previous, Current}}) ->
io_lib:format("~ts ~ts/~B already defined as ~ts", [Current, Name, Arity, Previous]);
format_error({no_alias, Atom}) ->
io_lib:format("function names should start with lowercase characters or underscore, invalid name ~ts", [Atom]);
format_error({invalid_def, Kind, NameAndArgs}) ->
io_lib:format("invalid syntax in ~ts ~ts", [Kind, 'Elixir.Macro':to_string(NameAndArgs)]);
format_error(invalid_args_for_bodyless_clause) ->
"can use only variables and \\\\ as arguments of bodyless clause";
format_error({is_record, Kind}) ->
io_lib:format("cannot define function named ~ts is_record/2 due to compability "
"issues with the Erlang compiler (it is a known bug)", [Kind]);
format_error({missing_do, Kind}) ->
io_lib:format("missing do keyword in ~ts", [Kind]).