diff options
-rw-r--r-- | lib/typer/src/Makefile | 1 | ||||
-rw-r--r-- | lib/typer/src/typer.app.src | 1 | ||||
-rw-r--r-- | lib/typer/src/typer.erl | 113 | ||||
-rw-r--r-- | lib/typer/src/typer.hrl | 8 | ||||
-rw-r--r-- | lib/typer/src/typer_annotator.erl | 60 | ||||
-rw-r--r-- | lib/typer/src/typer_info.erl | 12 | ||||
-rw-r--r-- | lib/typer/src/typer_options.erl | 10 | ||||
-rw-r--r-- | lib/typer/src/typer_preprocess.erl | 8 |
8 files changed, 138 insertions, 75 deletions
diff --git a/lib/typer/src/Makefile b/lib/typer/src/Makefile index 9c9ef6156f..f367d5980a 100644 --- a/lib/typer/src/Makefile +++ b/lib/typer/src/Makefile @@ -49,7 +49,6 @@ MODULES = \ typer \ typer_annotator \ typer_info \ - typer_map \ typer_options \ typer_preprocess diff --git a/lib/typer/src/typer.app.src b/lib/typer/src/typer.app.src index 3eb0cbf816..f7c3ff867f 100644 --- a/lib/typer/src/typer.app.src +++ b/lib/typer/src/typer.app.src @@ -6,7 +6,6 @@ {modules, [typer, typer_annotator, typer_info, - typer_map, typer_options, typer_preprocess]}, {registered, []}, diff --git a/lib/typer/src/typer.erl b/lib/typer/src/typer.erl index 206ce8e797..c1406cdbbe 100644 --- a/lib/typer/src/typer.erl +++ b/lib/typer/src/typer.erl @@ -18,21 +18,60 @@ %% %CopyrightEnd% %% -%%-------------------------------------------------------------------- +%%----------------------------------------------------------------------- %% File : typer.erl -%% Author : Bingwen He <[email protected]> -%% Description : The main driver of the TypEr application -%%-------------------------------------------------------------------- +%% Author(s) : The first version of typer was written by Bingwen He +%% with guidance from Kostis Sagonas and Tobias Lindahl. +%% Since June 2008 typer is maintained by Kostis Sagonas. +%% Description : An Erlang/OTP application that shows type information +%% for Erlang modules to the user. Additionally, it can +%% annotates the code of files with such type information. +%%----------------------------------------------------------------------- -module(typer). -export([start/0]). --export([error/1, compile_error/1]). % for error reporting +-export([fatal_error/1, compile_error/1]). % for error reporting +-export([map__new/0, map__insert/2, map__lookup/2, map__from_list/1, map__remove/2, map__fold/3]). + +%%----------------------------------------------------------------------- -%% Avoid warning for local function error/1 clashing with autoimported BIF. --compile({no_auto_import, [error/1]}). +-define(SHOW, show). +-define(SHOW_EXPORTED, show_exported). +-define(ANNOTATE, annotate). +-define(ANNOTATE_INC_FILES, annotate_inc_files). --include("typer.hrl"). +-type mode() :: ?SHOW | ?SHOW_EXPORTED | ?ANNOTATE | ?ANNOTATE_INC_FILES. + +%%----------------------------------------------------------------------- + +-record(typer_analysis, + {mode :: mode(), + macros = [] :: [{atom(), term()}], % {macro_name, value} + includes = [] :: [file:filename()], + %% --- for dialyzer --- + code_server = dialyzer_codeserver:new():: dialyzer_codeserver:codeserver(), + callgraph = dialyzer_callgraph:new() :: dialyzer_callgraph:callgraph(), + ana_files = [] :: [file:filename()], % absolute filenames + plt = none :: 'none' | file:filename(), + no_spec = false :: boolean(), + %% --- for typer --- + t_files = [] :: [file:filename()], + %% For choosing between contracts or comments + contracts = true :: boolean(), + %% Files in 'final_files' are compilable with option 'to_pp'; we keep + %% them as {FileName, ModuleName} in case the ModuleName is different + final_files = [] :: [{file:filename(), module()}], + ex_func = map__new() :: map(), + record = map__new() :: map(), + func = map__new() :: map(), + inc_func = map__new() :: map(), + trust_plt = dialyzer_plt:new() :: dialyzer_plt:plt()}). +-type analysis() :: #typer_analysis{}. + +-record(args, {files = [] :: [file:filename()], + files_r = [] :: [file:filename()], + trusted = [] :: [file:filename()]}). %%-------------------------------------------------------------------- @@ -57,7 +96,7 @@ start() -> %%-------------------------------------------------------------------- --spec extract(#typer_analysis{}) -> #typer_analysis{}. +-spec extract(analysis()) -> analysis(). extract(#typer_analysis{macros = Macros, includes = Includes, t_files = TFiles, trust_plt = TrustPLT} = Analysis) -> @@ -117,7 +156,7 @@ extract(#typer_analysis{macros = Macros, includes = Includes, %%-------------------------------------------------------------------- --spec get_type_info(#typer_analysis{}) -> #typer_analysis{}. +-spec get_type_info(analysis()) -> analysis(). get_type_info(#typer_analysis{callgraph = CallGraph, trust_plt = TrustPLT, @@ -130,10 +169,10 @@ get_type_info(#typer_analysis{callgraph = CallGraph, Analysis#typer_analysis{callgraph = StrippedCallGraph, trust_plt = NewPlt} catch error:What -> - error(io_lib:format("Analysis failed with message: ~p", - [{What, erlang:get_stacktrace()}])); + fatal_error(io_lib:format("Analysis failed with message: ~p", + [{What, erlang:get_stacktrace()}])); throw:{dialyzer_succ_typing_error, Msg} -> - error(io_lib:format("Analysis failed with message: ~s", [Msg])) + fatal_error(io_lib:format("Analysis failed with message: ~s", [Msg])) end. -spec remove_external(dialyzer_callgraph:callgraph(), dialyzer_plt:plt()) -> dialyzer_callgraph:callgraph(). @@ -170,31 +209,27 @@ get_external(Exts, Plt) -> lists:foldl(Fun, [], Exts). %%-------------------------------------------------------------------- +%% Utilities for error reporting. +%%-------------------------------------------------------------------- --spec error(string()) -> no_return(). +-spec fatal_error(string()) -> no_return(). -error(Slogan) -> +fatal_error(Slogan) -> msg(io_lib:format("typer: ~s\n", [Slogan])), erlang:halt(1). -%%-------------------------------------------------------------------- - -spec compile_error([string()]) -> no_return(). compile_error(Reason) -> JoinedString = lists:flatten([X ++ "\n" || X <- Reason]), Msg = "Analysis failed with error report:\n" ++ JoinedString, - error(Msg). - -%%-------------------------------------------------------------------- -%% Outputs a message on 'stderr', if possible. -%%-------------------------------------------------------------------- + fatal_error(Msg). -spec msg(string()) -> 'ok'. msg(Msg) -> case os:type() of - {unix, _} -> + {unix, _} -> % Output a message on 'stderr', if possible P = open_port({fd, 0, 2}, [out]), port_command(P, Msg), true = port_close(P), @@ -216,7 +251,37 @@ rcv_ext_types(Self, ExtTypes) -> receive {Self, ext_types, ExtType} -> rcv_ext_types(Self, [ExtType|ExtTypes]); - {Self, done} -> lists:usort(ExtTypes) + {Self, done} -> + lists:usort(ExtTypes) end. %%-------------------------------------------------------------------- +%% A convenient abstraction of a Key-Value mapping data structure +%%-------------------------------------------------------------------- + +-type map() :: dict(). + +-spec map__new() -> map(). +map__new() -> + dict:new(). + +-spec map__insert({term(), term()}, map()) -> map(). +map__insert(Object, Map) -> + {Key, Value} = Object, + dict:store(Key, Value, Map). + +-spec map__lookup(term(), map()) -> term(). +map__lookup(Key, Map) -> + try dict:fetch(Key, Map) catch error:_ -> none end. + +-spec map__from_list([{term(), term()}]) -> map(). +map__from_list(List) -> + dict:from_list(List). + +-spec map__remove(term(), map()) -> map(). +map__remove(Key, Dict) -> + dict:erase(Key, Dict). + +-spec map__fold(fun((term(), term(), term()) -> term()), term(), map()) -> term(). +map__fold(Fun, Acc0, Dict) -> + dict:fold(Fun, Acc0, Dict). diff --git a/lib/typer/src/typer.hrl b/lib/typer/src/typer.hrl index f08668a2ac..d41bf2c83b 100644 --- a/lib/typer/src/typer.hrl +++ b/lib/typer/src/typer.hrl @@ -42,10 +42,10 @@ %% Files in 'final_files' are compilable with option 'to_pp'; we keep %% them as {FileName, ModuleName} in case the ModuleName is different final_files = [] :: [{file:filename(), module()}], - ex_func = typer_map:new() :: dict(), - record = typer_map:new() :: dict(), - func = typer_map:new() :: dict(), - inc_func = typer_map:new() :: dict(), + ex_func = typer:map__new() :: dict(), + record = typer:map__new() :: dict(), + func = typer:map__new() :: dict(), + inc_func = typer:map__new() :: dict(), trust_plt = dialyzer_plt:new() :: dialyzer_plt:plt()}). -record(args, {files = [] :: [file:filename()], diff --git a/lib/typer/src/typer_annotator.erl b/lib/typer/src/typer_annotator.erl index 8904867d3e..205087407e 100644 --- a/lib/typer/src/typer_annotator.erl +++ b/lib/typer/src/typer_annotator.erl @@ -40,11 +40,11 @@ -type fun_info() :: {non_neg_integer(), atom(), arity()}. --record(info, {records = typer_map:new() :: dict(), +-record(info, {records = typer:map__new() :: dict(), functions = [] :: [fun_info()], types :: dict(), no_comment_specs = true :: boolean()}). --record(inc, {map = typer_map:new() :: dict(), +-record(inc, {map = typer:map__new() :: dict(), filter = [] :: [file:filename()]}). %%---------------------------------------------------------------------------- @@ -79,13 +79,13 @@ write_and_collect_inc_info(Analysis) -> write_inc_files(Inc) -> Fun = fun (File) -> - Val = typer_map:lookup(File,Inc#inc.map), + Val = typer:map__lookup(File,Inc#inc.map), %% Val is function with its type info %% in form [{{Line,F,A},Type}] Functions = [Key || {Key,_} <- Val], Val1 = [{{F,A},Type} || {{_Line,F,A},Type} <- Val], - Info = #info{types = typer_map:from_list(Val1), - records = typer_map:new(), + Info = #info{types = typer:map__from_list(Val1), + records = typer:map__new(), %% Note we need to sort functions here! functions = lists:keysort(1, Functions)}, %% io:format("Types ~p\n", [Info#info.types]), @@ -153,17 +153,17 @@ check_imported_functions({File, {Line, F, A}}, Inc, Types) -> IncMap = Inc#inc.map, FA = {F, A}, Type = get_type_info(FA, Types), - case typer_map:lookup(File, IncMap) of + case typer:map__lookup(File, IncMap) of none -> %% File is not added. Add it Obj = {File,[{FA, {Line, Type}}]}, - NewMap = typer_map:insert(Obj, IncMap), + NewMap = typer:map__insert(Obj, IncMap), Inc#inc{map = NewMap}; Val -> %% File is already in. Check. case lists:keyfind(FA, 1, Val) of false -> %% Function is not in; add it Obj = {File, Val ++ [{FA, {Line, Type}}]}, - NewMap = typer_map:insert(Obj, IncMap), + NewMap = typer:map__insert(Obj, IncMap), Inc#inc{map = NewMap}; Type -> %% Function is in and with same type @@ -174,9 +174,9 @@ check_imported_functions({File, {Line, F, A}}, Inc, Types) -> Elem = lists:keydelete(FA, 1, Val), NewMap = case Elem of [] -> - typer_map:remove(File, IncMap); + typer:map__remove(File, IncMap); _ -> - typer_map:insert({File, Elem}, IncMap) + typer:map__insert({File, Elem}, IncMap) end, Inc#inc{map = NewMap} end @@ -192,20 +192,20 @@ clean_inc(Inc) -> remove_yecc_generated_file(#inc{filter = Filter} = Inc) -> Fun = fun (Key, #inc{map = Map} = I) -> - I#inc{map = typer_map:remove(Key, Map)} + I#inc{map = typer:map__remove(Key, Map)} end, lists:foldl(Fun, Inc, Filter). normalize_obj(TmpInc) -> Fun = fun (Key, Val, Inc) -> NewVal = [{{Line,F,A},Type} || {{F,A},{Line,Type}} <- Val], - typer_map:insert({Key,NewVal}, Inc) + typer:map__insert({Key,NewVal}, Inc) end, - NewMap = typer_map:fold(Fun, typer_map:new(), TmpInc#inc.map), + NewMap = typer:map__fold(Fun, typer:map__new(), TmpInc#inc.map), TmpInc#inc{map = NewMap}. get_records(File, Analysis) -> - typer_map:lookup(File, Analysis#typer_analysis.record). + typer:map__lookup(File, Analysis#typer_analysis.record). get_types(Module, Analysis, Records) -> TypeInfoPlt = Analysis#typer_analysis.trust_plt, @@ -216,7 +216,7 @@ get_types(Module, Analysis, Records) -> end, CodeServer = Analysis#typer_analysis.code_server, TypeInfoList = [get_type(I, CodeServer, Records) || I <- TypeInfo], - typer_map:from_list(TypeInfoList). + typer:map__from_list(TypeInfoList). get_type({{M, F, A} = MFA, Range, Arg}, CodeServer, Records) -> case dialyzer_codeserver:lookup_mfa_contract(MFA, CodeServer) of @@ -231,32 +231,32 @@ get_type({{M, F, A} = MFA, Range, Arg}, CodeServer, Records) -> {error, invalid_contract} -> CString = dialyzer_contracts:contract_to_string(Contract), SigString = dialyzer_utils:format_sig(Sig, Records), - typer:error( - io_lib:format("Error in contract of function ~w:~w/~w\n" - "\t The contract is: " ++ CString ++ "\n" ++ - "\t but the inferred signature is: ~s", - [M, F, A, SigString])); - {error, Msg} when is_list(Msg) -> % Msg is a string() - typer:error( - io_lib:format("Error in contract of function ~w:~w/~w: ~s", - [M, F, A, Msg])) + Msg = io_lib:format("Error in contract of function ~w:~w/~w\n" + "\t The contract is: " ++ CString ++ "\n" ++ + "\t but the inferred signature is: ~s", + [M, F, A, SigString]), + typer:fatal_error(Msg); + {error, ErrorStr} when is_list(ErrorStr) -> % ErrorStr is a string() + Msg = io_lib:format("Error in contract of function ~w:~w/~w: ~s", + [M, F, A, ErrorStr]), + typer:fatal_error(Msg) end end. get_functions(File, Analysis) -> case Analysis#typer_analysis.mode of ?SHOW -> - Funcs = typer_map:lookup(File, Analysis#typer_analysis.func), - Inc_Funcs = typer_map:lookup(File, Analysis#typer_analysis.inc_func), + Funcs = typer:map__lookup(File, Analysis#typer_analysis.func), + Inc_Funcs = typer:map__lookup(File, Analysis#typer_analysis.inc_func), remove_module_info(Funcs) ++ normalize_incFuncs(Inc_Funcs); ?SHOW_EXPORTED -> - Ex_Funcs = typer_map:lookup(File, Analysis#typer_analysis.ex_func), + Ex_Funcs = typer:map__lookup(File, Analysis#typer_analysis.ex_func), remove_module_info(Ex_Funcs); ?ANNOTATE -> - Funcs = typer_map:lookup(File, Analysis#typer_analysis.func), + Funcs = typer:map__lookup(File, Analysis#typer_analysis.func), remove_module_info(Funcs); ?ANNOTATE_INC_FILES -> - typer_map:lookup(File, Analysis#typer_analysis.inc_func) + typer:map__lookup(File, Analysis#typer_analysis.inc_func) end. normalize_incFuncs(Functions) -> @@ -371,7 +371,7 @@ show_type_info(File, Info) -> lists:foreach(Fun, Info#info.functions). get_type_info(Func, Types) -> - case typer_map:lookup(Func, Types) of + case typer:map__lookup(Func, Types) of none -> %% Note: Typeinfo of any function should exist in %% the result offered by dialyzer, otherwise there diff --git a/lib/typer/src/typer_info.erl b/lib/typer/src/typer_info.erl index 1387245064..a568518ffe 100644 --- a/lib/typer/src/typer_info.erl +++ b/lib/typer/src/typer_info.erl @@ -42,7 +42,7 @@ collect(Analysis) -> dialyzer_plt:merge_plts([Analysis#typer_analysis.trust_plt, DialyzerPlt]) catch throw:{dialyzer_error,_Reason} -> - typer:error("Dialyzer's PLT is missing or is not up-to-date; please (re)create it") + typer:fatal_error("Dialyzer's PLT is missing or is not up-to-date; please (re)create it") end, NewAnalysis = lists:foldl(fun collect_one_file_info/2, Analysis#typer_analysis{trust_plt = NewPlt}, @@ -66,7 +66,7 @@ collect(Analysis) -> dialyzer_contracts:process_contract_remote_types(TmpCServer3) catch throw:{error, ErrorMsg} -> - typer:error(ErrorMsg) + typer:fatal_error(ErrorMsg) end, NewAnalysis#typer_analysis{code_server = NewCServer}. @@ -122,19 +122,19 @@ analyze_core_tree(Core, Records, SpecInfo, ExpTypes, Analysis, File) -> Fun = fun analyze_one_function/2, All_Defs = cerl:module_defs(Tree), Acc = lists:foldl(Fun, #tmpAcc{file=File, module=Module}, All_Defs), - Exported_FuncMap = typer_map:insert({File, Ex_Funcs}, + Exported_FuncMap = typer:map__insert({File, Ex_Funcs}, Analysis#typer_analysis.ex_func), %% NOTE: we must sort all functions in the file which %% originate from this file by *numerical order* of lineNo Sorted_Functions = lists:keysort(1, Acc#tmpAcc.funcAcc), - FuncMap = typer_map:insert({File, Sorted_Functions}, + FuncMap = typer:map__insert({File, Sorted_Functions}, Analysis#typer_analysis.func), %% NOTE: However we do not need to sort functions %% which are imported from included files. - IncFuncMap = typer_map:insert({File, Acc#tmpAcc.incFuncAcc}, + IncFuncMap = typer:map__insert({File, Acc#tmpAcc.incFuncAcc}, Analysis#typer_analysis.inc_func), Final_Files = Analysis#typer_analysis.final_files ++ [{File, Module}], - RecordMap = typer_map:insert({File, Records}, Analysis#typer_analysis.record), + RecordMap = typer:map__insert({File, Records}, Analysis#typer_analysis.record), Analysis#typer_analysis{final_files=Final_Files, callgraph=CG, code_server=CS6, diff --git a/lib/typer/src/typer_options.erl b/lib/typer/src/typer_options.erl index 9545c7334b..b041052cd2 100644 --- a/lib/typer/src/typer_options.erl +++ b/lib/typer/src/typer_options.erl @@ -72,7 +72,7 @@ cl(["--no_spec"|Opts]) -> {no_spec, Opts}; cl(["--plt",Plt|Opts]) -> {{plt, Plt}, Opts}; cl(["-D"++Def|Opts]) -> case Def of - "" -> typer:error("no variable name specified after -D"); + "" -> typer:fatal_error("no variable name specified after -D"); _ -> DefPair = process_def_list(re:split(Def, "=", [{return, list}])), {{def, DefPair}, Opts} @@ -80,19 +80,19 @@ cl(["-D"++Def|Opts]) -> cl(["-I",Dir|Opts]) -> {{inc, Dir}, Opts}; cl(["-I"++Dir|Opts]) -> case Dir of - "" -> typer:error("no include directory specified after -I"); + "" -> typer:fatal_error("no include directory specified after -I"); _ -> {{inc, Dir}, Opts} end; cl(["-T"|Opts]) -> {Files, RestOpts} = dialyzer_cl_parse:collect_args(Opts), case Files of - [] -> typer:error("no file or directory specified after -T"); + [] -> typer:fatal_error("no file or directory specified after -T"); [_|_] -> {{trusted, Files}, RestOpts} end; cl(["-r"|Opts]) -> {Files, RestOpts} = dialyzer_cl_parse:collect_args(Opts), {{files_r, Files}, RestOpts}; -cl(["-"++H|_]) -> typer:error("unknown option -"++H); +cl(["-"++H|_]) -> typer:fatal_error("unknown option -"++H); cl(Opts) -> {Files, RestOpts} = dialyzer_cl_parse:collect_args(Opts), {{files, Files}, RestOpts}. @@ -141,7 +141,7 @@ analyze_result(no_spec, Args, Analysis) -> -spec mode_error() -> no_return(). mode_error() -> - typer:error("can not do \"show\", \"show-exported\", \"annotate\", and \"annotate-inc-files\" at the same time"). + typer:fatal_error("can not do \"show\", \"show-exported\", \"annotate\", and \"annotate-inc-files\" at the same time"). -spec version_message() -> no_return(). version_message() -> diff --git a/lib/typer/src/typer_preprocess.erl b/lib/typer/src/typer_preprocess.erl index 27660e849e..3366704bad 100644 --- a/lib/typer/src/typer_preprocess.erl +++ b/lib/typer/src/typer_preprocess.erl @@ -30,7 +30,7 @@ get_all_files(#args{files=Fs,files_r=Ds}, analysis) -> case files_and_dirs(Fs, Ds, fun test_erl_file_exclude_ann/1) of - [] -> typer:error("no file(s) to analyze"); + [] -> typer:fatal_error("no file(s) to analyze"); AllFiles -> AllFiles end; get_all_files(#args{trusted=Fs}, trust) -> @@ -98,11 +98,11 @@ check_dir(Dir, Recursive, Acc, Fun) -> Acc ++ TmpAcc1 ++ TmpAcc2 end; {error, eacces} -> - typer:error("no access permission to dir \""++Dir++"\""); + typer:fatal_error("no access permission to dir \""++Dir++"\""); {error, enoent} -> - typer:error("cannot access "++Dir++": No such file or directory"); + typer:fatal_error("cannot access "++Dir++": No such file or directory"); {error, _Reason} -> - typer:error("error involving a use of file:list_dir/1") + typer:fatal_error("error involving a use of file:list_dir/1") end. %% Same order as the input list |