678 lines
24 KiB
Erlang
678 lines
24 KiB
Erlang
%% SPDX-License-Identifier: Apache-2.0
|
|
%% SPDX-FileCopyrightText: 2021 The Elixir Team
|
|
%% SPDX-FileCopyrightText: 2012 Plataformatec
|
|
|
|
-module(elixir_module).
|
|
-export([file/1, data_tables/1, is_open/1, mode/1, delete_definition_attributes/6,
|
|
compile/6, expand_callback/6, format_error/1, compiler_modules/0, exports_md5/3,
|
|
write_cache/3, read_cache/2, next_counter/1, taint/1, cache_env/1, get_cached_env/1]).
|
|
-include("elixir.hrl").
|
|
-define(counter_attr, {elixir, counter}).
|
|
-define(cache_key, {elixir, cache_env}).
|
|
|
|
%% Stores modules currently being defined by the compiler
|
|
|
|
compiler_modules() ->
|
|
case erlang:get(elixir_compiler_modules) of
|
|
undefined -> [];
|
|
M when is_list(M) -> M
|
|
end.
|
|
|
|
put_compiler_modules([]) ->
|
|
erlang:erase(elixir_compiler_modules);
|
|
put_compiler_modules(M) when is_list(M) ->
|
|
erlang:put(elixir_compiler_modules, M).
|
|
|
|
exports_md5(Def, Defmacro, Struct) ->
|
|
erlang:md5(term_to_binary({lists:sort(Def), lists:sort(Defmacro), Struct}, [deterministic])).
|
|
|
|
%% Table functions
|
|
|
|
file(Module) ->
|
|
ets:lookup_element(elixir_modules, Module, 4).
|
|
|
|
data_tables(Module) ->
|
|
ets:lookup_element(elixir_modules, Module, 2).
|
|
|
|
is_open(Module) ->
|
|
ets:member(elixir_modules, Module).
|
|
|
|
mode(Module) ->
|
|
try ets:lookup_element(elixir_modules, Module, 5) of
|
|
Mode -> Mode
|
|
catch
|
|
_:badarg -> closed
|
|
end.
|
|
|
|
make_readonly(Module) ->
|
|
ets:update_element(elixir_modules, Module, {5, readonly}).
|
|
|
|
delete_definition_attributes(#{module := Module}, _, _, _, _, _) ->
|
|
{DataSet, _} = data_tables(Module),
|
|
ets:delete(DataSet, doc),
|
|
ets:delete(DataSet, deprecated),
|
|
ets:delete(DataSet, impl).
|
|
|
|
write_cache(Module, Key, Value) ->
|
|
{DataSet, _} = data_tables(Module),
|
|
ets:insert(DataSet, {{cache, Key}, Value}).
|
|
|
|
read_cache(Module, Key) ->
|
|
{DataSet, _} = data_tables(Module),
|
|
ets:lookup_element(DataSet, {cache, Key}, 2).
|
|
|
|
next_counter(nil) -> erlang:unique_integer();
|
|
next_counter(Module) ->
|
|
try
|
|
{DataSet, _} = data_tables(Module),
|
|
{Module, ets:update_counter(DataSet, ?counter_attr, 1)}
|
|
catch
|
|
_:_ -> erlang:unique_integer()
|
|
end.
|
|
|
|
taint(Module) ->
|
|
try
|
|
{DataSet, _} = data_tables(Module),
|
|
ets:insert(DataSet, [{{elixir, taint}}]),
|
|
true
|
|
catch
|
|
_:_ -> false
|
|
end.
|
|
|
|
cache_env(#{line := Line, module := Module} = E) ->
|
|
{Set, _} = data_tables(Module),
|
|
Cache = elixir_env:reset_vars(E#{line := nil}),
|
|
PrevKey = ets:lookup_element(Set, ?cache_key, 2),
|
|
|
|
Pos =
|
|
case ets:lookup(Set, {cache_env, PrevKey}) of
|
|
[{_, Cache}] ->
|
|
PrevKey;
|
|
_ ->
|
|
NewKey = PrevKey + 1,
|
|
ets:insert(Set, [{{cache_env, NewKey}, Cache}, {?cache_key, NewKey}]),
|
|
NewKey
|
|
end,
|
|
|
|
{Module, {Line, Pos}}.
|
|
|
|
get_cached_env({Module, {Line, Pos}}) ->
|
|
{Set, _} = data_tables(Module),
|
|
(ets:lookup_element(Set, {cache_env, Pos}, 2))#{line := Line};
|
|
get_cached_env(Env) ->
|
|
Env.
|
|
|
|
%% Compilation hook
|
|
|
|
compile(Meta, Module, Block, Vars, Prune, Env) ->
|
|
ModuleAsCharlist = validate_module_name(Module),
|
|
#{function := Function, versioned_vars := OldVerVars} = Env,
|
|
|
|
{VerVars, _} =
|
|
lists:mapfoldl(fun({Var, _}, I) -> {{Var, I}, I + 1} end, 0, maps:to_list(OldVerVars)),
|
|
|
|
BaseEnv = Env#{module := Module, versioned_vars := maps:from_list(VerVars)},
|
|
|
|
MaybeLexEnv =
|
|
case Function of
|
|
nil -> BaseEnv;
|
|
_ -> BaseEnv#{lexical_tracker := nil, tracers := [], function := nil}
|
|
end,
|
|
|
|
case MaybeLexEnv of
|
|
#{lexical_tracker := nil} ->
|
|
elixir_lexical:run(
|
|
MaybeLexEnv,
|
|
fun(LexEnv) -> compile(Meta, Module, ModuleAsCharlist, Block, Vars, Prune, LexEnv) end,
|
|
fun(_LexEnv) -> ok end
|
|
);
|
|
_ ->
|
|
compile(Meta, Module, ModuleAsCharlist, Block, Vars, Prune, MaybeLexEnv)
|
|
end.
|
|
|
|
validate_module_name(Module) when Module == nil; is_boolean(Module); not is_atom(Module) ->
|
|
invalid_module_name(Module);
|
|
validate_module_name(Module) ->
|
|
Charlist = atom_to_list(Module),
|
|
case lists:any(fun(Char) -> (Char =:= $/) or (Char =:= $\\) end, Charlist) of
|
|
true -> invalid_module_name(Module);
|
|
false -> Charlist
|
|
end.
|
|
|
|
invalid_module_name(Module) ->
|
|
%% We raise an argument error to keep it close to Elixir errors before it starts.
|
|
erlang:error('Elixir.ArgumentError':exception(
|
|
<<"invalid module name: ",
|
|
('Elixir.Kernel':inspect(Module))/binary>>
|
|
)).
|
|
|
|
compile(Meta, Module, ModuleAsCharlist, Block, Vars, Prune, E) ->
|
|
Anno = ?ann(Meta),
|
|
Line = erl_anno:line(Anno),
|
|
|
|
File = ?key(E, file),
|
|
check_module_availability(Module, Line, E),
|
|
elixir_env:trace(defmodule, E),
|
|
|
|
CompilerModules = compiler_modules(),
|
|
{Tables, Ref} = build(Module, Line, File, E),
|
|
{DataSet, DataBag} = Tables,
|
|
|
|
try
|
|
CompilerInfo = erlang:get(elixir_compiler_info),
|
|
put_compiler_modules([Module | CompilerModules]),
|
|
{Result, ModuleE, CallbackE} = eval_form(Line, Module, DataBag, Block, Vars, Prune, E),
|
|
CheckerInfo = checker_info(),
|
|
{BeamLocation, Forceload} = beam_location(ModuleAsCharlist),
|
|
|
|
{Binary, PersistedAttributes, Autoload} =
|
|
elixir_erl_compiler:spawn(fun() ->
|
|
case CompilerInfo of
|
|
undefined -> undefined;
|
|
_ -> erlang:put(elixir_compiler_info, CompilerInfo)
|
|
end,
|
|
|
|
PersistedAttributes = ets:lookup_element(DataBag, persisted_attributes, 2),
|
|
Attributes = attributes(DataSet, DataBag, PersistedAttributes),
|
|
AllDefinitions = elixir_def:fetch_definitions(Module, E),
|
|
|
|
OnLoadAttribute = lists:keyfind(on_load, 1, Attributes),
|
|
validate_on_load_attribute(OnLoadAttribute, AllDefinitions, DataBag, Line, E),
|
|
|
|
DialyzerAttribute = lists:keyfind(dialyzer, 1, Attributes),
|
|
validate_dialyzer_attribute(DialyzerAttribute, AllDefinitions, Line, E),
|
|
|
|
NifsAttribute = lists:keyfind(nifs, 1, Attributes),
|
|
validate_nifs_attribute(NifsAttribute, AllDefinitions, Line, E),
|
|
elixir_import:ensure_no_local_conflict(Module, AllDefinitions, E),
|
|
make_readonly(Module),
|
|
|
|
(not elixir_config:is_bootstrap()) andalso
|
|
'Elixir.Module':'__check_attributes__'(E, DataSet, DataBag),
|
|
|
|
AfterVerify = bag_lookup_element(DataBag, {accumulate, after_verify}, 2),
|
|
[elixir_env:trace({remote_function, [{line, Line}], VerifyMod, VerifyFun, 1}, CallbackE) ||
|
|
{VerifyMod, VerifyFun} <- AfterVerify],
|
|
|
|
%% Ensure there are no errors before we infer types
|
|
compile_error_if_tainted(DataSet, E),
|
|
|
|
{Signatures, Unreachable} =
|
|
case elixir_config:is_bootstrap() of
|
|
true -> {#{}, []};
|
|
false ->
|
|
UsedPrivate = bag_lookup_element(DataBag, used_private, 2),
|
|
'Elixir.Module.Types':infer(Module, File, Attributes, AllDefinitions, UsedPrivate, E, CheckerInfo)
|
|
end,
|
|
|
|
RawCompileOpts = bag_lookup_element(DataBag, {accumulate, compile}, 2),
|
|
CompileOpts = validate_compile_opts(RawCompileOpts, AllDefinitions, Unreachable, Line, E),
|
|
Impls = bag_lookup_element(DataBag, impls, 2),
|
|
|
|
Struct = get_struct(DataSet),
|
|
set_exports_md5(DataSet, AllDefinitions, Struct),
|
|
|
|
ModuleMap = #{
|
|
struct => Struct,
|
|
module => Module,
|
|
anno => Anno,
|
|
file => File,
|
|
relative_file => elixir_utils:relative_to_cwd(File),
|
|
attributes => Attributes,
|
|
definitions => AllDefinitions,
|
|
after_verify => AfterVerify,
|
|
compile_opts => CompileOpts,
|
|
deprecated => get_deprecated(DataBag),
|
|
defines_behaviour => defines_behaviour(DataBag),
|
|
impls => Impls,
|
|
unreachable => Unreachable
|
|
},
|
|
|
|
compile_error_if_tainted(DataSet, E),
|
|
Binary = elixir_erl:compile(ModuleMap, Signatures),
|
|
Autoload = Forceload or proplists:get_value(autoload, CompileOpts, false),
|
|
spawn_parallel_checker(CheckerInfo, Module, ModuleMap, Signatures, BeamLocation),
|
|
{Binary, PersistedAttributes, Autoload}
|
|
end),
|
|
|
|
Autoload andalso code:load_binary(Module, BeamLocation, Binary),
|
|
make_module_available(Module, Binary, Autoload),
|
|
put_compiler_modules(CompilerModules),
|
|
eval_callbacks(Line, DataBag, after_compile, [CallbackE, Binary], CallbackE),
|
|
elixir_env:trace({on_module, Binary, none}, ModuleE),
|
|
warn_unused_attributes(DataSet, DataBag, PersistedAttributes, E),
|
|
(element(2, CheckerInfo) == nil) andalso
|
|
[VerifyMod:VerifyFun(Module) ||
|
|
{VerifyMod, VerifyFun} <- bag_lookup_element(DataBag, {accumulate, after_verify}, 2)],
|
|
{module, Module, Binary, Result}
|
|
catch
|
|
error:undef:Stacktrace ->
|
|
code:purge(Module),
|
|
code:delete(Module),
|
|
case Stacktrace of
|
|
[{Module, Fun, Args, _Info} | _] = Stack when is_list(Args) ->
|
|
compile_undef(Module, Fun, length(Args), Stack);
|
|
[{Module, Fun, Arity, _Info} | _] = Stack ->
|
|
compile_undef(Module, Fun, Arity, Stack);
|
|
Stack ->
|
|
erlang:raise(error, undef, Stack)
|
|
end;
|
|
Kind:Reason:Stacktrace ->
|
|
code:purge(Module),
|
|
code:delete(Module),
|
|
erlang:raise(Kind, Reason, Stacktrace)
|
|
after
|
|
put_compiler_modules(CompilerModules),
|
|
ets:delete(DataSet),
|
|
ets:delete(DataBag),
|
|
elixir_code_server:call({undefmodule, Ref})
|
|
end.
|
|
|
|
compile_error_if_tainted(DataSet, E) ->
|
|
case ets:member(DataSet, {elixir, taint}) of
|
|
true -> elixir_errors:compile_error(E);
|
|
false -> ok
|
|
end.
|
|
|
|
set_exports_md5(DataSet, AllDefinitions, Struct) ->
|
|
{Funs, Macros} =
|
|
lists:foldl(fun
|
|
({Tuple, def, _Meta, _Clauses}, {Funs, Macros}) -> {[Tuple | Funs], Macros};
|
|
({Tuple, defmacro, _Meta, _Clauses}, {Funs, Macros}) -> {Funs, [Tuple | Macros]};
|
|
({_Tuple, _Kind, _Meta, _Clauses}, {Funs, Macros}) -> {Funs, Macros}
|
|
end, {[], []}, AllDefinitions),
|
|
MD5 = exports_md5(Funs, Macros, Struct),
|
|
ets:insert(DataSet, {exports_md5, MD5, nil, []}).
|
|
|
|
validate_compile_opts(Opts, Defs, Unreachable, Line, E) ->
|
|
lists:flatmap(fun (Opt) -> validate_compile_opt(Opt, Defs, Unreachable, Line, E) end, Opts).
|
|
|
|
%% TODO: Make this an error on v2.0
|
|
validate_compile_opt({parse_transform, Module} = Opt, _Defs, _Unreachable, Line, E) ->
|
|
elixir_errors:file_warn([{line, Line}], E, ?MODULE, {parse_transform, Module}),
|
|
[Opt];
|
|
validate_compile_opt({inline, Inlines}, Defs, Unreachable, Line, E) ->
|
|
case validate_inlines(Inlines, Defs, Unreachable, []) of
|
|
{ok, []} ->
|
|
[];
|
|
{ok, FilteredInlines} ->
|
|
[{inline, FilteredInlines}];
|
|
{error, Reason} ->
|
|
elixir_errors:module_error([{line, Line}], E, ?MODULE, Reason),
|
|
[]
|
|
end;
|
|
validate_compile_opt(Opt, Defs, Unreachable, Line, E) when is_list(Opt) ->
|
|
validate_compile_opts(Opt, Defs, Unreachable, Line, E);
|
|
validate_compile_opt(Opt, _Defs, _Unreachable, _Line, _E) ->
|
|
[Opt].
|
|
|
|
validate_inlines([Inline | Inlines], Defs, Unreachable, Acc) ->
|
|
case lists:keyfind(Inline, 1, Defs) of
|
|
false ->
|
|
{error, {undefined_function, {compile, inline}, Inline}};
|
|
{_Def, Kind, _Meta, _Clauses} when Kind == defmacro; Kind == defmacrop ->
|
|
{error, {bad_macro, {compile, inline}, Inline}};
|
|
_ ->
|
|
case lists:member(Inline, Unreachable) of
|
|
true -> validate_inlines(Inlines, Defs, Unreachable, Acc);
|
|
false -> validate_inlines(Inlines, Defs, Unreachable, [Inline | Acc])
|
|
end
|
|
end;
|
|
validate_inlines([], _Defs, _Unreachable, Acc) -> {ok, Acc}.
|
|
|
|
validate_on_load_attribute({on_load, Def}, Defs, Bag, Line, E) ->
|
|
case lists:keyfind(Def, 1, Defs) of
|
|
false ->
|
|
elixir_errors:module_error([{line, Line}], E, ?MODULE, {undefined_function, on_load, Def});
|
|
{_Def, Kind, _Meta, _Clauses} when Kind == defmacro; Kind == defmacrop ->
|
|
elixir_errors:module_error([{line, Line}], E, ?MODULE, {bad_macro, on_load, Def});
|
|
{{Name, Arity}, Kind, _Meta, _Clauses} ->
|
|
elixir_env:trace({local_function, [{line, Line}], Name, Arity}, E),
|
|
(Kind == defp) andalso ets:insert(Bag, {used_private, Def})
|
|
end;
|
|
validate_on_load_attribute(false, _Defs, _Bag, _Line, _E) -> ok.
|
|
|
|
validate_dialyzer_attribute({dialyzer, Dialyzer}, Defs, Line, E) ->
|
|
[validate_definition({dialyzer, Key}, Fun, Defs, Line, E)
|
|
|| {Key, Funs} <- lists:flatten([Dialyzer]), Fun <- lists:flatten([Funs])];
|
|
validate_dialyzer_attribute(false, _Defs, _Line, _E) ->
|
|
ok.
|
|
|
|
validate_nifs_attribute({nifs, Funs}, Defs, Line, E) ->
|
|
[validate_definition(nifs, Fun, Defs, Line, E) || Fun <- lists:flatten([Funs])];
|
|
validate_nifs_attribute(false, _Defs, _Line, _E) ->
|
|
ok.
|
|
|
|
validate_definition(Key, Fun, Defs, Line, E) ->
|
|
case lists:keyfind(Fun, 1, Defs) of
|
|
false ->
|
|
elixir_errors:module_error([{line, Line}], E, ?MODULE, {undefined_function, Key, Fun});
|
|
{Fun, Type, _Meta, _Clauses} when Type == defmacro; Type == defmacrop ->
|
|
elixir_errors:module_error([{line, Line}], E, ?MODULE, {bad_macro, Key, Fun});
|
|
_ ->
|
|
ok
|
|
end.
|
|
|
|
defines_behaviour(DataBag) ->
|
|
ets:member(DataBag, {accumulate, callback}) orelse ets:member(DataBag, {accumulate, macrocallback}).
|
|
|
|
%% An undef error for a function in the module being compiled might result in an
|
|
%% exception message suggesting the current module is not loaded. This is
|
|
%% misleading so use a custom reason.
|
|
compile_undef(Module, Fun, Arity, Stack) ->
|
|
case elixir_config:is_bootstrap() of
|
|
false ->
|
|
Opts = [{module, Module}, {function, Fun}, {arity, Arity},
|
|
{reason, 'function not available'}],
|
|
Exception = 'Elixir.UndefinedFunctionError':exception(Opts),
|
|
erlang:raise(error, Exception, Stack);
|
|
true ->
|
|
erlang:raise(error, undef, Stack)
|
|
end.
|
|
|
|
%% Handle reserved modules and duplicates.
|
|
|
|
check_module_availability(Module, Line, E) ->
|
|
Reserved = ['Elixir.True', 'Elixir.False', 'Elixir.Nil',
|
|
'Elixir.Any', 'Elixir.BitString', 'Elixir.PID',
|
|
'Elixir.Reference', 'Elixir.Elixir', 'Elixir'],
|
|
|
|
case lists:member(Module, Reserved) of
|
|
true -> elixir_errors:file_error([{line, Line}], E, ?MODULE, {module_reserved, Module});
|
|
false -> ok
|
|
end,
|
|
|
|
case elixir_config:get(ignore_module_conflict) of
|
|
false ->
|
|
case code:ensure_loaded(Module) of
|
|
{module, _} ->
|
|
elixir_errors:file_warn([{line, Line}], E, ?MODULE, {module_defined, Module});
|
|
{error, _} ->
|
|
ok
|
|
end;
|
|
true ->
|
|
ok
|
|
end.
|
|
|
|
%% Hook that builds both attribute and functions and set up common hooks.
|
|
|
|
build(Module, Line, File, E) ->
|
|
%% In the set table we store:
|
|
%%
|
|
%% * {Attribute, Value, AccumulateOrUnsetOrReadOrUnreadline, TraceLineOrNil}
|
|
%% * {{elixir, ...}, ...}
|
|
%% * {{cache, ...}, ...}
|
|
%% * {{function, Tuple}, ...}, {{macro, Tuple}, ...}
|
|
%% * {{type, Tuple}, ...}, {{opaque, Tuple}, ...}
|
|
%% * {{callback, Tuple}, ...}, {{macrocallback, Tuple}, ...}
|
|
%% * {{def, Tuple}, ...} (from elixir_def)
|
|
%% * {{overridable, Tuple}, ...} (from elixir_overridable)
|
|
%%
|
|
DataSet = ets:new(Module, [set, public]),
|
|
|
|
%% In the bag table we store:
|
|
%%
|
|
%% * {{accumulate, Attribute}, ...} (includes typespecs)
|
|
%% * {warn_attributes, ...}
|
|
%% * {impls, ...}
|
|
%% * {deprecated, ...}
|
|
%% * {persisted_attributes, ...}
|
|
%% * {defs, ...} (from elixir_def)
|
|
%% * {overridables, ...} (from elixir_overridable)
|
|
%% * {{default, Name}, ...} (from elixir_def)
|
|
%% * {{clauses, Tuple}, ...} (from elixir_def)
|
|
%%
|
|
DataBag = ets:new(Module, [duplicate_bag, public]),
|
|
|
|
ets:insert(DataSet, [
|
|
% {Key, Value, ReadOrUnreadLine, TraceLine}
|
|
{moduledoc, nil, nil, []},
|
|
|
|
% {Key, Value, accumulate, TraceLine}
|
|
{after_compile, [], accumulate, []},
|
|
{after_verify, [], accumulate, []},
|
|
{before_compile, [], accumulate, []},
|
|
{behaviour, [], accumulate, []},
|
|
{compile, [], accumulate, []},
|
|
{derive, [], accumulate, []},
|
|
{dialyzer, [], accumulate, []},
|
|
{external_resource, [], accumulate, []},
|
|
{nifs, [], accumulate, []},
|
|
{on_definition, [], accumulate, []},
|
|
{opaque, [], accumulate, []},
|
|
{type, [], accumulate, []},
|
|
{typep, [], accumulate, []},
|
|
{spec, [], accumulate, []},
|
|
{callback, [], accumulate, []},
|
|
{macrocallback, [], accumulate, []},
|
|
{optional_callbacks, [], accumulate, []},
|
|
|
|
% Others
|
|
{?cache_key, 0},
|
|
{?counter_attr, 0}
|
|
]),
|
|
|
|
Persisted = [behaviour, dialyzer, external_resource, nifs, on_load, vsn],
|
|
ets:insert(DataBag, [{persisted_attributes, Attr} || Attr <- Persisted]),
|
|
|
|
OnDefinition =
|
|
case elixir_config:is_bootstrap() of
|
|
false -> {'Elixir.Module', compile_definition_attributes};
|
|
_ -> {elixir_module, delete_definition_attributes}
|
|
end,
|
|
ets:insert(DataBag, {{accumulate, on_definition}, OnDefinition}),
|
|
|
|
%% Setup definition related modules
|
|
Tables = {DataSet, DataBag},
|
|
elixir_def:setup(Tables),
|
|
Tuple = {Module, Tables, Line, File, all},
|
|
|
|
Ref =
|
|
case elixir_code_server:call({defmodule, Module, self(), Tuple}) of
|
|
{ok, ModuleRef} ->
|
|
ModuleRef;
|
|
{error, {Module, _, OldLine, OldFile, _}} ->
|
|
ets:delete(DataSet),
|
|
ets:delete(DataBag),
|
|
Error = {module_in_definition, Module, OldFile, OldLine},
|
|
elixir_errors:file_error([{line, Line}], E, ?MODULE, Error)
|
|
end,
|
|
|
|
{Tables, Ref}.
|
|
|
|
%% Handles module and callback evaluations.
|
|
|
|
eval_form(Line, Module, DataBag, Block, Vars, Prune, E) ->
|
|
%% Given Elixir modules can get very long to compile due to metaprogramming,
|
|
%% we disable expansions that have linear time to code size.
|
|
{Value, ExS, EE} =
|
|
case elixir_config:get(module_definition) of
|
|
interpreted -> elixir_compiler:interpret(Block, Vars, E);
|
|
compiled -> elixir_compiler:compile(Block, Vars, [no_bool_opt, no_ssa_opt], E)
|
|
end,
|
|
elixir_overridable:store_not_overridden(Module),
|
|
EV = (elixir_env:reset_vars(EE))#{line := Line},
|
|
EC = eval_callbacks(Line, DataBag, before_compile, [EV], EV),
|
|
elixir_overridable:store_not_overridden(Module),
|
|
{Value, maybe_prune_versioned_vars(Prune, Vars, ExS, E), EC}.
|
|
|
|
maybe_prune_versioned_vars(false, _Vars, _Exs, E) ->
|
|
E;
|
|
maybe_prune_versioned_vars(true, Vars, ExS, E) ->
|
|
PruneBefore = length(Vars),
|
|
#elixir_ex{vars={ExVars, _}, unused=Unused} = ExS,
|
|
|
|
VersionedVars =
|
|
maps:filter(fun
|
|
(Pair, Version) when Version < PruneBefore, not is_map_key({Pair, Version}, Unused) -> false;
|
|
(_, _) -> true
|
|
end, ExVars),
|
|
|
|
E#{versioned_vars := VersionedVars}.
|
|
|
|
eval_callbacks(Line, DataBag, Name, Args, E) ->
|
|
Callbacks = bag_lookup_element(DataBag, {accumulate, Name}, 2),
|
|
lists:foldl(fun({M, F}, Acc) ->
|
|
expand_callback(Line, M, F, Args, Acc, fun(AM, AF, AA) -> apply(AM, AF, AA) end)
|
|
end, E, Callbacks).
|
|
|
|
expand_callback(Line, M, F, Args, Acc, Fun) ->
|
|
E = elixir_env:reset_vars(Acc),
|
|
S = elixir_env:env_to_ex(E),
|
|
Meta = [{line, Line}, {required, true}],
|
|
|
|
{EE, _S, ET} =
|
|
elixir_dispatch:dispatch_require(Meta, M, F, Args, S, E, fun(AM, AF) ->
|
|
Fun(AM, AF, Args),
|
|
{ok, S, E}
|
|
end),
|
|
|
|
if
|
|
is_atom(EE) ->
|
|
ET;
|
|
true ->
|
|
try
|
|
{_Value, _Binding, EF} = elixir:eval_forms(EE, [], ET),
|
|
EF
|
|
catch
|
|
Kind:Reason:Stacktrace ->
|
|
Info = {M, F, length(Args), location(Line, E)},
|
|
erlang:raise(Kind, Reason, prune_stacktrace(Info, Stacktrace))
|
|
end
|
|
end.
|
|
|
|
%% Add attributes handling to the form
|
|
|
|
attributes(DataSet, DataBag, PersistedAttributes) ->
|
|
[{Key, Value} || Key <- PersistedAttributes, Value <- lookup_attribute(DataSet, DataBag, Key)].
|
|
|
|
lookup_attribute(DataSet, DataBag, Key) when is_atom(Key) ->
|
|
case ets:lookup(DataSet, Key) of
|
|
[{_, _, accumulate, _}] -> bag_lookup_element(DataBag, {accumulate, Key}, 2);
|
|
[{_, _, unset, _}] -> [];
|
|
[{_, Value, _, _}] -> [Value];
|
|
[] -> []
|
|
end.
|
|
|
|
warn_unused_attributes(DataSet, DataBag, PersistedAttrs, E) ->
|
|
StoredAttrs = bag_lookup_element(DataBag, warn_attributes, 2),
|
|
%% This is the same list as in Module.put_attribute
|
|
%% without moduledoc which are never warned on.
|
|
Attrs = [doc, typedoc, impl, deprecated | StoredAttrs -- PersistedAttrs],
|
|
Query = [{{Attr, '_', '$1', '_'}, [{is_integer, '$1'}], [[Attr, '$1']]} || Attr <- Attrs],
|
|
[elixir_errors:file_warn([{line, Line}], E, ?MODULE, {unused_attribute, Key})
|
|
|| [Key, Line] <- ets:select(DataSet, Query)].
|
|
|
|
get_struct(Set) ->
|
|
case ets:lookup(Set, {elixir, struct}) of
|
|
[] -> nil;
|
|
[{_, Fields}] -> Fields
|
|
end.
|
|
|
|
get_deprecated(Bag) ->
|
|
lists:usort(bag_lookup_element(Bag, deprecated, 2)).
|
|
|
|
bag_lookup_element(Table, Name, Pos) ->
|
|
try
|
|
ets:lookup_element(Table, Name, Pos)
|
|
catch
|
|
error:badarg -> []
|
|
end.
|
|
|
|
beam_location(ModuleAsCharlist) ->
|
|
case erlang:get(elixir_compiler_dest) of
|
|
{Dest, Forceload} when is_binary(Dest) ->
|
|
{filename:join(elixir_utils:characters_to_list(Dest), ModuleAsCharlist ++ ".beam"),
|
|
Forceload};
|
|
_ ->
|
|
{"", true}
|
|
end.
|
|
|
|
%% Integration with elixir_compiler that makes the module available
|
|
|
|
checker_info() ->
|
|
case erlang:get(elixir_checker_info) of
|
|
undefined -> {self(), nil};
|
|
_ -> 'Elixir.Module.ParallelChecker':get()
|
|
end.
|
|
|
|
spawn_parallel_checker({_, nil}, _Module, _ModuleMap, _Signatures, _BeamLocation) ->
|
|
ok;
|
|
spawn_parallel_checker(CheckerInfo, Module, ModuleMap, Signatures, BeamLocation) ->
|
|
Log =
|
|
case erlang:get(elixir_code_diagnostics) of
|
|
{_, false} -> false;
|
|
_ -> true
|
|
end,
|
|
'Elixir.Module.ParallelChecker':spawn(CheckerInfo, Module, ModuleMap, Signatures, BeamLocation, Log).
|
|
|
|
make_module_available(Module, Binary, Loaded) ->
|
|
case erlang:get(elixir_module_binaries) of
|
|
Current when is_list(Current) ->
|
|
put(elixir_module_binaries, [{Module, Binary} | Current]);
|
|
_ ->
|
|
ok
|
|
end,
|
|
|
|
case erlang:get(elixir_compiler_info) of
|
|
undefined ->
|
|
ok;
|
|
{PID, _} ->
|
|
Ref = make_ref(),
|
|
PID ! {module_available, self(), Ref, erlang:get(elixir_compiler_file), Module, Binary, Loaded},
|
|
receive {Ref, ack} -> ok end
|
|
end.
|
|
|
|
%% Error handling and helpers.
|
|
|
|
%% We've reached the elixir_module or eval internals, skip it with the rest
|
|
prune_stacktrace(Info, [{elixir, eval_forms, _, _} | _]) ->
|
|
[Info];
|
|
prune_stacktrace(Info, [{elixir_module, _, _, _} | _]) ->
|
|
[Info];
|
|
prune_stacktrace(Info, [H | T]) ->
|
|
[H | prune_stacktrace(Info, T)];
|
|
prune_stacktrace(Info, []) ->
|
|
[Info].
|
|
|
|
location(Line, E) ->
|
|
[{file, elixir_utils:characters_to_list(?key(E, file))}, {line, Line}].
|
|
|
|
format_error({unused_attribute, typedoc}) ->
|
|
"module attribute @typedoc was set but no type follows it";
|
|
format_error({unused_attribute, doc}) ->
|
|
"module attribute @doc was set but no definition follows it";
|
|
format_error({unused_attribute, impl}) ->
|
|
"module attribute @impl was set but no definition follows it";
|
|
format_error({unused_attribute, deprecated}) ->
|
|
"module attribute @deprecated was set but no definition follows it";
|
|
format_error({unused_attribute, Attr}) ->
|
|
io_lib:format("module attribute @~ts was set but never used", [Attr]);
|
|
format_error({module_defined, Module}) ->
|
|
Extra =
|
|
case code:which(Module) of
|
|
"" ->
|
|
" (current version defined in memory)";
|
|
Path when is_list(Path) ->
|
|
io_lib:format(" (current version loaded from ~ts)", [elixir_utils:relative_to_cwd(Path)]);
|
|
_ ->
|
|
""
|
|
end,
|
|
io_lib:format("redefining module ~ts~ts", [elixir_aliases:inspect(Module), Extra]);
|
|
format_error({module_reserved, Module}) ->
|
|
io_lib:format("module ~ts is reserved and cannot be defined", [elixir_aliases:inspect(Module)]);
|
|
format_error({module_in_definition, Module, File, Line}) ->
|
|
io_lib:format("cannot define module ~ts because it is currently being defined in ~ts:~B",
|
|
[elixir_aliases:inspect(Module), elixir_utils:relative_to_cwd(File), Line]);
|
|
format_error({undefined_function, {Attr, Key}, {Name, Arity}}) ->
|
|
io_lib:format("undefined function ~ts/~B given to @~ts :~ts", [Name, Arity, Attr, Key]);
|
|
format_error({undefined_function, Attr, {Name, Arity}}) ->
|
|
io_lib:format("undefined function ~ts/~B given to @~ts", [Name, Arity, Attr]);
|
|
format_error({bad_macro, {Attr, Key}, {Name, Arity}}) ->
|
|
io_lib:format("macro ~ts/~B given to @~ts :~ts (only functions are supported)", [Name, Arity, Attr, Key]);
|
|
format_error({bad_macro, Attr, {Name, Arity}}) ->
|
|
io_lib:format("macro ~ts/~B given to @~ts (only functions are supported)", [Name, Arity, Attr]);
|
|
format_error({parse_transform, Module}) ->
|
|
io_lib:format("@compile {:parse_transform, ~ts} is deprecated. Elixir will no longer support "
|
|
"Erlang-based transforms in future versions", [elixir_aliases:inspect(Module)]).
|