Perform return type inference and application across local calls (#13984)

This commit is contained in:
José Valim
2024-11-11 23:06:31 +01:00
committed by GitHub
parent 6689eb9462
commit cb2e03688e
39 changed files with 1765 additions and 1747 deletions
+1 -1
View File
@@ -88,9 +88,9 @@ start(_Type, _Args) ->
%% Compiler options
{docs, true},
{infer_signatures, true},
{ignore_already_consolidated, false},
{ignore_module_conflict, false},
{infer_signatures, true},
{on_undefined_variable, raise},
{parser_options, [{columns, true}]},
{debug_info, true},
+1 -1
View File
@@ -50,7 +50,7 @@ define({Line, _S, #{module := Module} = E}, Kind, Call, Expr) ->
Key
end,
Args = [Kind, Store, elixir_locals:cache_env(E#{line := Line})],
Args = [Kind, Store, elixir_module:cache_env(E#{line := Line})],
{{'.', [], [elixir_def, store_definition]}, [], Args}.
unless_loaded(Fun, Args, Callback) ->
+1 -2
View File
@@ -144,7 +144,6 @@ bootstrap() ->
elixir_config:static(#{bootstrap => true}),
elixir_config:put(docs, false),
elixir_config:put(ignore_module_conflict, true),
elixir_config:put(infer_signatures, false),
elixir_config:put(on_undefined_variable, raise),
elixir_config:put(parser_options, []),
elixir_config:put(relative_paths, false),
@@ -191,13 +190,13 @@ bootstrap_files() ->
[
<<"list/chars.ex">>,
<<"bitwise.ex">>,
<<"module/locals_tracker.ex">>,
<<"module/parallel_checker.ex">>,
<<"module/behaviour.ex">>,
<<"module/types/helpers.ex">>,
<<"module/types/descr.ex">>,
<<"module/types/of.ex">>,
<<"module/types/pattern.ex">>,
<<"module/types/apply.ex">>,
<<"module/types/expr.ex">>,
<<"module/types.ex">>,
<<"exception.ex">>,
+7 -3
View File
@@ -37,6 +37,7 @@ fun_for(Meta, Module, Name, Arity, Kinds, External) ->
{[{_, Kind, LocalMeta, _, _, _}], ClausesPairs} ->
case (Kinds == all) orelse (lists:member(Kind, Kinds)) of
true ->
(Kind == defmacrop) andalso track_defmacrop(Module, Tuple),
Local = {value, fun(Fun, Args) -> invoke_local(Meta, Module, Fun, Args, External) end},
Clauses = [Clause || {_, Clause} <- ClausesPairs],
elixir_erl:definition_to_anonymous(Kind, LocalMeta, Clauses, Local, External);
@@ -60,6 +61,10 @@ invoke_local(Meta, Module, ErlName, Args, External) ->
apply(Fun, Args)
end.
track_defmacrop(Module, FunArity) ->
{_, Bag} = elixir_module:data_tables(Module),
ets:insert(Bag, {defmacrop_calls, FunArity}).
invoke_external(Meta, Mod, Name, Args, E) ->
is_map(E) andalso elixir_env:trace({require, Meta, Mod, []}, E),
apply(Mod, Name, Args).
@@ -128,10 +133,10 @@ head_and_definition_meta(_, _Meta, _HeadDefaults, [{_, _, HeadMeta, _} | _]) ->
%% Section for storing definitions
store_definition(Kind, {Call, Body}, Pos) ->
E = elixir_locals:get_cached_env(Pos),
E = elixir_module:get_cached_env(Pos),
store_definition(Kind, false, Call, Body, E);
store_definition(Kind, Key, Pos) ->
#{module := Module} = E = elixir_locals:get_cached_env(Pos),
#{module := Module} = E = elixir_module:get_cached_env(Pos),
{Call, Body} = elixir_module:read_cache(Module, Key),
store_definition(Kind, true, Call, Body, E).
@@ -210,7 +215,6 @@ store_definition(Meta, Kind, CheckClauses, Name, Arity, DefaultsArgs, Guards, Bo
Clause <- def_to_clauses(Kind, Meta, Args, Guards, Body, E)],
DefaultsLength = length(Defaults),
elixir_locals:record_defaults(Tuple, Kind, Module, DefaultsLength, Meta),
check_previous_defaults(Meta, Module, Name, Arity, Kind, DefaultsLength, E),
store_definition(CheckClauses, Kind, Meta, Name, Arity, File,
+4 -8
View File
@@ -55,7 +55,7 @@ import_function(Meta, Name, Arity, E) ->
case find_import_by_name_arity(Meta, Tuple, [], E) of
{function, Receiver} ->
elixir_env:trace({imported_function, Meta, Receiver, Name, Arity}, E),
elixir_locals:record_import(Tuple, Receiver, ?key(E, module), ?key(E, function)),
elixir_import:record(Tuple, Receiver, ?key(E, module), ?key(E, function)),
remote_function(Meta, Receiver, Name, Arity, E);
{macro, _Receiver} ->
false;
@@ -74,7 +74,6 @@ import_function(Meta, Name, Arity, E) ->
elixir_def:local_for(Meta, Name, Arity, [defmacro, defmacrop], E) of
false ->
elixir_env:trace({local_function, Meta, Name, Arity}, E),
elixir_locals:record_local(Tuple, ?key(E, module), ?key(E, function), Meta, false),
{local, Name, Arity};
_ ->
false
@@ -183,10 +182,7 @@ expand_import(Meta, Name, Arity, E, Extra, AllowLocals, Trace) ->
%% Dispatch to the local.
_ ->
Trace andalso begin
elixir_env:trace({local_macro, Meta, Name, Arity}, E),
elixir_locals:record_local(Tuple, Module, ?key(E, function), Meta, true)
end,
Trace andalso elixir_env:trace({local_macro, Meta, Name, Arity}, E),
{macro, Module, expander_macro_fun(Meta, Local, Module, Name, E)}
end
end.
@@ -196,13 +192,13 @@ do_expand_import(Result, Meta, Name, Arity, Module, E, Trace) ->
{function, Receiver} ->
Trace andalso begin
elixir_env:trace({imported_function, Meta, Receiver, Name, Arity}, E),
elixir_locals:record_import({Name, Arity}, Receiver, Module, ?key(E, function))
elixir_import:record({Name, Arity}, Receiver, Module, ?key(E, function))
end,
{function, Receiver, Name};
{macro, Receiver} ->
Trace andalso begin
elixir_env:trace({imported_macro, Meta, Receiver, Name, Arity}, E),
elixir_locals:record_import({Name, Arity}, Receiver, Module, ?key(E, function))
elixir_import:record({Name, Arity}, Receiver, Module, ?key(E, function))
end,
{macro, Receiver, expander_macro_named(Meta, Receiver, Name, Arity, E)};
{import, Receiver} ->
+1 -2
View File
@@ -900,8 +900,7 @@ expand_local(Meta, Name, Args, S, #{module := Module, function := Function, cont
nil ->
Arity = length(Args),
elixir_env:trace({local_function, Meta, Name, Arity}, E),
elixir_locals:record_local({Name, Arity}, Module, Function, Meta, false)
elixir_env:trace({local_function, Meta, Name, Arity}, E)
end,
{EArgs, SA, EA} = expand_args(Args, S, E),
+33 -2
View File
@@ -2,7 +2,9 @@
%% between local functions and imports.
%% For imports dispatch, please check elixir_dispatch.
-module(elixir_import).
-export([import/6, import/7, special_form/2, format_error/1]).
-export([import/6, import/7, special_form/2,
record/4, ensure_no_local_conflict/3,
format_error/1]).
-compile(inline_list_funcs).
-include("elixir.hrl").
@@ -135,6 +137,31 @@ calculate_key(Meta, Key, Old, New, E, Warn) ->
{true, FinalSet, [{Key, FinalSet} | keydelete(Key, Old)]}
end.
%% Record function calls for local conflicts
record(_Tuple, Receiver, Module, Function)
when Function == nil; Module == Receiver -> false;
record(Tuple, Receiver, Module, _Function) ->
try
{Set, _Bag} = elixir_module:data_tables(Module),
ets:insert(Set, {{import, Tuple}, Receiver}),
true
catch
error:badarg -> false
end.
ensure_no_local_conflict('Elixir.Kernel', _All, _E) ->
ok;
ensure_no_local_conflict(Module, AllDefinitions, E) ->
{Set, _} = elixir_module:data_tables(Module),
[try
Receiver = ets:lookup_element(Set, {import, Pair}, 2),
elixir_errors:module_error(Meta, E, ?MODULE, {import_conflict, Receiver, Pair})
catch
error:badarg -> false
end || {Pair, _, Meta, _} <- AllDefinitions].
%% Retrieve functions and macros from modules
get_functions(Module, InfoCallback) ->
@@ -229,7 +256,11 @@ format_error({special_form_conflict, {Receiver, Name, Arity}}) ->
[elixir_aliases:inspect(Receiver), Name, Arity]);
format_error({no_macros, Module}) ->
io_lib:format("could not load macros from module ~ts", [elixir_aliases:inspect(Module)]).
io_lib:format("could not load macros from module ~ts", [elixir_aliases:inspect(Module)]);
format_error({import_conflict, Receiver, {Name, Arity}}) ->
io_lib:format("imported ~ts.~ts/~B conflicts with local function",
[elixir_aliases:inspect(Receiver), Name, Arity]).
%% LIST HELPERS
-146
View File
@@ -1,146 +0,0 @@
%% Module responsible for tracking invocations of module calls.
-module(elixir_locals).
-export([
setup/1, stop/1, cache_env/1, get_cached_env/1,
record_local/5, record_import/4, record_defaults/5,
yank/2, reattach/6, ensure_no_import_conflict/3,
warn_unused_local/4, ensure_no_undefined_local/3,
format_error/1
]).
-include("elixir.hrl").
-define(cache_key, {elixir, cache_env}).
-define(locals_key, {elixir, locals}).
-define(locals, 'Elixir.Module.LocalsTracker').
setup({DataSet, _DataBag}) ->
ets:insert(DataSet, {?cache_key, 0}),
case elixir_config:is_bootstrap() of
false -> ets:insert(DataSet, {?locals_key, true});
true -> ok
end,
ok.
stop({DataSet, _DataBag}) ->
ets:delete(DataSet, ?locals_key).
yank(Tuple, Module) ->
if_tracker(Module, fun(Tracker) -> ?locals:yank(Tracker, Tuple) end).
reattach(Tuple, Kind, Module, Function, Neighbours, Meta) ->
if_tracker(Module, fun(Tracker) -> ?locals:reattach(Tracker, Tuple, Kind, Function, Neighbours, Meta) end).
record_local(_Tuple, _Module, nil, _Meta, _IsMacroDispatch) ->
ok;
record_local(Tuple, Module, Function, Meta, IsMacroDispatch) ->
if_tracker(Module, fun(Tracker) -> ?locals:add_local(Tracker, Function, Tuple, Meta, IsMacroDispatch), ok end).
record_import(_Tuple, Receiver, Module, Function)
when Function == nil; Module == Receiver -> false;
record_import(Tuple, Receiver, Module, Function) ->
if_tracker(Module, fun(Tracker) -> ?locals:add_import(Tracker, Function, Receiver, Tuple), ok end).
record_defaults(_Tuple, _Kind, _Module, 0, _Meta) ->
ok;
record_defaults(Tuple, Kind, Module, Defaults, Meta) ->
if_tracker(Module, fun(Tracker) -> ?locals:add_defaults(Tracker, Kind, Tuple, Defaults, Meta), ok end).
if_tracker(Module, Callback) ->
if_tracker(Module, ok, Callback).
if_tracker(Module, Default, Callback) ->
try
{DataSet, _} = Tables = elixir_module:data_tables(Module),
{ets:member(DataSet, ?locals_key), Tables}
of
{true, Tracker} -> Callback(Tracker);
{false, _} -> Default
catch
error:badarg -> Default
end.
%% CACHING
cache_env(#{line := Line, module := Module} = E) ->
{Set, _} = elixir_module: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, _} = elixir_module:data_tables(Module),
(ets:lookup_element(Set, {cache_env, Pos}, 2))#{line := Line};
get_cached_env(Env) ->
Env.
%% ERROR HANDLING
ensure_no_import_conflict('Elixir.Kernel', _All, _E) ->
ok;
ensure_no_import_conflict(Module, All, E) ->
if_tracker(Module, ok, fun(Tracker) ->
[elixir_errors:module_error(Meta, E, ?MODULE, {function_conflict, Error})
|| {Meta, Error} <- ?locals:collect_imports_conflicts(Tracker, All)],
ok
end).
ensure_no_undefined_local(Module, All, E) ->
if_tracker(Module, [], fun(Tracker) ->
[elixir_errors:module_error(Meta, E#{function := Function, file := File}, ?MODULE, {Error, Tuple, Module})
|| {Function, Meta, File, Tuple, Error} <- ?locals:collect_undefined_locals(Tracker, All, ?key(E, file))],
ok
end).
warn_unused_local(Module, All, Private, E) ->
if_tracker(Module, [], fun(Tracker) ->
{Unreachable, Warnings} = ?locals:collect_unused_locals(Tracker, All, Private),
[elixir_errors:file_warn(Meta, E, ?MODULE, Error) || {Meta, Error} <- Warnings],
Unreachable
end).
format_error({function_conflict, {Receiver, {Name, Arity}}}) ->
io_lib:format("imported ~ts.~ts/~B conflicts with local function",
[elixir_aliases:inspect(Receiver), Name, Arity]);
format_error({unused_args, {Name, Arity}}) ->
io_lib:format("default values for the optional arguments in ~ts/~B are never used", [Name, Arity]);
format_error({unused_args, {Name, Arity}, Count}) when Arity - Count == 1 ->
io_lib:format("the default value for the last optional argument in ~ts/~B is never used", [Name, Arity]);
format_error({unused_args, {Name, Arity}, Count}) ->
io_lib:format("the default values for the last ~B optional arguments in ~ts/~B are never used", [Arity - Count, Name, Arity]);
format_error({unused_def, {Name, Arity}, defp}) ->
io_lib:format("function ~ts/~B is unused", [Name, Arity]);
format_error({unused_def, {Name, Arity}, defmacrop}) ->
io_lib:format("macro ~ts/~B is unused", [Name, Arity]);
format_error({undefined_function, {F, A}, _})
when F == '__info__', A == 1;
F == 'behaviour_info', A == 1;
F == 'module_info', A == 1;
F == 'module_info', A == 0 ->
io_lib:format("undefined function ~ts/~B (this function is auto-generated by the compiler "
"and must always be called as a remote, as in __MODULE__.~ts/~B)", [F, A, F, A]);
format_error({undefined_function, {F, A}, Module}) ->
io_lib:format("undefined function ~ts/~B (expected ~ts to define such a function or "
"for it to be imported, but none are available)", [F, A, elixir_aliases:inspect(Module)]);
format_error({incorrect_dispatch, {F, A}, _Module}) ->
io_lib:format("cannot invoke macro ~ts/~B before its definition", [F, A]).
+48 -27
View File
@@ -1,9 +1,10 @@
-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,
write_cache/3, read_cache/2, next_counter/1, taint/1]).
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
@@ -71,6 +72,29 @@ taint(Module) ->
_:_ -> 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) ->
@@ -146,37 +170,30 @@ compile(Meta, Module, ModuleAsCharlist, Block, Vars, Prune, E) ->
NifsAttribute = lists:keyfind(nifs, 1, Attributes),
validate_nifs_attribute(NifsAttribute, AllDefinitions, Line, E),
Unreachable = elixir_locals:warn_unused_local(Module, AllDefinitions, NewPrivate, E),
elixir_locals:ensure_no_undefined_local(Module, AllDefinitions, E),
elixir_locals:ensure_no_import_conflict(Module, AllDefinitions, E),
%% We stop tracking locals here to avoid race conditions in case after_load
%% evaluates code in a separate process that may write to locals table.
elixir_locals:stop({DataSet, DataBag}),
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),
RawCompileOpts = bag_lookup_element(DataBag, {accumulate, compile}, 2),
CompileOpts = validate_compile_opts(RawCompileOpts, AllDefinitions, Unreachable, Line, E),
Impls = bag_lookup_element(DataBag, impls, 2),
AfterVerify = bag_lookup_element(DataBag, {accumulate, after_verify}, 2),
[elixir_env:trace({remote_function, [], VerifyMod, VerifyFun, 1}, CallbackE) ||
{VerifyMod, VerifyFun} <- AfterVerify],
%% Compute signatures only if the module is valid.
case ets:member(DataSet, {elixir, taint}) of
true -> elixir_errors:compile_error(E);
false -> ok
end,
%% Ensure there are no errors before we infer types
compile_error_if_tainted(DataSet, E),
Signatures = case elixir_config:get(infer_signatures) of
true -> 'Elixir.Module.Types':infer(Module, File, AllDefinitions, CallbackE);
false -> #{}
end,
{Signatures, Unreachable} =
case elixir_config:is_bootstrap() of
true -> {#{}, []};
false ->
Defmacrop = bag_lookup_element(DataBag, defmacrop_calls, 2),
'Elixir.Module.Types':infer(Module, File, AllDefinitions, NewPrivate, Defmacrop, E)
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),
ModuleMap = #{
struct => get_struct(DataSet),
@@ -186,15 +203,16 @@ compile(Meta, Module, ModuleAsCharlist, Block, Vars, Prune, E) ->
relative_file => elixir_utils:relative_to_cwd(File),
attributes => Attributes,
definitions => AllDefinitions,
unreachable => Unreachable,
after_verify => AfterVerify,
compile_opts => CompileOpts,
deprecated => get_deprecated(DataBag),
defines_behaviour => defines_behaviour(DataBag),
impls => Impls,
unreachable => Unreachable,
signatures => Signatures
},
compile_error_if_tainted(DataSet, E),
Binary = elixir_erl:compile(ModuleMap),
Autoload = proplists:get_value(autoload, CompileOpts, true),
spawn_parallel_checker(CheckerInfo, Module, ModuleMap),
@@ -228,6 +246,12 @@ compile(Meta, Module, ModuleAsCharlist, Block, Vars, Prune, E) ->
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.
validate_compile_opts(Opts, Defs, Unreachable, Line, E) ->
lists:flatmap(fun (Opt) -> validate_compile_opt(Opt, Defs, Unreachable, Line, E) end, Opts).
@@ -351,7 +375,6 @@ build(Module, Line, File, E) ->
%% * {{type, Tuple}, ...}, {{opaque, Tuple}, ...}
%% * {{callback, Tuple}, ...}, {{macrocallback, Tuple}, ...}
%% * {{def, Tuple}, ...} (from elixir_def)
%% * {{import, Tuple}, ...} (from elixir_locals)
%% * {{overridable, Tuple}, ...} (from elixir_overridable)
%%
DataSet = ets:new(Module, [set, public]),
@@ -367,8 +390,6 @@ build(Module, Line, File, E) ->
%% * {overridables, ...} (from elixir_overridable)
%% * {{default, Name}, ...} (from elixir_def)
%% * {{clauses, Tuple}, ...} (from elixir_def)
%% * {reattach, ...} (from elixir_locals)
%% * {{local, Tuple}, ...} (from elixir_locals)
%%
DataBag = ets:new(Module, [duplicate_bag, public]),
@@ -395,6 +416,7 @@ build(Module, Line, File, E) ->
{optional_callbacks, [], accumulate, []},
% Others
{?cache_key, 0},
{?counter_attr, 0}
]),
@@ -411,7 +433,6 @@ build(Module, Line, File, E) ->
%% Setup definition related modules
Tables = {DataSet, DataBag},
elixir_def:setup(Tables),
elixir_locals:setup(Tables),
Tuple = {Module, Tables, Line, File, all},
Ref =
+8 -9
View File
@@ -1,10 +1,10 @@
% Holds the logic responsible for defining overridable functions and handling super.
-module(elixir_overridable).
-export([overridables_for/1, overridable_for/2,
record_overridable/4, super/4,
record_overridable/3, super/4,
store_not_overridden/1, format_error/1]).
-include("elixir.hrl").
-define(overridden_pos, 5).
-define(overridden_pos, 4).
overridables_for(Module) ->
{_, Bag} = elixir_module:data_tables(Module),
@@ -22,20 +22,20 @@ overridable_for(Module, Tuple) ->
[] -> not_overridable
end.
record_overridable(Module, Tuple, Def, Neighbours) ->
record_overridable(Module, Tuple, Def) ->
{Set, Bag} = elixir_module:data_tables(Module),
case ets:insert_new(Set, {{overridable, Tuple}, 1, Def, Neighbours, false}) of
case ets:insert_new(Set, {{overridable, Tuple}, 1, Def, false}) of
true ->
ets:insert(Bag, {overridables, Tuple});
false ->
[{_, Count, PreviousDef, _, _}] = ets:lookup(Set, {overridable, Tuple}),
[{_, Count, PreviousDef, _}] = ets:lookup(Set, {overridable, Tuple}),
{{_, Kind, Meta, File, _, _}, _} = Def,
{{_, PreviousKind, _, _, _, _}, _} = PreviousDef,
case is_valid_kind(Kind, PreviousKind) of
true ->
ets:insert(Set, {{overridable, Tuple}, Count + 1, Def, Neighbours, false});
ets:insert(Set, {{overridable, Tuple}, Count + 1, Def, false});
false ->
elixir_errors:file_error(Meta, File, ?MODULE, {bad_kind, Module, Tuple, Kind})
end
@@ -74,7 +74,7 @@ store_not_overridden(Module) ->
%% Private
store(Set, Module, Tuple, {_, Count, Def, Neighbours, Overridden}, Hidden) ->
store(Set, Module, Tuple, {_, Count, Def, Overridden}, Hidden) ->
{{{def, {Name, Arity}}, Kind, Meta, File, _Check,
{Defaults, _HasBody, _LastDefaults}}, Clauses} = Def,
@@ -92,8 +92,7 @@ store(Set, Module, Tuple, {_, Count, Def, Neighbours, Overridden}, Hidden) ->
false ->
ets:update_element(Set, {overridable, Tuple}, {?overridden_pos, true}),
elixir_def:store_definition(none, FinalKind, Meta, FinalName, FinalArity,
File, Module, Defaults, FinalClauses),
elixir_locals:reattach({FinalName, FinalArity}, FinalKind, Module, Tuple, Neighbours, Meta);
File, Module, Defaults, FinalClauses);
true ->
ok
end,