summaryrefslogtreecommitdiff
path: root/lib/kernel/src/code.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/kernel/src/code.erl')
-rw-r--r--lib/kernel/src/code.erl356
1 files changed, 213 insertions, 143 deletions
diff --git a/lib/kernel/src/code.erl b/lib/kernel/src/code.erl
index 001f3ac981..13fed657d1 100644
--- a/lib/kernel/src/code.erl
+++ b/lib/kernel/src/code.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2022. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2023. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -26,17 +26,15 @@
%% some implementation details. See also related modules: code_*.erl
%% in this directory.
--export([objfile_extension/0,
- set_path/1,
- get_path/0,
+-export([objfile_extension/0,
+ set_path/1, set_path/2,
+ get_path/0,
load_file/1,
ensure_loaded/1,
ensure_modules_loaded/1,
load_abs/1,
load_abs/2,
load_binary/3,
- load_native_partial/2,
- load_native_sticky/3,
atomic_load/1,
prepare_loading/1,
finish_loading/1,
@@ -59,18 +57,20 @@
unstick_mod/1,
is_sticky/1,
get_object_code/1,
- add_path/1,
- add_pathsz/1,
- add_paths/1,
- add_pathsa/1,
- add_patha/1,
- add_pathz/1,
+ add_paths/1, add_paths/2,
+ add_path/1, add_path/2,
+ add_pathsa/1, add_pathsa/2,
+ add_pathsz/1, add_pathsz/2,
+ add_patha/1, add_patha/2,
+ add_pathz/1, add_pathz/2,
del_path/1,
- replace_path/2,
- rehash/0,
+ del_paths/1,
+ clear_cache/0,
+ replace_path/2,replace_path/3,
start_link/0,
which/1,
get_doc/1,
+ get_doc/2,
where_is_file/1,
where_is_file/2,
set_primary_archive/4,
@@ -80,8 +80,8 @@
modified_modules/0,
get_mode/0]).
--deprecated({rehash,0,"the code path cache feature has been removed"}).
--deprecated({is_module_native,1,"HiPE has been removed"}).
+-removed({rehash,0,"the code path cache feature has been removed"}).
+-removed({is_module_native,1,"HiPE has been removed"}).
-export_type([load_error_rsn/0, load_ret/0]).
-export_type([prepared_code/0]).
@@ -93,6 +93,8 @@
%% Some types for basic exported functions of this module
%%----------------------------------------------------------------------------
+-define(is_cache(T), T =:= cache orelse T =:= nocache).
+-type cache() :: cache | nocache.
-type load_error_rsn() :: 'badfile'
| 'nofile'
| 'not_purged'
@@ -110,7 +112,7 @@
%%% BIFs
--export([get_chunk/2, is_module_native/1, module_md5/1]).
+-export([get_chunk/2, module_md5/1]).
-spec get_chunk(Bin, Chunk) ->
binary() | undefined when
@@ -134,16 +136,6 @@ get_chunk_1(Beam, Chunk) ->
erlang:raise(error, Reason, [{Mod,get_chunk,L,Loc}|Rest])
end.
--spec is_module_native(Module) -> true | false | undefined when
- Module :: module().
-is_module_native(Module) when is_atom(Module) ->
- case is_loaded(Module) of
- {file, _} -> false;
- false -> undefined
- end;
-is_module_native(Module) ->
- erlang:error(badarg, [Module]).
-
-spec module_md5(binary()) -> binary() | undefined.
module_md5(<<"FOR1", _/bits>>=Beam) ->
@@ -183,7 +175,10 @@ objfile_extension() ->
-spec load_file(Module) -> load_ret() when
Module :: module().
load_file(Mod) when is_atom(Mod) ->
- call({load_file,Mod}).
+ case get_object_code(Mod) of
+ error -> {error,nofile};
+ {Mod,Binary,File} -> load_module(Mod, File, Binary, false, false)
+ end.
-spec ensure_loaded(Module) -> {module, Module} | {error, What} when
Module :: module(),
@@ -191,20 +186,36 @@ load_file(Mod) when is_atom(Mod) ->
ensure_loaded(Mod) when is_atom(Mod) ->
case erlang:module_loaded(Mod) of
true -> {module, Mod};
- false -> call({ensure_loaded,Mod})
+ false ->
+ case get_object_code(Mod) of
+ error -> call({sync_ensure_on_load, Mod});
+ {Mod,Binary,File} ->
+ load_module(Mod, File, Binary, false, true)
+ end
end.
%% XXX File as an atom is allowed only for backwards compatibility.
-spec load_abs(Filename) -> load_ret() when
Filename :: file:filename().
load_abs(File) when is_list(File); is_atom(File) ->
- Mod = list_to_atom(filename:basename(File)),
- call({load_abs,File,Mod}).
+ load_abs(File, list_to_atom(filename:basename(File))).
%% XXX Filename is also an atom(), e.g. 'cover_compiled'
-spec load_abs(Filename :: loaded_filename(), Module :: module()) -> load_ret().
load_abs(File, M) when (is_list(File) orelse is_atom(File)), is_atom(M) ->
- call({load_abs,File,M}).
+ case modp(File) of
+ true ->
+ FileName0 = lists:concat([File, objfile_extension()]),
+ FileName = code_server:absname(FileName0),
+ case erl_prim_loader:get_file(FileName) of
+ {ok,Bin,_} ->
+ load_module(M, FileName, Bin, false, false);
+ error ->
+ {error, nofile}
+ end;
+ false ->
+ {error,badarg}
+ end.
%% XXX Filename is also an atom(), e.g. 'cover_compiled'
-spec load_binary(Module, Filename, Binary) ->
@@ -215,17 +226,26 @@ load_abs(File, M) when (is_list(File) orelse is_atom(File)), is_atom(M) ->
What :: badarg | load_error_rsn().
load_binary(Mod, File, Bin)
when is_atom(Mod), (is_list(File) orelse is_atom(File)), is_binary(Bin) ->
- call({load_binary,Mod,File,Bin}).
+ case modp(File) of
+ true -> load_module(Mod, File, Bin, true, false);
+ false -> {error,badarg}
+ end.
+
+load_module(Mod, File, Bin, Purge, EnsureLoaded) ->
+ case erlang:prepare_loading(Mod, Bin) of
+ {error,_}=Error ->
+ Error;
+ Prepared ->
+ call({load_module, Prepared, Mod, File, Purge, EnsureLoaded})
+ end.
--spec load_native_partial(Module :: module(), Binary :: binary()) -> load_ret().
-load_native_partial(Mod, Bin) when is_atom(Mod), is_binary(Bin) ->
- call({load_native_partial,Mod,Bin}).
+modp(Atom) when is_atom(Atom) -> true;
+modp(List) when is_list(List) -> int_list(List);
+modp(_) -> false.
--spec load_native_sticky(Module :: module(), Binary :: binary(), WholeModule :: 'false' | binary()) -> load_ret().
-load_native_sticky(Mod, Bin, WholeModule)
- when is_atom(Mod), is_binary(Bin),
- (is_binary(WholeModule) orelse WholeModule =:= false) ->
- call({load_native_sticky,Mod,Bin,WholeModule}).
+int_list([H|T]) when is_integer(H) -> int_list(T);
+int_list([_|_]) -> false;
+int_list([]) -> true.
-spec delete(Module) -> boolean() when
Module :: module().
@@ -242,7 +262,8 @@ soft_purge(Mod) when is_atom(Mod) -> call({soft_purge,Mod}).
-spec is_loaded(Module) -> {'file', Loaded} | false when
Module :: module(),
Loaded :: loaded_filename().
-is_loaded(Mod) when is_atom(Mod) -> call({is_loaded,Mod}).
+is_loaded(Mod) when is_atom(Mod) ->
+ code_server:is_loaded(Mod).
-spec get_object_code(Module) -> {Module, Binary, Filename} | error when
Module :: module(),
@@ -345,12 +366,18 @@ unstick_mod(Mod) when is_atom(Mod) -> call({unstick_mod,Mod}).
-spec is_sticky(Module) -> boolean() when
Module :: module().
-is_sticky(Mod) when is_atom(Mod) -> call({is_sticky,Mod}).
+is_sticky(Mod) when is_atom(Mod) ->
+ code_server:is_sticky(Mod).
--spec set_path(Path) -> 'true' | {'error', What} when
- Path :: [Dir :: file:filename()],
- What :: 'bad_directory'.
-set_path(PathList) when is_list(PathList) -> call({set_path,PathList}).
+-type set_path_ret() :: 'true' | {'error', 'bad_directory'}.
+-spec set_path(Path) -> set_path_ret() when
+ Path :: [Dir :: file:filename()].
+set_path(PathList) -> set_path(PathList, nocache).
+
+-spec set_path(Path, cache()) -> set_path_ret() when
+ Path :: [Dir :: file:filename()].
+set_path(PathList, Cache) when is_list(PathList), ?is_cache(Cache) ->
+ call({set_path,PathList,Cache}).
-spec get_path() -> Path when
Path :: [Dir :: file:filename()].
@@ -359,27 +386,51 @@ get_path() -> call(get_path).
-type add_path_ret() :: 'true' | {'error', 'bad_directory'}.
-spec add_path(Dir) -> add_path_ret() when
Dir :: file:filename().
-add_path(Dir) when is_list(Dir) -> call({add_path,last,Dir}).
+add_path(Dir) -> add_path(Dir, nocache).
+
+-spec add_path(Dir, cache()) -> add_path_ret() when
+ Dir :: file:filename().
+add_path(Dir, Cache) when is_list(Dir), ?is_cache(Cache) -> call({add_path,last,Dir,Cache}).
-spec add_pathz(Dir) -> add_path_ret() when
Dir :: file:filename().
-add_pathz(Dir) when is_list(Dir) -> call({add_path,last,Dir}).
+add_pathz(Dir) -> add_pathz(Dir, nocache).
+
+-spec add_pathz(Dir, cache()) -> add_path_ret() when
+ Dir :: file:filename().
+add_pathz(Dir, Cache) when is_list(Dir), ?is_cache(Cache) -> call({add_path,last,Dir,Cache}).
-spec add_patha(Dir) -> add_path_ret() when
Dir :: file:filename().
-add_patha(Dir) when is_list(Dir) -> call({add_path,first,Dir}).
+add_patha(Dir) -> add_patha(Dir, nocache).
+
+-spec add_patha(Dir, cache()) -> add_path_ret() when
+ Dir :: file:filename().
+add_patha(Dir, Cache) when is_list(Dir), ?is_cache(Cache) -> call({add_path,first,Dir,Cache}).
-spec add_paths(Dirs) -> 'ok' when
Dirs :: [Dir :: file:filename()].
-add_paths(Dirs) when is_list(Dirs) -> call({add_paths,last,Dirs}).
+add_paths(Dirs) -> add_paths(Dirs, nocache).
+
+-spec add_paths(Dirs, cache()) -> 'ok' when
+ Dirs :: [Dir :: file:filename()].
+add_paths(Dirs, Cache) when is_list(Dirs), ?is_cache(Cache) -> call({add_paths,last,Dirs,Cache}).
-spec add_pathsz(Dirs) -> 'ok' when
Dirs :: [Dir :: file:filename()].
-add_pathsz(Dirs) when is_list(Dirs) -> call({add_paths,last,Dirs}).
+add_pathsz(Dirs) -> add_pathsz(Dirs, nocache).
+
+-spec add_pathsz(Dirs, cache()) -> 'ok' when
+ Dirs :: [Dir :: file:filename()].
+add_pathsz(Dirs, Cache) when is_list(Dirs), ?is_cache(Cache) -> call({add_paths,last,Dirs,Cache}).
-spec add_pathsa(Dirs) -> 'ok' when
Dirs :: [Dir :: file:filename()].
-add_pathsa(Dirs) when is_list(Dirs) -> call({add_paths,first,Dirs}).
+add_pathsa(Dirs) -> add_pathsa(Dirs, nocache).
+
+-spec add_pathsa(Dirs, cache()) -> 'ok' when
+ Dirs :: [Dir :: file:filename()].
+add_pathsa(Dirs, Cache) when is_list(Dirs), ?is_cache(Cache) -> call({add_paths,first,Dirs,Cache}).
-spec del_path(NameOrDir) -> boolean() | {'error', What} when
NameOrDir :: Name | Dir,
@@ -388,22 +439,33 @@ add_pathsa(Dirs) when is_list(Dirs) -> call({add_paths,first,Dirs}).
What :: 'bad_name'.
del_path(Name) when is_list(Name) ; is_atom(Name) -> call({del_path,Name}).
--spec replace_path(Name, Dir) -> 'true' | {'error', What} when
+-spec del_paths(NamesOrDirs) -> 'ok' when
+ NamesOrDirs :: [Name | Dir],
+ Name :: atom(),
+ Dir :: file:filename().
+del_paths(Dirs) when is_list(Dirs) -> call({del_paths,Dirs}).
+
+-type replace_path_ret() :: 'true' |
+ {'error', 'bad_directory' | 'bad_name' | {'badarg',_}}.
+-spec replace_path(Name, Dir) -> replace_path_ret() when
Name:: atom(),
- Dir :: file:filename(),
- What :: 'bad_directory' | 'bad_name' | {'badarg',_}.
-replace_path(Name, Dir) when (is_atom(Name) orelse is_list(Name)),
- (is_atom(Dir) orelse is_list(Dir)) ->
- call({replace_path,Name,Dir}).
-
--spec rehash() -> 'ok'.
-rehash() ->
- cache_warning(),
- ok.
+ Dir :: file:filename().
+replace_path(Name, Dir) ->
+ replace_path(Name, Dir, nocache).
+
+-spec replace_path(Name, Dir, cache()) -> replace_path_ret() when
+ Name:: atom(),
+ Dir :: file:filename().
+replace_path(Name, Dir, Cache) when (is_atom(Name) orelse is_list(Name)),
+ (is_atom(Dir) orelse is_list(Dir)), ?is_cache(Cache) ->
+ call({replace_path,Name,Dir,Cache}).
-spec get_mode() -> 'embedded' | 'interactive'.
get_mode() -> call(get_mode).
+-spec clear_cache() -> ok.
+clear_cache() -> call(clear_cache).
+
%%%
%%% Loading of several modules in parallel.
%%%
@@ -431,8 +493,8 @@ ensure_modules_loaded_1(Ms0) ->
end,
ensure_modules_loaded_2(OnLoad, Error1).
-ensure_modules_loaded_2([{M,_}|Ms], Errors) ->
- case ensure_loaded(M) of
+ensure_modules_loaded_2([{M,{Prepared,File}}|Ms], Errors) ->
+ case call({load_module, Prepared, M, File, false, true}) of
{module,M} ->
ensure_modules_loaded_2(Ms, Errors);
{error,Err} ->
@@ -578,12 +640,12 @@ prepare_check_uniq_1([], [_|_]=Errors) ->
{error,Errors}.
partition_on_load(Prep) ->
- P = fun({_,{PC,_,_}}) ->
+ P = fun({_,{PC,_}}) ->
erlang:has_prepared_code_on_load(PC)
end,
lists:partition(P, Prep).
-verify_prepared([{M,{Prep,Name,_Native}}|T])
+verify_prepared([{M,{Prep,Name}}|T])
when is_atom(M), is_list(Name) ->
try erlang:has_prepared_code_on_load(Prep) of
false ->
@@ -599,62 +661,35 @@ verify_prepared([]) ->
verify_prepared(_) ->
error.
-finish_loading(Prepared0, EnsureLoaded) ->
- Prepared = [{M,{Bin,File}} || {M,{Bin,File,_}} <- Prepared0],
- Native0 = [{M,Code} || {M,{_,_,Code}} <- Prepared0,
- Code =/= undefined],
- case call({finish_loading,Prepared,EnsureLoaded}) of
- ok ->
- finish_loading_native(Native0);
- {error,Errors}=E when EnsureLoaded ->
- S0 = sofs:relation(Errors),
- S1 = sofs:domain(S0),
- R0 = sofs:relation(Native0),
- R1 = sofs:drestriction(R0, S1),
- Native = sofs:to_external(R1),
- finish_loading_native(Native),
- E;
- {error,_}=E ->
- E
- end.
-
-finish_loading_native([{Mod,Code}|Ms]) ->
- _ = load_native_partial(Mod, Code),
- finish_loading_native(Ms);
-finish_loading_native([]) ->
- ok.
+finish_loading(Prepared, EnsureLoaded) ->
+ call({finish_loading,Prepared,EnsureLoaded}).
load_mods([]) ->
{[],[]};
load_mods(Mods) ->
- Path = get_path(),
- F = prepare_loading_fun(),
- {ok,{Succ,Error0}} = erl_prim_loader:get_modules(Mods, F, Path),
- Error = [case E of
- badfile -> {M,E};
- _ -> {M,nofile}
- end || {M,E} <- Error0],
- {Succ,Error}.
+ F = fun(Mod) ->
+ case get_object_code(Mod) of
+ {Mod, Beam, File} -> prepare_loading(Mod, File, Beam);
+ error -> {error, nofile}
+ end
+ end,
+ do_par(F, Mods).
load_bins([]) ->
{[],[]};
load_bins(BinItems) ->
- F = prepare_loading_fun(),
+ F = fun({Mod, File, Beam}) -> prepare_loading(Mod, File, Beam) end,
do_par(F, BinItems).
--type prep_fun_type() :: fun((module(), file:filename(), binary()) ->
- {ok,_} | {error,_}).
+-spec prepare_loading(module(), file:filename(), binary()) ->
+ {ok,_} | {error,_}.
--spec prepare_loading_fun() -> prep_fun_type().
-
-prepare_loading_fun() ->
- fun(Mod, FullName, Beam) ->
- case erlang:prepare_loading(Mod, Beam) of
- {error,_}=Error ->
- Error;
- Prepared ->
- {ok,{Prepared,FullName,undefined}}
- end
+prepare_loading(Mod, FullName, Beam) ->
+ case erlang:prepare_loading(Mod, Beam) of
+ {error,_}=Error ->
+ Error;
+ Prepared ->
+ {ok,{Prepared,FullName}}
end.
do_par(Fun, L) ->
@@ -664,31 +699,36 @@ do_par(Fun, L) ->
Res
end.
--spec do_par_fun(prep_fun_type(), list()) -> fun(() -> no_return()).
+-type par_fun_type() :: fun((module() | {module(), file:filename(), binary()}) ->
+ {ok,_} | {error,_}).
+
+-spec do_par_fun(par_fun_type(), list()) -> fun(() -> no_return()).
do_par_fun(Fun, L) ->
fun() ->
- _ = [spawn_monitor(do_par_fun_2(Fun, Item)) ||
- Item <- L],
- exit(do_par_recv(length(L), [], []))
+ _ = [spawn_monitor(do_par_fun_each(Fun, Item)) || Item <- L],
+ exit(do_par_recv(length(L), [], []))
end.
--spec do_par_fun_2(prep_fun_type(),
- {module(),file:filename(),binary()}) ->
+-spec do_par_fun_each(par_fun_type(), term()) ->
fun(() -> no_return()).
-do_par_fun_2(Fun, Item) ->
+do_par_fun_each(Fun, Mod) when is_atom(Mod) ->
+ do_par_fun_each(Fun, Mod, Mod);
+do_par_fun_each(Fun, {Mod, _, _} = Item) ->
+ do_par_fun_each(Fun, Mod, Item).
+
+do_par_fun_each(Fun, Mod, Item) ->
fun() ->
- {Mod,Filename,Bin} = Item,
- try Fun(Mod, Filename, Bin) of
- {ok,Res} ->
- exit({good,{Mod,Res}});
- {error,Error} ->
- exit({bad,{Mod,Error}})
- catch
- _:Error ->
- exit({bad,{Mod,Error}})
- end
+ try Fun(Item) of
+ {ok,Res} ->
+ exit({good,{Mod,Res}});
+ {error,Error} ->
+ exit({bad,{Mod,Error}})
+ catch
+ _:Error ->
+ exit({bad,{Mod,Error}})
+ end
end.
do_par_recv(0, Good, Bad) ->
@@ -860,34 +900,52 @@ where_is_file(Tail, File, Path, Files) ->
Res :: #docs_v1{},
Reason :: non_existing | missing | file:posix().
get_doc(Mod) when is_atom(Mod) ->
+ get_doc(Mod, #{sources => [eep48, debug_info]}).
+
+get_doc(Mod, #{sources:=[Source|Sources]}=Options) ->
+ GetDoc = fun(Fn) -> R = case Source of
+ debug_info -> get_doc_chunk_from_ast(Fn);
+ eep48 -> get_doc_chunk(Fn, Mod)
+ end,
+ case R of
+ {error, missing} -> get_doc(Mod, Options#{sources=>Sources});
+ _ -> R
+ end
+ end,
case which(Mod) of
preloaded ->
- Fn = filename:join([code:lib_dir(erts),"ebin",atom_to_list(Mod) ++ ".beam"]),
- get_doc_chunk(Fn, Mod);
+ ErtsDir = code:lib_dir(erts),
+ ErtsEbinDir =
+ case filelib:is_dir(filename:join([ErtsDir,"ebin"])) of
+ true -> filename:join([ErtsDir,"ebin"]);
+ false -> filename:join([ErtsDir,"preloaded","ebin"])
+ end,
+ Fn = filename:join([ErtsEbinDir, atom_to_list(Mod) ++ ".beam"]),
+ GetDoc(Fn);
Error when is_atom(Error) ->
{error, Error};
Fn ->
- get_doc_chunk(Fn, Mod)
- end.
+ GetDoc(Fn)
+ end;
+get_doc(_, #{sources:=[]}) ->
+ {error, missing}.
get_doc_chunk(Filename, Mod) when is_atom(Mod) ->
case beam_lib:chunks(Filename, ["Docs"]) of
{error,beam_lib,{missing_chunk,_,_}} ->
- case get_doc_chunk(Filename, atom_to_list(Mod)) of
- {error,missing} ->
- get_doc_chunk_from_ast(Filename);
- Error ->
- Error
- end;
+ get_doc_chunk(Filename, atom_to_list(Mod));
{error,beam_lib,{file_error,_Filename,_Err}} ->
get_doc_chunk(Filename, atom_to_list(Mod));
{ok, {Mod, [{"Docs",Bin}]}} ->
{ok,binary_to_term(Bin)}
end;
get_doc_chunk(Filename, Mod) ->
+ RootDir = code:root_dir(),
case filename:dirname(Filename) of
Filename ->
{error,missing};
+ RootDir ->
+ {error,missing};
Dir ->
ChunkFile = filename:join([Dir,"doc","chunks",Mod ++ ".chunk"]),
case file:read_file(ChunkFile) of
@@ -904,24 +962,36 @@ get_doc_chunk_from_ast(Filename) ->
case beam_lib:chunks(Filename, [abstract_code]) of
{error,beam_lib,{missing_chunk,_,_}} ->
{error,missing};
+ {error,beam_lib,{file_error,_,_}} ->
+ {error, missing};
{ok, {_Mod, [{abstract_code,
{raw_abstract_v1, AST}}]}} ->
Docs = get_function_docs_from_ast(AST),
+ Types = get_type_docs_from_ast(AST),
{ok, #docs_v1{ anno = 0, beam_language = erlang,
module_doc = none,
- metadata = #{ generated => true, otp_doc_vsn => ?CURR_DOC_VERSION },
- docs = Docs }};
+ metadata = #{ generated => true, otp_doc_vsn => ?CURR_DOC_VERSION},
+ docs = Docs++Types }};
{ok, {_Mod, [{abstract_code,no_abstract_code}]}} ->
{error,missing};
Error ->
Error
end.
+get_type_docs_from_ast(AST) ->
+ lists:flatmap(fun(E) -> get_type_docs_from_ast(E, AST) end, AST).
+get_type_docs_from_ast({attribute, Anno, type, {TypeName, _, Ps}}=Meta, _) ->
+ Arity = length(Ps),
+ Signature = io_lib:format("~p/~p",[TypeName,Arity]),
+ [{{type, TypeName, Arity},Anno,[unicode:characters_to_binary(Signature)],none,#{signature => [Meta]}}];
+get_type_docs_from_ast(_, _) ->
+ [].
+
get_function_docs_from_ast(AST) ->
lists:flatmap(fun(E) -> get_function_docs_from_ast(E, AST) end, AST).
get_function_docs_from_ast({function,Anno,Name,Arity,_Code}, AST) ->
Signature = io_lib:format("~p/~p",[Name,Arity]),
- Specs = lists:filter(
+ Specs = lists:filter(
fun({attribute,_Ln,spec,{FA,_}}) ->
case FA of
{F,A} ->