From 63c13786ec04bba57acaea6dee1f2fbc77c7b5e1 Mon Sep 17 00:00:00 2001 From: Kostis Sagonas Date: Tue, 26 Jan 2010 17:38:55 +0200 Subject: [PATCH 1/8] Fix a HiPE compiler bug evaluating an expression that throws system_limit --- lib/hipe/cerl/erl_bif_types.erl | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/lib/hipe/cerl/erl_bif_types.erl b/lib/hipe/cerl/erl_bif_types.erl index 756fcb8bbf44..8aa2cc76c7e0 100644 --- a/lib/hipe/cerl/erl_bif_types.erl +++ b/lib/hipe/cerl/erl_bif_types.erl @@ -3128,20 +3128,25 @@ arith(Op, X1, X2) -> %% io:format("done arith ~p = ~p~n", [Op, {NewMin, NewMax}]), {ok, t_from_range(NewMin, NewMax)}; false -> - AllVals = - case Op of - '+' -> [X + Y || X <- L1, Y <- L2]; - '-' -> [X - Y || X <- L1, Y <- L2]; - '*' -> [X * Y || X <- L1, Y <- L2]; - 'div' -> [X div Y || X <- L1, Y <- L2,Y =/= 0]; - 'rem' -> [X rem Y || X <- L1, Y <- L2,Y =/= 0]; - 'bsl' -> [X bsl Y || X <- L1, Y <- L2]; - 'bsr' -> [X bsr Y || X <- L1, Y <- L2]; - 'band' -> [X band Y || X <- L1, Y <- L2]; - 'bor' -> [X bor Y || X <- L1, Y <- L2]; - 'bxor' -> [X bxor Y || X <- L1, Y <- L2] - end, - {ok, t_integers(ordsets:from_list(AllVals))} + %% Some of these arithmetic operations might throw a system_limit + %% exception; for example, when trying to evaluate 1 bsl 100000000. + try case Op of + '+' -> [X + Y || X <- L1, Y <- L2]; + '-' -> [X - Y || X <- L1, Y <- L2]; + '*' -> [X * Y || X <- L1, Y <- L2]; + 'div' -> [X div Y || X <- L1, Y <- L2,Y =/= 0]; + 'rem' -> [X rem Y || X <- L1, Y <- L2,Y =/= 0]; + 'bsl' -> [X bsl Y || X <- L1, Y <- L2]; + 'bsr' -> [X bsr Y || X <- L1, Y <- L2]; + 'band' -> [X band Y || X <- L1, Y <- L2]; + 'bor' -> [X bor Y || X <- L1, Y <- L2]; + 'bxor' -> [X bxor Y || X <- L1, Y <- L2] + end of + AllVals -> + {ok, t_integers(ordsets:from_list(AllVals))} + catch + error:system_limit -> error + end end end. From eab3576f33ec8c8fa5302bc07f55c20ffc9e51b0 Mon Sep 17 00:00:00 2001 From: Kostis Sagonas Date: Wed, 10 Feb 2010 10:54:18 +0200 Subject: [PATCH 2/8] typer: New version for the R13B04 release --- lib/typer/src/typer_annotator.erl | 4 +++- lib/typer/vsn.mk | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/typer/src/typer_annotator.erl b/lib/typer/src/typer_annotator.erl index 17eeeb6dfe0c..a68360604dad 100644 --- a/lib/typer/src/typer_annotator.erl +++ b/lib/typer/src/typer_annotator.erl @@ -227,6 +227,8 @@ get_type({{M, F, A} = MFA, Range, Arg}, CodeServer, RecMap) -> Sig = erl_types:t_fun(Arg, Range), case dialyzer_contracts:check_contract(Contract, Sig) of ok -> {{F, A}, {contract, Contract}}; + {error, {extra_range, _, _}} -> + {{F, A}, {contract, Contract}}; {error, invalid_contract} -> CString = dialyzer_contracts:contract_to_string(Contract), SigString = dialyzer_utils:format_sig(Sig, RecMap), @@ -235,7 +237,7 @@ get_type({{M, F, A} = MFA, Range, Arg}, CodeServer, RecMap) -> "\t The contract is: " ++ CString ++ "\n" ++ "\t but the inferred signature is: ~s", [M, F, A, SigString])); - {error, Msg} -> + {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])) diff --git a/lib/typer/vsn.mk b/lib/typer/vsn.mk index 95584123751d..285fa62da31d 100644 --- a/lib/typer/vsn.mk +++ b/lib/typer/vsn.mk @@ -1 +1 @@ -TYPER_VSN = 0.1.7.3 +TYPER_VSN = 0.1.7.4 From cdd3a82020d7c6d0d9a3fe35469d66809685ddf5 Mon Sep 17 00:00:00 2001 From: Kostis Sagonas Date: Wed, 10 Feb 2010 10:57:33 +0200 Subject: [PATCH 3/8] hipe: Miscellaneous additions --- lib/hipe/Makefile | 2 ++ lib/hipe/flow/Makefile | 1 + lib/hipe/vsn.mk | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/hipe/Makefile b/lib/hipe/Makefile index 54efd7fcaf99..6682c9aac000 100644 --- a/lib/hipe/Makefile +++ b/lib/hipe/Makefile @@ -37,6 +37,8 @@ else SUB_DIRECTORIES = $(ALWAYS_SUBDIRS) endif +include native.mk + # # Default Subdir Targets # diff --git a/lib/hipe/flow/Makefile b/lib/hipe/flow/Makefile index 91dcfda6f5ba..02f610587b03 100644 --- a/lib/hipe/flow/Makefile +++ b/lib/hipe/flow/Makefile @@ -105,4 +105,5 @@ release_spec: opt release_docs_spec: $(EBIN)/hipe_bb.beam: hipe_bb.hrl +$(EBIN)/hipe_dominators.beam: cfg.hrl $(EBIN)/hipe_gen_cfg.beam: cfg.hrl cfg.inc ../main/hipe.hrl diff --git a/lib/hipe/vsn.mk b/lib/hipe/vsn.mk index c75ac5efe78e..129718a30542 100644 --- a/lib/hipe/vsn.mk +++ b/lib/hipe/vsn.mk @@ -1 +1 @@ -HIPE_VSN = 3.7.4 +HIPE_VSN = 3.7.5 From a9c8a2496d54668d2d46133ff0ef1787cd7b80d6 Mon Sep 17 00:00:00 2001 From: Kostis Sagonas Date: Wed, 10 Feb 2010 11:03:35 +0200 Subject: [PATCH 4/8] dialyzer: New version for the R13B04 release --- lib/dialyzer/README | 4 +- lib/dialyzer/RELEASE_NOTES | 18 +- lib/dialyzer/doc/manual.txt | 82 +- lib/dialyzer/doc/src/Makefile | 3 +- lib/dialyzer/doc/warnings.txt | 2 - lib/dialyzer/src/Makefile | 2 + lib/dialyzer/src/dialyzer.app.src | 5 +- lib/dialyzer/src/dialyzer.erl | 61 +- lib/dialyzer/src/dialyzer.hrl | 5 +- .../src/dialyzer_analysis_callgraph.erl | 51 +- lib/dialyzer/src/dialyzer_behaviours.erl | 324 +++++++ lib/dialyzer/src/dialyzer_callgraph.erl | 21 +- lib/dialyzer/src/dialyzer_cl.erl | 50 +- lib/dialyzer/src/dialyzer_cl_parse.erl | 46 +- lib/dialyzer/src/dialyzer_codeserver.erl | 77 +- lib/dialyzer/src/dialyzer_contracts.erl | 36 +- lib/dialyzer/src/dialyzer_dataflow.erl | 181 ++-- lib/dialyzer/src/dialyzer_dep.erl | 2 +- lib/dialyzer/src/dialyzer_gui_wx.erl | 61 +- lib/dialyzer/src/dialyzer_options.erl | 2 + lib/dialyzer/src/dialyzer_plt.erl | 138 ++- lib/dialyzer/src/dialyzer_races.erl | 165 ++-- lib/dialyzer/src/dialyzer_succ_typings.erl | 23 +- lib/dialyzer/src/dialyzer_typesig.erl | 64 +- lib/dialyzer/src/dialyzer_utils.erl | 128 ++- lib/dialyzer/vsn.mk | 2 +- lib/hipe/cerl/erl_bif_types.erl | 31 +- lib/hipe/cerl/erl_types.erl | 790 ++++++++++++------ 28 files changed, 1685 insertions(+), 689 deletions(-) create mode 100644 lib/dialyzer/src/dialyzer_behaviours.erl diff --git a/lib/dialyzer/README b/lib/dialyzer/README index e9b03883d2ab..82d0f5ec48c1 100644 --- a/lib/dialyzer/README +++ b/lib/dialyzer/README @@ -3,9 +3,7 @@ ## Author(s): Tobias Lindahl ## Kostis Sagonas ## -## Copyright: Held by the authors; all rights reserved (2004 - 2009). -## -## $Id$ +## Copyright: Held by the authors; all rights reserved (2004 - 2010). ##---------------------------------------------------------------------------- The DIALYZER, a DIscrepany AnaLYZer for ERlang programs. diff --git a/lib/dialyzer/RELEASE_NOTES b/lib/dialyzer/RELEASE_NOTES index 8205acf19229..b6681423277d 100644 --- a/lib/dialyzer/RELEASE_NOTES +++ b/lib/dialyzer/RELEASE_NOTES @@ -3,11 +3,25 @@ (in reversed chronological order) ============================================================================== +Version 2.2.0 (in Erlang/OTP R13B04) +------------------------------------ + - Much better support for opaque types (thanks to Manouk Manoukian). + - Added support for recursive types (experimental). + - Added support for parameterized modules. + - Dialyzer now warns when -specs state that a function returns some type + when in fact it does not. + - Added --no_native (-nn) option so that the user can bypass the native code + compilation that dialyzer heuristically performs when dialyzing many files. + - Fixed minor bug in the dialyzer script allowing the --wx option to bring + up the wx-based GUI regardless of its placement in the options list. + - Options --apps and -Wrace_conditions, which were added in the previous + version, are now properly documented in the manual. + Version 2.1.0 (in Erlang/OTP R13B03) ------------------------------------ - Dialyzer can statically detect some kinds of data races in Erlang programs. Use the new option -Wrace_conditions to enable the race analysis. - The technique is described in a paper which is available at: + The static analysis technique is described in a paper available at: http://www.it.uu.se/research/group/hipe/dialyzer/publications/races.pdf - Added support for packages (thanks to Maria Christakis). - There has been a major change in the default mode of Dialyzer. @@ -27,7 +41,7 @@ Version 2.1.0 (in Erlang/OTP R13B03) The new option can also take absolute file names as well as applications. Note that the application versions that will be included in the PLT are those that correspond to the Erlang/OTP system which is used. - - Dialyzer has a new wxWidgets based GUI (thanks to Elli Frangaki) + - Dialyzer has a new wxWidgets based GUI (thanks to Elli Fragkaki) for platforms where the wx application is available. Version 2.0.0 (in Erlang/OTP R13B02) diff --git a/lib/dialyzer/doc/manual.txt b/lib/dialyzer/doc/manual.txt index f1faed3c7933..dac61b74b1a6 100644 --- a/lib/dialyzer/doc/manual.txt +++ b/lib/dialyzer/doc/manual.txt @@ -2,8 +2,6 @@ ## File: doc/manual.txt ## Author(s): Tobias Lindahl ## Kostis Sagonas -## -## $Id$ ##---------------------------------------------------------------------------- The DIALYZER, a DIscrepany AnaLYZer for ERlang programs. @@ -126,22 +124,29 @@ The exit status of the command line version is: Usage: dialyzer [--help] [--version] [--shell] [--quiet] [--verbose] [-pa dir]* [--plt plt] [-Ddefine]* [-I include_dir]* - [--output_plt file] [-Wwarn]* [--src] - [-c applications] [-r applications] [-o outfile] - [--build_plt] [--add_to_plt] [--remove_from_plt] [--check_plt] - [--plt_info] [--get_warnings] + [--output_plt file] [-Wwarn]* [--src] [--gui | --wx] + [files_or_dirs] [-r dirs] [--apps applications] [-o outfile] + [--build_plt] [--add_to_plt] [--remove_from_plt] + [--check_plt] [--no_check_plt] [--plt_info] [--get_warnings] + [--no_native] Options: - -c applications (or --command-line applications) - Use Dialyzer from the command line (no GUI) to detect defects in the - specified applications (directories or .erl or .beam files) - -r applications - Same as -c only that directories are searched recursively for - subdirectories containing .erl or .beam files (depending on the - type of analysis) - -o outfile (or --output outfile) - When using Dialyzer from the command line, send the analysis - results in the specified \"outfile\" rather than in stdout + files_or_dirs (for backwards compatibility also as: -c files_or_dirs) + Use Dialyzer from the command line to detect defects in the + specified files or directories containing .erl or .beam files, + depending on the type of the analysis + -r dirs + Same as the previous but the specified directories are searched + recursively for subdirectories containing .erl or .beam files in + them, depending on the type of analysis + --apps applications + Option typically used when building or modifying PLT as in: + dialyzer --build_plt --apps erts kernel stdlib mnesia ... + to conveniently refer to library applications corresponding to the + Erlang/OTP installation. However, the option is general and can also + be used during analysis in order to refer to Erlang/OTP applications. + In addition, file or directory names can also be included, as in: + dialyzer --apps inets ssl ./ebin ../other_lib/ebin/my_module.beam --raw When using Dialyzer from the command line, output the raw analysis results (Erlang terms) instead of the formatted result. @@ -154,14 +159,14 @@ Options: When analyzing from source, pass the define to Dialyzer (**) -I include_dir When analyzing from source, pass the include_dir to Dialyzer (**) + -pa dir + Include dir in the path for Erlang (useful when analyzing files + that have '-include_lib()' directives) --output_plt file Store the plt at the specified file after building it --plt plt Use the specified plt as the initial plt (if the plt was built during setup the files will be checked for consistency) - -pa dir - Include dir in the path for Erlang (useful when analyzing files - that have '-include_lib()' directives) -Wwarn A family of options which selectively turn on/off warnings (for help on the names of warnings use dialyzer -Whelp) @@ -200,6 +205,19 @@ Options: --get_warnings Makes Dialyzer emit warnings even when manipulating the plt. Only emits warnings for files that are actually analyzed. + --dump_callgraph file + Dump the call graph into the specified file whose format is determined + by the file name extension. Supported extensions are: raw, dot, and ps. + If something else is used as file name extension, default format '.raw' + will be used. + --no_native (or -nn) + Bypass the native code compilation of some key files that dialyzer + heuristically performs when dialyzing many files; this avoids the + compilation time but it may result in (much) longer analysis time. + --gui + Use the gs-based GUI. + --wx + Use the wx-based GUI. Note: * denotes that multiple occurrences of these options are possible. @@ -221,14 +239,22 @@ Warning options: Include warnings for function calls which ignore the return value(s). -Werror_handling *** Include warnings for functions that only return by means of an exception. + -Wrace_conditions *** + Include warnings for possible race conditions. + -Wbehaviours *** + Include warnings about behaviour callbacks which drift from the published + recommended interfaces. -Wunderspecs *** Warn about underspecified functions - (the -spec is strictly more allowing than the success typing) + (those whose -spec is strictly more allowing than the success typing). + +The following options are also available but their use is not recommended: +(they are mostly for Dialyzer developers and internal debugging) -Woverspecs *** Warn about overspecified functions - (the -spec is strictly less allowing than the success typing) + (those whose -spec is strictly less allowing than the success typing). -Wspecdiffs *** - Warn when the -spec is different than the success typing + Warn when the -spec is different than the success typing. Note: *** These are options that turn on warnings rather than turning them off. @@ -307,9 +333,7 @@ are using frequently. The PLT is built using the --build_plt option to dialyzer. The following command builds the recommended minimal PLT for OTP. -dialyzer --build_plt -r $ERL_TOP/lib/stdlib/ebin\ - $ERL_TOP/lib/kernel/ebin\ - $ERL_TOP/lib/mnesia/ebin + dialyzer --build_plt --apps erts kernel stdlib mnesia Dialyzer will look if there is an environment variable called $DIALYZER_PLT and place the PLT at this location. If no such variable @@ -321,22 +345,22 @@ You can also add information to an existing plt using the --add_to_plt option. Suppose you want to also include the compiler in the PLT and place it in a new PLT, then give the command -dialyzer --add_to_plt -r $ERL_TOP/lib/compiler/ebin --output_plt my.plt + dialyzer --add_to_plt --apps compiler --output_plt my.plt Then you would like to add your favorite application my_app to the new plt. -dialyzer --add_to_plt --plt my.plt -r /my_app/ebin + dialyzer --add_to_plt --plt my.plt -r /my_app/ebin But you realize that it is unnecessary to have compiler in this one. -dialyzer --remove_from_plt --plt my.plt -r $ERL_TOP/lib/compiler/ebin + dialyzer --remove_from_plt --plt my.plt ---apps compiler Later, when you have fixed a bug in your application my_app, you want to update the plt so that it will be fresh the next time you run Dialyzer, run the command -dialyzer --check_plt --plt my.plt + dialyzer --check_plt --plt my.plt Dialyzer will then reanalyze the files that have been changed, and the files that depend on these files. Note that this consistency check diff --git a/lib/dialyzer/doc/src/Makefile b/lib/dialyzer/doc/src/Makefile index 37bcb49de0a2..45b0ffa5ffb0 100755 --- a/lib/dialyzer/doc/src/Makefile +++ b/lib/dialyzer/doc/src/Makefile @@ -13,8 +13,7 @@ # Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings # AB. All Rights Reserved.'' # -# $Id$ -# + include $(ERL_TOP)/make/target.mk include $(ERL_TOP)/make/$(TARGET)/otp.mk diff --git a/lib/dialyzer/doc/warnings.txt b/lib/dialyzer/doc/warnings.txt index fd8cc0dc608b..5c93f15a4790 100644 --- a/lib/dialyzer/doc/warnings.txt +++ b/lib/dialyzer/doc/warnings.txt @@ -2,8 +2,6 @@ ## File: doc/warnings.txt ## Author(s): Tobias Lindahl ## Kostis Sagonas -## -## $Id$ ##---------------------------------------------------------------------------- diff --git a/lib/dialyzer/src/Makefile b/lib/dialyzer/src/Makefile index ffdc0c6dcde7..2521a866f3be 100644 --- a/lib/dialyzer/src/Makefile +++ b/lib/dialyzer/src/Makefile @@ -48,6 +48,7 @@ DIALYZER_DIR = $(ERL_TOP)/lib/dialyzer MODULES = \ dialyzer \ dialyzer_analysis_callgraph \ + dialyzer_behaviours \ dialyzer_callgraph \ dialyzer_cl \ dialyzer_cl_parse \ @@ -128,6 +129,7 @@ $(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk $(EBIN)/dialyzer.beam: dialyzer.hrl $(EBIN)/dialyzer_analysis_callgraph.beam: dialyzer.hrl $(EBIN)/dialyzer_callgraph.beam: dialyzer.hrl +$(EBIN)/dialyzer_behaviours.beam: dialyzer.hrl $(EBIN)/dialyzer_cl.beam: dialyzer.hrl ../../kernel/include/file.hrl $(EBIN)/dialyzer_cl_parse.beam: dialyzer.hrl $(EBIN)/dialyzer_codeserver.beam: dialyzer.hrl diff --git a/lib/dialyzer/src/dialyzer.app.src b/lib/dialyzer/src/dialyzer.app.src index c1d109812ca7..bd8b79bf21c2 100644 --- a/lib/dialyzer/src/dialyzer.app.src +++ b/lib/dialyzer/src/dialyzer.app.src @@ -23,6 +23,7 @@ {vsn, "%VSN%"}, {modules, [dialyzer, dialyzer_analysis_callgraph, + dialyzer_behaviours, dialyzer_callgraph, dialyzer_cl, dialyzer_cl_parse, @@ -30,7 +31,9 @@ dialyzer_contracts, dialyzer_dataflow, dialyzer_dep, + dialyzer_explanation, dialyzer_gui, + dialyzer_gui_wx, dialyzer_options, dialyzer_plt, dialyzer_races, @@ -38,5 +41,5 @@ dialyzer_typesig, dialyzer_utils]}, {registered, []}, - {applications, [compiler, gs, hipe, kernel, stdlib]}, + {applications, [compiler, gs, hipe, kernel, stdlib, wx]}, {env, []}]}. diff --git a/lib/dialyzer/src/dialyzer.erl b/lib/dialyzer/src/dialyzer.erl index c1897ed892f5..95955db7e970 100644 --- a/lib/dialyzer/src/dialyzer.erl +++ b/lib/dialyzer/src/dialyzer.erl @@ -379,7 +379,7 @@ message_to_string({spec_missing_fun, [M, F, A]}) -> [M, F, A]); %%----- Warnings for opaque type violations ------------------- message_to_string({call_with_opaque, [M, F, Args, ArgNs, ExpArgs]}) -> - io_lib:format("The call ~w:~w~s contains ~s when ~s\n", + io_lib:format("The call ~w:~w~s contains ~s argument when ~s\n", [M, F, Args, form_positions(ArgNs), form_expected(ExpArgs)]); message_to_string({call_without_opaque, [M, F, Args, ExpectedTriples]}) -> io_lib:format("The call ~w:~w~s does not have ~s\n", @@ -403,8 +403,25 @@ message_to_string({opaque_type_test, [Fun, Opaque]}) -> io_lib:format("The type test ~s(~s) breaks the opaqueness of the term ~s\n", [Fun, Opaque, Opaque]); %%----- Warnings for concurrency errors -------------------- message_to_string({race_condition, [M, F, Args, Reason]}) -> - io_lib:format("The call ~w:~w~s ~s\n", [M, F, Args, Reason]). - + io_lib:format("The call ~w:~w~s ~s\n", [M, F, Args, Reason]); +%%----- Warnings for behaviour errors -------------------- +message_to_string({callback_type_mismatch, [B, F, A, O]}) -> + io_lib:format("The inferred return type of the ~w/~w callback includes the" + " type ~s which is not a valid return for the ~w behaviour\n", + [F, A, erl_types:t_to_string(O), B]); +message_to_string({callback_arg_type_mismatch, [B, F, A, N, O]}) -> + io_lib:format("The inferred type of the ~s argument of ~w/~w callback" + " includes the type ~s which is not valid for the ~w behaviour" + "\n", [ordinal(N), F, A, erl_types:t_to_string(O), B]); +message_to_string({callback_missing, [B, F, A]}) -> + io_lib:format("Undefined callback function ~w/~w (behaviour '~w')\n", + [F, A, B]); +message_to_string({invalid_spec, [B, F, A, R]}) -> + io_lib:format("The spec for the ~w:~w/~w callback is not correct: ~s\n", + [B, F, A, R]); +message_to_string({spec_missing, [B, F, A]}) -> + io_lib:format("Type info about ~w:~w/~w callback is not available\n", + [B, F, A]). %%----------------------------------------------------------------------------- %% Auxiliary functions below @@ -421,8 +438,8 @@ call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet, io_lib:format("will never return since the success typing arguments" " are ~s\n", [SigArgs]); false -> - io_lib:format("will never return since it differs in argument" - " ~s from the success typing arguments: ~s\n", + io_lib:format("will never return since it differs in the ~s argument" + " from the success typing arguments: ~s\n", [PositionString, SigArgs]) end; only_contract -> @@ -431,7 +448,7 @@ call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet, %% We do not know which arguments caused the failure io_lib:format("breaks the contract ~s\n", [Contract]); false -> - io_lib:format("breaks the contract ~s in argument ~s\n", + io_lib:format("breaks the contract ~s in the ~s argument\n", [Contract, PositionString]) end; both -> @@ -441,22 +458,26 @@ call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet, form_positions(ArgNs) -> case ArgNs of - [_] -> "an opaque term in "; - [_,_|_] -> "opaque terms in " - end ++ form_position_string(ArgNs). + [_] -> "an opaque term as "; + [_,_|_] -> "opaque terms as " + end ++ form_position_string(ArgNs) ++ + case ArgNs of + [_] -> " argument"; + [_,_|_] -> " arguments" + end. %% We know which positions N are to blame; %% the list of triples will never be empty. form_expected_without_opaque([{N, T, TStr}]) -> case erl_types:t_is_opaque(T) of true -> - io_lib:format("an opaque term of type ~s in ", [TStr]); + io_lib:format("an opaque term of type ~s as ", [TStr]); false -> - io_lib:format("a term of type ~s (with opaque subterms) in ", [TStr]) - end ++ form_position_string([N]); + io_lib:format("a term of type ~s (with opaque subterms) as ", [TStr]) + end ++ form_position_string([N]) ++ " argument"; form_expected_without_opaque(ExpectedTriples) -> %% TODO: can do much better here {ArgNs, _Ts, _TStrs} = lists:unzip3(ExpectedTriples), - "opaque terms in " ++ form_position_string(ArgNs). + "opaque terms as " ++ form_position_string(ArgNs) ++ " arguments". form_expected(ExpectedArgs) -> case ExpectedArgs of @@ -472,9 +493,15 @@ form_expected(ExpectedArgs) -> form_position_string(ArgNs) -> case ArgNs of [] -> ""; - [N1] -> io_lib:format("position ~w", [N1]); + [N1] -> ordinal(N1); [_,_|_] -> - " and"++ArgString = lists:flatten([io_lib:format(" and ~w", [N]) - || N <- ArgNs]), - "positions" ++ ArgString + [Last|Prevs] = lists:reverse(ArgNs), + ", " ++ Head = lists:flatten([io_lib:format(", ~s",[ordinal(N)]) || + N <- lists:reverse(Prevs)]), + Head ++ " and " ++ ordinal(Last) end. + +ordinal(1) -> "1st"; +ordinal(2) -> "2nd"; +ordinal(3) -> "3rd"; +ordinal(N) when is_integer(N) -> io_lib:format("~wth",[N]). diff --git a/lib/dialyzer/src/dialyzer.hrl b/lib/dialyzer/src/dialyzer.hrl index f0f9bd25d70f..d50b8720f8c9 100644 --- a/lib/dialyzer/src/dialyzer.hrl +++ b/lib/dialyzer/src/dialyzer.hrl @@ -55,6 +55,7 @@ -define(WARN_CALLGRAPH, warn_callgraph). -define(WARN_UNMATCHED_RETURN, warn_umatched_return). -define(WARN_RACE_CONDITION, warn_race_condition). +-define(WARN_BEHAVIOUR,warn_behaviour). %% %% The following type has double role: @@ -68,7 +69,8 @@ | ?WARN_CONTRACT_TYPES | ?WARN_CONTRACT_SYNTAX | ?WARN_CONTRACT_NOT_EQUAL | ?WARN_CONTRACT_SUBTYPE | ?WARN_CONTRACT_SUPERTYPE | ?WARN_CALLGRAPH - | ?WARN_UNMATCHED_RETURN | ?WARN_RACE_CONDITION. + | ?WARN_UNMATCHED_RETURN | ?WARN_RACE_CONDITION + | ?WARN_BEHAVIOUR. %% %% This is the representation of each warning as they will be returned @@ -118,6 +120,7 @@ plt :: dialyzer_plt:plt(), use_contracts = true :: boolean(), race_detection = false :: boolean(), + behaviours_chk = false :: boolean(), callgraph_file = "" :: file:filename()}). -record(options, {files = [] :: [file:filename()], diff --git a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl index 97d63a1f149a..db62dcebac41 100644 --- a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl +++ b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl @@ -43,7 +43,8 @@ parent :: pid(), plt :: dialyzer_plt:plt(), start_from = byte_code :: start_from(), - use_contracts = true :: boolean() + use_contracts = true :: boolean(), + behaviours = {false,[]} :: {boolean(),[atom()]} }). -record(server_state, {parent :: pid(), legal_warnings :: [dial_warn_tag()]}). @@ -56,7 +57,9 @@ start(Parent, LegalWarnings, Analysis) -> RacesOn = ordsets:is_element(?WARN_RACE_CONDITION, LegalWarnings), - Analysis0 = Analysis#analysis{race_detection = RacesOn}, + BehavOn = ordsets:is_element(?WARN_BEHAVIOUR, LegalWarnings), + Analysis0 = Analysis#analysis{race_detection = RacesOn, + behaviours_chk = BehavOn}, Analysis1 = expand_files(Analysis0), Analysis2 = run_analysis(Analysis1), State = #server_state{parent = Parent, legal_warnings = LegalWarnings}, @@ -93,6 +96,9 @@ loop(#server_state{parent = Parent, legal_warnings = LegalWarnings} = State, end; {AnalPid, ext_calls, NewExtCalls} -> loop(State, Analysis, NewExtCalls); + {AnalPid, unknown_behaviours, UnknownBehaviour} -> + send_unknown_behaviours(Parent, UnknownBehaviour), + loop(State, Analysis, ExtCalls); {AnalPid, mod_deps, ModDeps} -> send_mod_deps(Parent, ModDeps), loop(State, Analysis, ExtCalls); @@ -116,7 +122,9 @@ analysis_start(Parent, Analysis) -> plt = Plt, parent = Parent, start_from = Analysis#analysis.start_from, - use_contracts = Analysis#analysis.use_contracts + use_contracts = Analysis#analysis.use_contracts, + behaviours = {Analysis#analysis.behaviours_chk, + []} }, Files = ordsets:from_list(Analysis#analysis.files), {Callgraph, NoWarn, TmpCServer0} = compile_and_store(Files, State), @@ -167,11 +175,13 @@ analyze_callgraph(Callgraph, State) -> State#analysis_state{plt = NewPlt}; succ_typings -> NoWarn = State#analysis_state.no_warn_unused, + {BehavioursChk, _Known} = State#analysis_state.behaviours, DocPlt = State#analysis_state.doc_plt, Callgraph1 = dialyzer_callgraph:finalize(Callgraph), {Warnings, NewPlt, NewDocPlt} = dialyzer_succ_typings:get_warnings(Callgraph1, Plt, DocPlt, - Codeserver, NoWarn, Parent), + Codeserver, NoWarn, Parent, + BehavioursChk), dialyzer_callgraph:delete(Callgraph1), send_warnings(State#analysis_state.parent, Warnings), State#analysis_state{plt = NewPlt, doc_plt = NewDocPlt} @@ -186,7 +196,9 @@ compile_and_store(Files, #analysis_state{codeserver = CServer, include_dirs = Dirs, parent = Parent, use_contracts = UseContracts, - start_from = StartFrom} = State) -> + start_from = StartFrom, + behaviours = {BehChk, _} + } = State) -> send_log(Parent, "Reading files and computing callgraph... "), {T1, _} = statistics(runtime), Includes = [{i, D} || D <- Dirs], @@ -234,18 +246,37 @@ compile_and_store(Files, #analysis_state{codeserver = CServer, {T2, _} = statistics(runtime), Msg1 = io_lib:format("done in ~.2f secs\nRemoving edges... ", [(T2-T1)/1000]), send_log(Parent, Msg1), - NewCallgraph2 = cleanup_callgraph(State, NewCServer, NewCallgraph1, Modules), + {KnownBehaviours, UnknownBehaviours} = + dialyzer_behaviours:get_behaviours(Modules, NewCServer), + if UnknownBehaviours =:= [] -> ok; + true -> send_unknown_behaviours(Parent, UnknownBehaviours) + end, + State1 = State#analysis_state{behaviours = {BehChk,KnownBehaviours}}, + NewCallgraph2 = cleanup_callgraph(State1, NewCServer, NewCallgraph1, Modules), {T3, _} = statistics(runtime), Msg2 = io_lib:format("done in ~.2f secs\n", [(T3-T2)/1000]), send_log(Parent, Msg2), {NewCallgraph2, sets:from_list(NoWarn), NewCServer}. cleanup_callgraph(#analysis_state{plt = InitPlt, parent = Parent, - codeserver = CodeServer}, + codeserver = CodeServer, + behaviours = {BehChk, KnownBehaviours} + }, CServer, Callgraph, Modules) -> ModuleDeps = dialyzer_callgraph:module_deps(Callgraph), send_mod_deps(Parent, ModuleDeps), {Callgraph1, ExtCalls} = dialyzer_callgraph:remove_external(Callgraph), + if BehChk -> + RelevantAPICalls = + dialyzer_behaviours:get_behaviour_apis(KnownBehaviours), + BehaviourAPICalls = [Call || {_From, To} = Call <- ExtCalls, + lists:member(To, RelevantAPICalls)], + Callgraph2 = + dialyzer_callgraph:put_behaviour_api_calls(BehaviourAPICalls, + Callgraph1); + true -> + Callgraph2 = Callgraph1 + end, ExtCalls1 = [Call || Call = {_From, To} <- ExtCalls, not dialyzer_plt:contains_mfa(InitPlt, To)], {BadCalls1, RealExtCalls} = @@ -268,7 +299,7 @@ cleanup_callgraph(#analysis_state{plt = InitPlt, parent = Parent, true -> send_ext_calls(Parent, lists:usort([To || {_From, To} <- RealExtCalls])) end, - Callgraph1. + Callgraph2. compile_src(File, Includes, Defines, Callgraph, CServer, UseContracts) -> DefaultIncludes = default_includes(filename:dirname(File)), @@ -445,6 +476,10 @@ send_ext_calls(Parent, ExtCalls) -> Parent ! {self(), ext_calls, ExtCalls}, ok. +send_unknown_behaviours(Parent, UnknownBehaviours) -> + Parent ! {self(), unknown_behaviours, UnknownBehaviours}, + ok. + send_codeserver_plt(Parent, CServer, Plt ) -> Parent ! {self(), cserver, CServer, Plt}, ok. diff --git a/lib/dialyzer/src/dialyzer_behaviours.erl b/lib/dialyzer/src/dialyzer_behaviours.erl new file mode 100644 index 000000000000..61cdcf27c008 --- /dev/null +++ b/lib/dialyzer/src/dialyzer_behaviours.erl @@ -0,0 +1,324 @@ +%% -*- erlang-indent-level: 2 -*- +%%----------------------------------------------------------------------- +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2006-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%%%------------------------------------------------------------------- +%%% File : dialyzer_behaviours.erl +%%% Authors : Stavros Aronis +%%% Description : Tools for analyzing proper behaviour usage. +%%% +%%% Created : 28 Oct 2009 by Stavros Aronis +%%%------------------------------------------------------------------- +%%% NOTE: This module is currently experimental -- do NOT rely on it! +%%%------------------------------------------------------------------- + +-module(dialyzer_behaviours). + +-export([check_callbacks/4, get_behaviours/2, get_behaviour_apis/1, + translate_behaviour_api_call/5, translatable_behaviours/1, + translate_callgraph/3]). + +%%-------------------------------------------------------------------- + +-include("dialyzer.hrl"). + +%%-------------------------------------------------------------------- + +-record(state, {plt :: dialyzer_plt:plt(), + codeserver :: dialyzer_codeserver:codeserver(), + filename :: string(), + behlines :: [{atom(), number()}]}). + +-spec get_behaviours([module()], dialyzer_codeserver:codeserver()) -> + {[atom()], [atom()]}. + +get_behaviours(Modules, Codeserver) -> + get_behaviours(Modules, Codeserver, [], []). + +-spec check_callbacks(module(), [{cerl:cerl(), cerl:cerl()}], + dialyzer_plt:plt(), + dialyzer_codeserver:codeserver()) -> [dial_warning()]. + +check_callbacks(Module, Attrs, Plt, Codeserver) -> + {Behaviours, BehLines} = get_behaviours(Attrs), + case Behaviours of + [] -> []; + _ -> {_Var,Code} = + dialyzer_codeserver:lookup_mfa_code({Module,module_info,0}, + Codeserver), + File = get_file(cerl:get_ann(Code)), + State = #state{plt = Plt, codeserver = Codeserver, filename = File, + behlines = BehLines}, + Warnings = get_warnings(Module, Behaviours, State), + [add_tag_file_line(Module, W, State) || W <- Warnings] + end. + +-spec translatable_behaviours(cerl:c_module()) -> [{atom(),[_]}]. + +translatable_behaviours(Tree) -> + Attrs = cerl:module_attrs(Tree), + {Behaviours, _BehLines} = get_behaviours(Attrs), + [{B, Calls} || B <- Behaviours, (Calls = behaviour_api_calls(B)) =/= []]. + +-spec get_behaviour_apis([atom()]) -> [mfa()]. + +get_behaviour_apis(Behaviours) -> + get_behaviour_apis(Behaviours, []). + +-spec translate_behaviour_api_call(_, _, _, _, _) -> _. + +translate_behaviour_api_call(_Fun, _ArgTypes, _Args, _Module, []) -> + plain_call; +translate_behaviour_api_call({Module, Fun, Arity}, ArgTypes, Args, + CallbackModule, BehApiInfo) -> + case lists:keyfind(Module, 1, BehApiInfo) of + false -> plain_call; + {Module, Calls} -> + case lists:keyfind({Fun, Arity}, 1, Calls) of + false -> plain_call; + {{Fun, Arity}, {CFun, CArity, COrder}} -> + {{CallbackModule, CFun, CArity}, + [nth_or_0(N, ArgTypes, erl_types:t_any()) || N <-COrder], + [nth_or_0(N, Args, bypassed) || N <-COrder]} + end + end; +translate_behaviour_api_call(_Fun, _ArgTypes, _Args, _Module, _BehApiInfo) -> + plain_call. + +-spec translate_callgraph([{atom(), _}], atom(), dialyzer_callgraph:callgraph()) + -> dialyzer_callgraph:callgraph(). + +translate_callgraph([{Behaviour,_}|Behaviours], Module, Callgraph) -> + UsedCalls = [Call || {_From, {M, _F, _A}} = Call <- + dialyzer_callgraph:get_behaviour_api_calls(Callgraph), + M =:= Behaviour], + Calls = [{{Behaviour, API, Arity}, Callback} || + {{API, Arity}, Callback} <- behaviour_api_calls(Behaviour)], + DirectCalls = [{From, {Module, Fun, Arity}} || + {From, To} <- UsedCalls,{API, {Fun, Arity, _Ord}} <- Calls, + To =:= API], + NewCallgraph = dialyzer_callgraph:add_edges(DirectCalls, Callgraph), + translate_callgraph(Behaviours, Module, NewCallgraph); +translate_callgraph([], _Module, Callgraph) -> + Callgraph. + +%%-------------------------------------------------------------------- + +get_behaviours(Attrs) -> + BehaviourListsAndLine = [{cerl:concrete(L2), hd(cerl:get_ann(L2))} || + {L1, L2} <- Attrs, cerl:is_literal(L1), + cerl:is_literal(L2), cerl:concrete(L1) =:= 'behaviour'], + Behaviours = lists:append([Behs || {Behs,_} <- BehaviourListsAndLine]), + BehLines = [{B,L} || {L1,L} <- BehaviourListsAndLine, B <- L1], + {Behaviours, BehLines}. + +get_warnings(Module, Behaviours, State) -> + get_warnings(Module, Behaviours, State, []). + +get_warnings(_, [], _, Acc) -> + Acc; +get_warnings(Module, [Behaviour|Rest], State, Acc) -> + Warnings = check_behaviour(Module, Behaviour, State), + get_warnings(Module, Rest, State, Warnings ++ Acc). + +check_behaviour(Module, Behaviour, State) -> + try + Callbacks = Behaviour:behaviour_info(callbacks), + Fun = fun({_,_,_}) -> true; + (_) -> false + end, + case lists:any(Fun, Callbacks) of + true -> check_all_callbacks(Module, Behaviour, Callbacks, State); + false -> [] + end + catch + _:_ -> [] + end. + +check_all_callbacks(Module, Behaviour, Callbacks, State) -> + check_all_callbacks(Module, Behaviour, Callbacks, State, []). + +check_all_callbacks(_Module, _Behaviour, [], _State, Acc) -> + Acc; +check_all_callbacks(Module, Behaviour, [{Fun, Arity, Spec}|Rest], State, Acc) -> + Records = dialyzer_codeserver:get_records(State#state.codeserver), + case parse_spec(Spec, Records) of + {ok, Fun, Type} -> + RetType = erl_types:t_fun_range(Type), + ArgTypes = erl_types:t_fun_args(Type), + Warns = check_callback(Module, Behaviour, Fun, Arity, RetType, + ArgTypes, State#state.plt); + Else -> + Warns = [{invalid_spec, [Behaviour, Fun, Arity, reason_spec_error(Else)]}] + end, + check_all_callbacks(Module, Behaviour, Rest, State, Warns ++ Acc); +check_all_callbacks(Module, Behaviour, [{Fun, Arity}|Rest], State, Acc) -> + Warns = {spec_missing, [Behaviour, Fun, Arity]}, + check_all_callbacks(Module, Behaviour, Rest, State, [Warns|Acc]). + +parse_spec(String, Records) -> + case erl_scan:string(String) of + {ok, Tokens, _} -> + case erl_parse:parse(Tokens) of + {ok, Form} -> + case Form of + {attribute, _, 'spec', {{Fun, _}, [TypeForm|_Constraint]}} -> + MaybeRemoteType = erl_types:t_from_form(TypeForm), + try + Type = erl_types:t_solve_remote(MaybeRemoteType, Records), + {ok, Fun, Type} + catch + throw:{error,Msg} -> {spec_remote_error, Msg} + end; + _Other -> not_a_spec + end; + {error, {Line, _, Msg}} -> {spec_parser_error, Line, Msg} + end; + _Other -> + lexer_error + end. + +reason_spec_error({spec_remote_error, Msg}) -> + io_lib:format("Remote type solver error: ~s. Make sure the behaviour source is included in the analysis or the plt",[Msg]); +reason_spec_error(not_a_spec) -> + "This is not a spec"; +reason_spec_error({spec_parser_error, Line, Msg}) -> + io_lib:format("~s line of the spec: ~s", [ordinal(Line),Msg]); +reason_spec_error(lexer_error) -> + "Lexical error". + +ordinal(1) -> "1st"; +ordinal(2) -> "2nd"; +ordinal(3) -> "3rd"; +ordinal(N) when is_integer(N) -> io_lib:format("~wth",[N]). + +check_callback(Module, Behaviour, Fun, Arity, XRetType, XArgTypes, Plt) -> + LookupType = dialyzer_plt:lookup(Plt, {Module, Fun, Arity}), + case LookupType of + {value, {Type,Args}} -> + Warn1 = case unifiable(Type, XRetType) of + [] -> []; + Offenders -> + [{callback_type_mismatch, + [Behaviour, Fun, Arity, erl_types:t_sup(Offenders)]}] + end, + ZipArgs = lists:zip3(lists:seq(1, Arity), Args, XArgTypes), + Warn2 = [{callback_arg_type_mismatch, + [Behaviour, Fun, Arity, N, + erl_types:t_sup(Offenders)]} || + {Offenders, N} <- [check_callback_1(V) || V <- ZipArgs], + Offenders =/= []], + Warn1 ++ Warn2; + _ -> [{callback_missing, [Behaviour, Fun, Arity]}] + end. + +check_callback_1({N, T1, T2}) -> + {unifiable(T1, T2), N}. + +unifiable(Type1, Type2) -> + List1 = erl_types:t_elements(Type1), + List2 = erl_types:t_elements(Type2), + [T || T <- List1, + lists:all(fun(T1) -> + erl_types:t_is_none(erl_types:t_inf(T, T1, opaque)) + end, List2)]. + +add_tag_file_line(_Module, {Tag, [B|_R]} = Warn, State) + when Tag =:= spec_missing; + Tag =:= invalid_spec; + Tag =:= callback_missing -> + {B, Line} = lists:keyfind(B, 1, State#state.behlines), + {?WARN_BEHAVIOUR, {State#state.filename, Line}, Warn}; +add_tag_file_line(Module, {_Tag, [_B, Fun, Arity|_R]} = Warn, State) -> + {_A, FunCode} = + dialyzer_codeserver:lookup_mfa_code({Module, Fun, Arity}, + State#state.codeserver), + Anns = cerl:get_ann(FunCode), + FileLine = {get_file(Anns), get_line(Anns)}, + {?WARN_BEHAVIOUR, FileLine, Warn}. + +get_line([Line|_]) when is_integer(Line) -> Line; +get_line([_|Tail]) -> get_line(Tail); +get_line([]) -> -1. + +get_file([{file, File}|_]) -> File; +get_file([_|Tail]) -> get_file(Tail). + +%%------------------------------------------------------------------------------ + +get_behaviours([], _Codeserver, KnownAcc, UnknownAcc) -> + {KnownAcc, UnknownAcc}; +get_behaviours([M|Rest], Codeserver, KnownAcc, UnknownAcc) -> + Tree = dialyzer_codeserver:lookup_mod_code(M, Codeserver), + Attrs = cerl:module_attrs(Tree), + {Behaviours, _BehLines} = get_behaviours(Attrs), + {Known, Unknown} = call_behaviours(Behaviours), + get_behaviours(Rest, Codeserver, Known ++ KnownAcc, Unknown ++ UnknownAcc). + +call_behaviours(Behaviours) -> + call_behaviours(Behaviours, [], []). +call_behaviours([], KnownAcc, UnknownAcc) -> + {lists:reverse(KnownAcc), lists:reverse(UnknownAcc)}; +call_behaviours([Behaviour|Rest], KnownAcc, UnknownAcc) -> + try + Callbacks = Behaviour:behaviour_info(callbacks), + Fun = fun({_,_,_}) -> true; + (_) -> false + end, + case lists:any(Fun, Callbacks) of + false -> call_behaviours(Rest, KnownAcc, [Behaviour | UnknownAcc]); + true -> call_behaviours(Rest, [Behaviour | KnownAcc], UnknownAcc) + end + catch + _:_ -> call_behaviours(Rest, KnownAcc, [Behaviour | UnknownAcc]) + end. + +%------------------------------------------------------------------------------- + +get_behaviour_apis([], Acc) -> + Acc; +get_behaviour_apis([Behaviour | Rest], Acc) -> + MFAs = [{Behaviour, Fun, Arity} || + {{Fun, Arity}, _} <- behaviour_api_calls(Behaviour)], + get_behaviour_apis(Rest, MFAs ++ Acc). + +%------------------------------------------------------------------------------- + +nth_or_0(0, _List, Zero) -> + Zero; +nth_or_0(N, List, _Zero) -> + lists:nth(N, List). + +%------------------------------------------------------------------------------- + +behaviour_api_calls(gen_server) -> + [{{start_link, 3}, {init, 1, [2]}}, + {{start_link, 4}, {init, 1, [3]}}, + {{start, 3}, {init, 1, [2]}}, + {{start, 4}, {init, 1, [3]}}, + {{call, 2}, {handle_call, 3, [2, 0, 0]}}, + {{call, 3}, {handle_call, 3, [2, 0, 0]}}, + {{multi_call, 2}, {handle_call, 3, [2, 0, 0]}}, + {{multi_call, 3}, {handle_call, 3, [3, 0, 0]}}, + {{multi_call, 4}, {handle_call, 3, [3, 0, 0]}}, + {{cast, 2}, {handle_cast, 2, [2, 0]}}, + {{abcast, 2}, {handle_cast, 2, [2, 0]}}, + {{abcast, 3}, {handle_cast, 2, [3, 0]}}]; +behaviour_api_calls(_Other) -> + []. diff --git a/lib/dialyzer/src/dialyzer_callgraph.erl b/lib/dialyzer/src/dialyzer_callgraph.erl index 21d31df71c91..1f79e164492d 100644 --- a/lib/dialyzer/src/dialyzer_callgraph.erl +++ b/lib/dialyzer/src/dialyzer_callgraph.erl @@ -27,7 +27,8 @@ %%%------------------------------------------------------------------- -module(dialyzer_callgraph). --export([all_nodes/1, +-export([add_edges/2, + all_nodes/1, delete/1, finalize/1, is_escaping/2, @@ -55,7 +56,8 @@ -export([cleanup/1, get_digraph/1, get_named_tables/1, get_public_tables/1, get_race_code/1, get_race_detection/1, race_code_new/1, put_race_code/2, put_race_detection/2, put_named_tables/2, - put_public_tables/2]). + put_public_tables/2, put_behaviour_api_calls/2, + get_behaviour_api_calls/1]). -include("dialyzer.hrl"). @@ -97,7 +99,8 @@ race_code = dict:new() :: dict(), public_tables = [] :: [label()], named_tables = [] :: [string()], - race_detection = false :: boolean()}). + race_detection = false :: boolean(), + beh_api_calls = [] :: [{mfa(), mfa()}]}). %% Exported Types @@ -695,3 +698,15 @@ to_ps(#callgraph{} = CG, File, Args) -> Command = io_lib:format("dot -Tps ~s -o ~s ~s", [Args, File, Dot_File]), _ = os:cmd(Command), ok. + +%------------------------------------------------------------------------------- + +-spec put_behaviour_api_calls([{mfa(), mfa()}], callgraph()) -> callgraph(). + +put_behaviour_api_calls(Calls, Callgraph) -> + Callgraph#callgraph{beh_api_calls = Calls}. + +-spec get_behaviour_api_calls(callgraph()) -> [{mfa(), mfa()}]. + +get_behaviour_api_calls(Callgraph) -> + Callgraph#callgraph.beh_api_calls. diff --git a/lib/dialyzer/src/dialyzer_cl.erl b/lib/dialyzer/src/dialyzer_cl.erl index ab56a4e6d359..724383d06a09 100644 --- a/lib/dialyzer/src/dialyzer_cl.erl +++ b/lib/dialyzer/src/dialyzer_cl.erl @@ -46,7 +46,8 @@ plt_info = none :: 'none' | dialyzer_plt:plt_info(), report_mode = normal :: rep_mode(), return_status= ?RET_NOTHING_SUSPICIOUS :: dial_ret(), - stored_warnings = [] :: [dial_warning()] + stored_warnings = [] :: [dial_warning()], + unknown_behaviours = [] :: [atom()] }). %%-------------------------------------------------------------------- @@ -440,7 +441,9 @@ expand_dependent_modules_1([], Included, _ModDeps) -> -spec hipe_compile([file:filename()], #options{}) -> 'ok'. hipe_compile(Files, #options{erlang_mode = ErlangMode} = Options) -> - case (length(Files) < ?MIN_FILES_FOR_NATIVE_COMPILE) orelse ErlangMode of + NoNative = (get(dialyzer_options_native) =:= false), + FewFiles = (length(Files) < ?MIN_FILES_FOR_NATIVE_COMPILE), + case NoNative orelse FewFiles orelse ErlangMode of true -> ok; false -> case erlang:system_info(hipe_architecture) of @@ -528,6 +531,9 @@ cl_loop(State, LogCache) -> {BackendPid, warnings, Warnings} -> NewState = store_warnings(State, Warnings), cl_loop(NewState, LogCache); + {BackendPid, unknown_behaviours, Behaviours} -> + NewState = store_unknown_behaviours(State, Behaviours), + cl_loop(NewState, LogCache); {BackendPid, done, NewPlt, _NewDocPlt} -> return_value(State, NewPlt); {BackendPid, ext_calls, ExtCalls} -> @@ -568,6 +574,11 @@ format_log_cache(LogCache) -> store_warnings(#cl_state{stored_warnings = StoredWarnings} = St, Warnings) -> St#cl_state{stored_warnings = StoredWarnings ++ Warnings}. +-spec store_unknown_behaviours(#cl_state{}, [_]) -> #cl_state{}. + +store_unknown_behaviours(#cl_state{unknown_behaviours = Behs} = St, Beh) -> + St#cl_state{unknown_behaviours = Beh ++ Behs}. + -spec error(string()) -> no_return(). error(Msg) -> @@ -602,6 +613,7 @@ return_value(State = #cl_state{erlang_mode = ErlangMode, false -> print_warnings(State), print_ext_calls(State), + print_unknown_behaviours(State), maybe_close_output_file(State), {RetValue, []}; true -> @@ -637,6 +649,40 @@ do_print_ext_calls(Output, [{M,F,A}|T], Before) -> do_print_ext_calls(_, [], _) -> ok. +%%print_unknown_behaviours(#cl_state{report_mode = quiet}) -> +%% ok; +print_unknown_behaviours(#cl_state{output = Output, + external_calls = Calls, + stored_warnings = Warnings, + unknown_behaviours = DupBehaviours, + legal_warnings = LegalWarnings, + output_format = Format}) -> + case ordsets:is_element(?WARN_BEHAVIOUR, LegalWarnings) + andalso DupBehaviours =/= [] of + false -> ok; + true -> + Behaviours = lists:usort(DupBehaviours), + case Warnings =:= [] andalso Calls =:= [] of + true -> io:nl(Output); %% Need to do a newline first + false -> ok + end, + case Format of + formatted -> + io:put_chars(Output, "Unknown behaviours (behaviour_info(callbacks)" + " does not return any specs):\n"), + do_print_unknown_behaviours(Output, Behaviours, " "); + raw -> + io:put_chars(Output, "%% Unknown behaviours:\n"), + do_print_unknown_behaviours(Output, Behaviours, "%% ") + end + end. + +do_print_unknown_behaviours(Output, [B|T], Before) -> + io:format(Output, "~s~p\n", [Before,B]), + do_print_unknown_behaviours(Output, T, Before); +do_print_unknown_behaviours(_, [], _) -> + ok. + print_warnings(#cl_state{stored_warnings = []}) -> ok; print_warnings(#cl_state{output = Output, diff --git a/lib/dialyzer/src/dialyzer_cl_parse.erl b/lib/dialyzer/src/dialyzer_cl_parse.erl index ae466e5c010a..1f0da012d6fe 100644 --- a/lib/dialyzer/src/dialyzer_cl_parse.erl +++ b/lib/dialyzer/src/dialyzer_cl_parse.erl @@ -68,6 +68,11 @@ cl(["-n"|T]) -> cl(["--no_check_plt"|T]) -> put(dialyzer_options_check_plt, false), cl(T); +cl(["-nn"|T]) -> + cl(["--no_native"|T]); +cl(["--no_native"|T]) -> + put(dialyzer_options_native, false), + cl(T); cl(["--plt_info"|T]) -> put(dialyzer_options_analysis_type, plt_info), cl(T); @@ -181,7 +186,7 @@ cl([H|_] = L) -> NewTail = command_line(L), cl(NewTail); false -> - error("Unknown option: "++H) + error("Unknown option: " ++ H) end; cl([]) -> {RetTag, Opts} = @@ -191,7 +196,7 @@ cl([]) -> {plt_info, cl_options()}; false -> case get(dialyzer_options_mode) of - {gui,_} = GUI -> {GUI, common_options()}; + {gui, _} = GUI -> {GUI, common_options()}; cl -> case get(dialyzer_options_analysis_type) =:= plt_check of true -> {check_init, cl_options()}; @@ -311,17 +316,27 @@ help_message() -> S = "Usage: dialyzer [--help] [--version] [--shell] [--quiet] [--verbose] [-pa dir]* [--plt plt] [-Ddefine]* [-I include_dir]* [--output_plt file] [-Wwarn]* [--src] [--gui | --wx] - [-c applications] [-r applications] [-o outfile] + [files_or_dirs] [-r dirs] [--apps applications] [-o outfile] [--build_plt] [--add_to_plt] [--remove_from_plt] - [--check_plt] [--no_check_plt] [--plt_info] [--get_warnings] -Options: - -c applications (or --command-line applications) - Use Dialyzer from the command line (no GUI) to detect defects in the - specified applications (directories or .erl or .beam files) - -r applications - Same as -c only that directories are searched recursively for - subdirectories containing .erl or .beam files (depending on the - type of analysis) + [--check_plt] [--no_check_plt] [--plt_info] [--get_warnings] + [--no_native] +Options: + files_or_dirs (for backwards compatibility also as: -c files_or_dirs) + Use Dialyzer from the command line to detect defects in the + specified files or directories containing .erl or .beam files, + depending on the type of the analysis + -r dirs + Same as the previous but the specified directories are searched + recursively for subdirectories containing .erl or .beam files in + them, depending on the type of analysis + --apps applications + Option typically used when building or modifying a PLT as in: + dialyzer --build_plt --apps erts kernel stdlib mnesia ... + to conveniently refer to library applications corresponding to the + Erlang/OTP installation. However, the option is general and can also + be used during analysis in order to refer to Erlang/OTP applications. + In addition, file or directory names can also be included, as in: + dialyzer --apps inets ssl ./ebin ../other_lib/ebin/my_module.beam -o outfile (or --output outfile) When using Dialyzer from the command line, send the analysis results to the specified \"outfile\" rather than to stdout @@ -389,6 +404,10 @@ Options: by the file name extension. Supported extensions are: raw, dot, and ps. If something else is used as file name extension, default format '.raw' will be used. + --no_native (or -nn) + Bypass the native code compilation of some key files that dialyzer + heuristically performs when dialyzing many files; this avoids the + compilation time but it may result in (much) longer analysis time. --gui Use the gs-based GUI. --wx @@ -432,6 +451,9 @@ warning_options_msg() -> Include warnings for functions that only return by means of an exception. -Wrace_conditions *** Include warnings for possible race conditions. + -Wbehaviours *** + Include warnings about behaviour callbacks which drift from the published + recommended interfaces. -Wunderspecs *** Warn about underspecified functions (those whose -spec is strictly more allowing than the success typing). diff --git a/lib/dialyzer/src/dialyzer_codeserver.erl b/lib/dialyzer/src/dialyzer_codeserver.erl index 624501fc49f0..3b4b66cb6887 100644 --- a/lib/dialyzer/src/dialyzer_codeserver.erl +++ b/lib/dialyzer/src/dialyzer_codeserver.erl @@ -56,83 +56,83 @@ %%-------------------------------------------------------------------- --record(dialyzer_codeserver, {table_pid :: pid(), - exports = sets:new() :: set(), % set(mfa()) - next_core_label = 0 :: label(), - records = dict:new() :: dict(), - temp_records = dict:new() :: dict(), - contracts = dict:new() :: dict(), - temp_contracts = dict:new() :: dict()}). +-record(codeserver, {table_pid :: pid(), + exports = sets:new() :: set(), % set(mfa()) + next_core_label = 0 :: label(), + records = dict:new() :: dict(), + temp_records = dict:new() :: dict(), + contracts = dict:new() :: dict(), + temp_contracts = dict:new() :: dict()}). --opaque codeserver() :: #dialyzer_codeserver{}. +-opaque codeserver() :: #codeserver{}. %%-------------------------------------------------------------------- -spec new() -> codeserver(). new() -> - #dialyzer_codeserver{table_pid = table__new()}. + #codeserver{table_pid = table__new()}. -spec delete(codeserver()) -> 'ok'. -delete(#dialyzer_codeserver{table_pid = TablePid}) -> +delete(#codeserver{table_pid = TablePid}) -> table__delete(TablePid). -spec insert(module(), cerl:c_module(), codeserver()) -> codeserver(). insert(Mod, ModCode, CS) -> - NewTablePid = table__insert(CS#dialyzer_codeserver.table_pid, Mod, ModCode), - CS#dialyzer_codeserver{table_pid = NewTablePid}. + NewTablePid = table__insert(CS#codeserver.table_pid, Mod, ModCode), + CS#codeserver{table_pid = NewTablePid}. -spec insert_exports([mfa()], codeserver()) -> codeserver(). -insert_exports(List, #dialyzer_codeserver{exports = Exports} = CS) -> +insert_exports(List, #codeserver{exports = Exports} = CS) -> Set = sets:from_list(List), NewExports = sets:union(Exports, Set), - CS#dialyzer_codeserver{exports = NewExports}. + CS#codeserver{exports = NewExports}. -spec is_exported(mfa(), codeserver()) -> boolean(). -is_exported(MFA, #dialyzer_codeserver{exports = Exports}) -> +is_exported(MFA, #codeserver{exports = Exports}) -> sets:is_element(MFA, Exports). -spec get_exports(codeserver()) -> set(). % set(mfa()) -get_exports(#dialyzer_codeserver{exports = Exports}) -> +get_exports(#codeserver{exports = Exports}) -> Exports. -spec lookup_mod_code(module(), codeserver()) -> cerl:c_module(). lookup_mod_code(Mod, CS) when is_atom(Mod) -> - table__lookup(CS#dialyzer_codeserver.table_pid, Mod). + table__lookup(CS#codeserver.table_pid, Mod). -spec lookup_mfa_code(mfa(), codeserver()) -> {cerl:c_var(), cerl:c_fun()}. lookup_mfa_code({_M, _F, _A} = MFA, CS) -> - table__lookup(CS#dialyzer_codeserver.table_pid, MFA). + table__lookup(CS#codeserver.table_pid, MFA). -spec get_next_core_label(codeserver()) -> label(). -get_next_core_label(#dialyzer_codeserver{next_core_label = NCL}) -> +get_next_core_label(#codeserver{next_core_label = NCL}) -> NCL. -spec set_next_core_label(label(), codeserver()) -> codeserver(). set_next_core_label(NCL, CS) -> - CS#dialyzer_codeserver{next_core_label = NCL}. + CS#codeserver{next_core_label = NCL}. -spec store_records(module(), dict(), codeserver()) -> codeserver(). -store_records(Mod, Dict, #dialyzer_codeserver{records = RecDict} = CS) +store_records(Mod, Dict, #codeserver{records = RecDict} = CS) when is_atom(Mod) -> case dict:size(Dict) =:= 0 of true -> CS; - false -> CS#dialyzer_codeserver{records = dict:store(Mod, Dict, RecDict)} + false -> CS#codeserver{records = dict:store(Mod, Dict, RecDict)} end. -spec lookup_mod_records(module(), codeserver()) -> dict(). -lookup_mod_records(Mod, #dialyzer_codeserver{records = RecDict}) +lookup_mod_records(Mod, #codeserver{records = RecDict}) when is_atom(Mod) -> case dict:find(Mod, RecDict) of error -> dict:new(); @@ -141,45 +141,44 @@ lookup_mod_records(Mod, #dialyzer_codeserver{records = RecDict}) -spec get_records(codeserver()) -> dict(). -get_records(#dialyzer_codeserver{records = RecDict}) -> +get_records(#codeserver{records = RecDict}) -> RecDict. -spec store_temp_records(module(), dict(), codeserver()) -> codeserver(). -store_temp_records(Mod, Dict, #dialyzer_codeserver{temp_records = TempRecDict} = CS) +store_temp_records(Mod, Dict, #codeserver{temp_records = TempRecDict} = CS) when is_atom(Mod) -> case dict:size(Dict) =:= 0 of true -> CS; - false -> CS#dialyzer_codeserver{temp_records = dict:store(Mod, Dict, TempRecDict)} + false -> CS#codeserver{temp_records = dict:store(Mod, Dict, TempRecDict)} end. -spec get_temp_records(codeserver()) -> dict(). -get_temp_records(#dialyzer_codeserver{temp_records = TempRecDict}) -> +get_temp_records(#codeserver{temp_records = TempRecDict}) -> TempRecDict. -spec set_temp_records(dict(), codeserver()) -> codeserver(). set_temp_records(Dict, CS) -> - CS#dialyzer_codeserver{temp_records = Dict}. + CS#codeserver{temp_records = Dict}. -spec finalize_records(dict(), codeserver()) -> codeserver(). finalize_records(Dict, CS) -> - CS#dialyzer_codeserver{records = Dict, temp_records = dict:new()}. + CS#codeserver{records = Dict, temp_records = dict:new()}. -spec store_contracts(module(), dict(), codeserver()) -> codeserver(). -store_contracts(Mod, Dict, #dialyzer_codeserver{contracts = C} = CS) - when is_atom(Mod) -> +store_contracts(Mod, Dict, #codeserver{contracts = C} = CS) when is_atom(Mod) -> case dict:size(Dict) =:= 0 of true -> CS; - false -> CS#dialyzer_codeserver{contracts = dict:store(Mod, Dict, C)} + false -> CS#codeserver{contracts = dict:store(Mod, Dict, C)} end. -spec lookup_mod_contracts(module(), codeserver()) -> dict(). -lookup_mod_contracts(Mod, #dialyzer_codeserver{contracts = ContDict}) +lookup_mod_contracts(Mod, #codeserver{contracts = ContDict}) when is_atom(Mod) -> case dict:find(Mod, ContDict) of error -> dict:new(); @@ -189,7 +188,7 @@ lookup_mod_contracts(Mod, #dialyzer_codeserver{contracts = ContDict}) -spec lookup_mfa_contract(mfa(), codeserver()) -> 'error' | {'ok', dialyzer_contracts:file_contract()}. -lookup_mfa_contract({M,_F,_A} = MFA, #dialyzer_codeserver{contracts = ContDict}) -> +lookup_mfa_contract({M,_F,_A} = MFA, #codeserver{contracts = ContDict}) -> case dict:find(M, ContDict) of error -> error; {ok, Dict} -> dict:find(MFA, Dict) @@ -197,27 +196,27 @@ lookup_mfa_contract({M,_F,_A} = MFA, #dialyzer_codeserver{contracts = ContDict}) -spec get_contracts(codeserver()) -> dict(). -get_contracts(#dialyzer_codeserver{contracts = ContDict}) -> +get_contracts(#codeserver{contracts = ContDict}) -> ContDict. -spec store_temp_contracts(module(), dict(), codeserver()) -> codeserver(). -store_temp_contracts(Mod, Dict, #dialyzer_codeserver{temp_contracts = C} = CS) +store_temp_contracts(Mod, Dict, #codeserver{temp_contracts = C} = CS) when is_atom(Mod) -> case dict:size(Dict) =:= 0 of true -> CS; - false -> CS#dialyzer_codeserver{temp_contracts = dict:store(Mod, Dict, C)} + false -> CS#codeserver{temp_contracts = dict:store(Mod, Dict, C)} end. -spec get_temp_contracts(codeserver()) -> dict(). -get_temp_contracts(#dialyzer_codeserver{temp_contracts = TempContDict}) -> +get_temp_contracts(#codeserver{temp_contracts = TempContDict}) -> TempContDict. -spec finalize_contracts(dict(), codeserver()) -> codeserver(). finalize_contracts(Dict, CS) -> - CS#dialyzer_codeserver{contracts = Dict, temp_contracts = dict:new()}. + CS#codeserver{contracts = Dict, temp_contracts = dict:new()}. table__new() -> spawn_link(fun() -> table__loop(none, dict:new()) end). diff --git a/lib/dialyzer/src/dialyzer_contracts.erl b/lib/dialyzer/src/dialyzer_contracts.erl index e2680bb03dd6..bad0a8d8cd00 100644 --- a/lib/dialyzer/src/dialyzer_contracts.erl +++ b/lib/dialyzer/src/dialyzer_contracts.erl @@ -196,9 +196,13 @@ check_contract(#contract{contracts = Contracts}, SuccType) -> ok -> InfList = [erl_types:t_inf(Contract, SuccType, opaque) || Contract <- Contracts2], - check_contract_inf_list(InfList, SuccType) + case check_contract_inf_list(InfList, SuccType) of + {error, _} = Invalid -> Invalid; + ok -> check_extraneous(Contracts2, SuccType) + end end - catch throw:{error, _} = Error -> Error + catch + throw:{error, _} = Error -> Error end. check_domains([_]) -> ok; @@ -233,6 +237,22 @@ check_contract_inf_list([FunType|Left], SuccType) -> check_contract_inf_list([], _SuccType) -> {error, invalid_contract}. +check_extraneous([], _SuccType) -> ok; +check_extraneous([C|Cs], SuccType) -> + case check_extraneous_1(C, SuccType) of + ok -> check_extraneous(Cs, SuccType); + Error -> Error + end. + +check_extraneous_1(Contract, SuccType) -> + CRngs = erl_types:t_elements(erl_types:t_fun_range(Contract)), + STRng = erl_types:t_fun_range(SuccType), + %% io:format("CR = ~p\nSR = ~p\n", [CRngs, STRng]), + case [CR || CR <- CRngs, erl_types:t_is_none(erl_types:t_inf(CR, STRng, opaque))] of + [] -> ok; + CRs -> {error, {extra_range, erl_types:t_sup(CRs), STRng}} + end. + %% This is the heart of the "range function" -spec process_contracts([contract_pair()], [erl_types:erl_type()]) -> erl_types:erl_type(). @@ -411,6 +431,8 @@ get_invalid_contract_warnings_funs([{MFA, {FileLine, Contract}}|Left], case check_contract(Contract, Sig) of {error, invalid_contract} -> [invalid_contract_warning(MFA, FileLine, Sig, RecDict)|Acc]; + {error, {extra_range, ExtraRanges, STRange}} -> + [extra_range_warning(MFA, FileLine, ExtraRanges, STRange)|Acc]; {error, Msg} -> [{?WARN_CONTRACT_SYNTAX, FileLine, Msg}|Acc]; ok -> @@ -442,9 +464,15 @@ get_invalid_contract_warnings_funs([{MFA, {FileLine, Contract}}|Left], get_invalid_contract_warnings_funs([], _Plt, _RecDict, Acc) -> Acc. -invalid_contract_warning({M, F, A}, FileLine, Type, RecDict) -> +invalid_contract_warning({M, F, A}, FileLine, SuccType, RecDict) -> + SuccTypeStr = dialyzer_utils:format_sig(SuccType, RecDict), + {?WARN_CONTRACT_TYPES, FileLine, {invalid_contract, [M, F, A, SuccTypeStr]}}. + +extra_range_warning({M, F, A}, FileLine, ExtraRanges, STRange) -> + ERangesStr = erl_types:t_to_string(ExtraRanges), + STRangeStr = erl_types:t_to_string(STRange), {?WARN_CONTRACT_TYPES, FileLine, - {invalid_contract, [M, F, A, dialyzer_utils:format_sig(Type, RecDict)]}}. + {extra_range, [M, F, A, ERangesStr, STRangeStr]}}. picky_contract_check(CSig0, Sig0, MFA, FileLine, Contract, RecDict, Acc) -> CSig = erl_types:t_abstract_records(CSig0, RecDict), diff --git a/lib/dialyzer/src/dialyzer_dataflow.erl b/lib/dialyzer/src/dialyzer_dataflow.erl index 178321ea18c5..7fb309497ad2 100644 --- a/lib/dialyzer/src/dialyzer_dataflow.erl +++ b/lib/dialyzer/src/dialyzer_dataflow.erl @@ -47,7 +47,7 @@ t_cons/0, t_cons/2, t_cons_hd/1, t_cons_tl/1, t_contains_opaque/1, t_find_opaque_mismatch/2, t_float/0, t_from_range/2, t_from_term/1, t_fun/0, t_fun/2, t_fun_args/1, t_fun_range/1, - t_inf/2, t_inf/3, t_inf_lists/2, t_inf_lists/3, + t_inf/2, t_inf/3, t_inf_lists/2, t_inf_lists/3, t_inf_lists_masked/3, t_integer/0, t_integers/1, t_is_any/1, t_is_atom/1, t_is_atom/2, t_is_boolean/1, t_is_equal/2, t_is_integer/1, t_is_nil/1, t_is_none/1, t_is_none_or_unit/1, @@ -93,11 +93,13 @@ tree_map :: dict(), warning_mode = false :: boolean(), warnings = [] :: [dial_warning()], - work :: {[_], [_], set()}}). + work :: {[_], [_], set()}, + module :: module(), + behaviour_api_info = [] :: [{atom(),[_]}]}). %% Exported Types --type state() :: #state{}. +-opaque state() :: #state{}. %%-------------------------------------------------------------------- @@ -263,10 +265,15 @@ analyze_module(Tree, Plt, Callgraph) -> analyze_module(Tree, Plt, Callgraph, Records, GetWarnings) -> debug_pp(Tree, false), Module = cerl:atom_val(cerl:module_name(Tree)), + RaceDetection = dialyzer_callgraph:get_race_detection(Callgraph), + BehaviourTranslations = + case RaceDetection of + true -> dialyzer_behaviours:translatable_behaviours(Tree); + false -> [] + end, TopFun = cerl:ann_c_fun([{label, top}], [], Tree), - State = - state__new(dialyzer_callgraph:race_code_new(Callgraph), - TopFun, Plt, Module, Records), + State = state__new(dialyzer_callgraph:race_code_new(Callgraph), + TopFun, Plt, Module, Records, BehaviourTranslations), State1 = state__race_analysis(not GetWarnings, State), State2 = analyze_loop(State1), RaceCode = dialyzer_callgraph:get_race_code(Callgraph), @@ -277,7 +284,18 @@ analyze_module(Tree, Plt, Callgraph, Records, GetWarnings) -> State3 = state__set_warning_mode(State2), State4 = analyze_loop(State3), State5 = state__restore_race_code(RaceCode, State4), - dialyzer_races:race(State5); + + %% EXPERIMENTAL: Turn all behaviour API calls into calls to the + %% respective callback module's functions. + + case BehaviourTranslations of + [] -> dialyzer_races:race(State5); + Behaviours -> + TranslatedCallgraph = + dialyzer_behaviours:translate_callgraph(Behaviours, Module, + State5#state.callgraph), + dialyzer_races:race(State5#state{callgraph = TranslatedCallgraph}) + end; false -> state__restore_race_code( dict:merge(fun (_K, V1, _V2) -> V1 end, @@ -567,6 +585,7 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], {M, F, A} = Fun, case erl_bif_types:is_known(M, F, A) of true -> + IsBIF = true, BArgs = erl_bif_types:arg_types(M, F, A), BRange = fun(FunArgs) -> @@ -585,9 +604,9 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], erl_bif_types:type(M, F, A, NewFunArgs) end, {BArgs, BRange}; - false -> GenSig + false -> IsBIF = false, GenSig end; - local -> GenSig + local -> IsBIF = false, GenSig end, {SigArgs, SigRange} = %% if there is hard-coded or contract information with opaque types, @@ -601,18 +620,33 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], none -> {AnyArgs, t_any()} end end, - NewArgsSig = t_inf_lists(SigArgs, ArgTypes), - NewArgsContract = t_inf_lists(CArgs, ArgTypes), - NewArgsBif = t_inf_lists(BifArgs, ArgTypes), - NewArgTypes0 = t_inf_lists(NewArgsSig, NewArgsContract), - NewArgTypes = t_inf_lists(NewArgTypes0, NewArgsBif), + ArgModeMask = [case lists:member(Arg, Opaques) of + true -> opaque; + false -> structured + end || Arg <- ArgTypes], + NewArgsSig = t_inf_lists_masked(SigArgs, ArgTypes, ArgModeMask), + NewArgsContract = t_inf_lists_masked(CArgs, ArgTypes, ArgModeMask), + NewArgsBif = t_inf_lists_masked(BifArgs, ArgTypes, ArgModeMask), + NewArgTypes0 = t_inf_lists_masked(NewArgsSig, NewArgsContract, ArgModeMask), + NewArgTypes = t_inf_lists_masked(NewArgTypes0, NewArgsBif, ArgModeMask), BifRet = BifRange(NewArgTypes), - ContrRet = CRange(NewArgTypes), - Mode = case t_contains_opaque(ContrRet) orelse t_contains_opaque(BifRet) of + {TmpArgTypes, TmpArgsContract} = + case (TypeOfApply == remote) andalso (not IsBIF) of + true -> + List1 = lists:zip(CArgs, NewArgTypes), + List2 = lists:zip(CArgs, NewArgsContract), + {[erl_types:t_unopaque_on_mismatch(T1, T2, Opaques) + || {T1, T2} <- List1], + [erl_types:t_unopaque_on_mismatch(T1, T2, Opaques) + || {T1, T2} <- List2]}; + false -> {NewArgTypes, NewArgsContract} + end, + ContrRet = CRange(TmpArgTypes), + RetMode = case t_contains_opaque(ContrRet) orelse t_contains_opaque(BifRet) of true -> opaque; false -> structured end, - RetWithoutLocal = t_inf(t_inf(ContrRet, BifRet, Mode), SigRange, Mode), + RetWithoutLocal = t_inf(t_inf(ContrRet, BifRet, RetMode), SigRange, RetMode), ?debug("--------------------------------------------------------\n", []), ?debug("Fun: ~p\n", [Fun]), ?debug("Args: ~s\n", [erl_types:t_to_string(t_product(ArgTypes))]), @@ -623,7 +657,7 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], ?debug("NewArgTypes: ~s\n", [erl_types:t_to_string(t_product(NewArgTypes))]), ?debug("RetWithoutLocal: ~s\n", [erl_types:t_to_string(RetWithoutLocal)]), ?debug("BifRet: ~s\n", [erl_types:t_to_string(BifRange(NewArgTypes))]), - ?debug("ContrRet: ~s\n", [erl_types:t_to_string(CRange(NewArgTypes))]), + ?debug("ContrRet: ~s\n", [erl_types:t_to_string(CRange(TmpArgTypes))]), ?debug("SigRet: ~s\n", [erl_types:t_to_string(SigRange)]), State1 = case dialyzer_callgraph:get_race_detection(Callgraph) andalso @@ -632,8 +666,21 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], Ann = cerl:get_ann(Tree), File = get_file(Ann), Line = abs(get_line(Ann)), - dialyzer_races:store_race_call(Fun, ArgTypes, Args, {File, Line}, - State); + + %% EXPERIMENTAL: Turn a behaviour's API call into a call to the + %% respective callback module's function. + + Module = State#state.module, + BehApiInfo = State#state.behaviour_api_info, + {RealFun, RealArgTypes, RealArgs} = + case dialyzer_behaviours:translate_behaviour_api_call(Fun, ArgTypes, + Args, Module, + BehApiInfo) of + plain_call -> {Fun, ArgTypes, Args}; + BehaviourAPI -> BehaviourAPI + end, + dialyzer_races:store_race_call(RealFun, RealArgTypes, RealArgs, + {File, Line}, State); false -> State end, FailedConj = any_none([RetWithoutLocal|NewArgTypes]), @@ -643,7 +690,7 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], case FailedConj andalso not (IsFailBif orelse IsFailSig) of true -> FailedSig = any_none(NewArgsSig), - FailedContract = any_none([CRange(NewArgsContract)|NewArgsContract]), + FailedContract = any_none([CRange(TmpArgsContract)|NewArgsContract]), FailedBif = any_none([BifRange(NewArgsBif)|NewArgsBif]), InfSig = t_inf(t_fun(SigArgs, SigRange), t_fun(BifArgs, BifRange(BifArgs))), @@ -786,8 +833,11 @@ expected_arg_triples(ArgNs, ArgTypes, State) -> add_bif_warnings({erlang, Op, 2}, [T1, T2] = Ts, Tree, State) when Op =:= '=:='; Op =:= '==' -> + Type1 = erl_types:t_unopaque(T1, State#state.opaques), + Type2 = erl_types:t_unopaque(T2, State#state.opaques), Inf = t_inf(T1, T2), - case t_is_none(Inf) andalso (not any_none(Ts)) + Inf1 = t_inf(Type1, Type2), + case t_is_none(Inf) andalso t_is_none(Inf1) andalso(not any_none(Ts)) andalso (not is_int_float_eq_comp(T1, Op, T2)) of true -> Args = case erl_types:t_is_opaque(T1) of @@ -905,9 +955,9 @@ handle_call(Tree, Map, State) -> Args = cerl:call_args(Tree), MFAList = [M, F|Args], {State1, Map1, [MType0, FType0|As]} = traverse_list(MFAList, Map, State), - %% Module and function names should be treated as *atoms* even if - %% they happen to be identical to an atom which is also involved in - %% the definition of an opaque data type + %% Module and function names should be treated as *structured terms* + %% even if they happen to be identical to an atom (or tuple) which + %% is also involved in the definition of an opaque data type. MType = t_inf(t_module(), t_unopaque(MType0)), FType = t_inf(t_atom(), t_unopaque(FType0)), Map2 = enter_type_lists([M, F], [MType, FType], Map1), @@ -936,13 +986,18 @@ handle_call(Tree, Map, State) -> end, {State2, Map2, t_none()}; false -> - %% XXX: Consider doing this for all combinations of MF - case {t_atom_vals(MType), t_atom_vals(FType)} of - {[MAtom], [FAtom]} -> - FunInfo = [{remote, state__fun_info({MAtom, FAtom, length(Args)}, - State1)}], - handle_apply_or_call(FunInfo, Args, As, Map2, Tree, State1); - {_MAtoms, _FAtoms} -> + case t_is_atom(MType) of + true -> + %% XXX: Consider doing this for all combinations of MF + case {t_atom_vals(MType), t_atom_vals(FType)} of + {[MAtom], [FAtom]} -> + FunInfo = [{remote, state__fun_info({MAtom, FAtom, length(Args)}, + State1)}], + handle_apply_or_call(FunInfo, Args, As, Map2, Tree, State1); + {_MAtoms, _FAtoms} -> + {State1, Map2, t_any()} + end; + false -> {State1, Map2, t_any()} end end. @@ -1481,10 +1536,7 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) -> Cons = t_inf(Type, t_cons()), case t_is_none(Cons) of true -> - case t_find_opaque_mismatch(t_cons(), Type) of - {ok, T1, T2} -> bind_error([Pat], T1, T2, opaque); - error -> bind_error([Pat], Type, t_none(), bind) - end; + bind_opaque_pats(t_cons(), Type, Pat, Map, State, Rev); false -> {Map1, [HdType, TlType]} = bind_pat_vars([cerl:cons_hd(Pat), cerl:cons_tl(Pat)], @@ -1501,18 +1553,7 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) -> end, case t_is_none(t_inf(LiteralOrOpaque, Type)) of true -> - case t_find_opaque_mismatch(Literal, Type) of - {ok, T1, T2} -> - case lists:member(T2, State#state.opaques) of - true -> - NewType = erl_types:t_struct_from_opaque(Type, T2), - {Map1, _} = - bind_pat_vars([Pat], [NewType], [], Map, State, Rev), - {Map1, T2}; - false -> bind_error([Pat], T1, T2, opaque) - end; - error -> bind_error([Pat], Type, t_none(), bind) - end; + bind_opaque_pats(Literal, Type, Pat, Map, State, Rev); false -> {Map, LiteralOrOpaque} end; tuple -> @@ -1534,18 +1575,7 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) -> Tuple = t_inf(Prototype, Type), case t_is_none(Tuple) of true -> - case t_find_opaque_mismatch(Prototype, Type) of - {ok, T1, T2} -> - case lists:member(T2, State#state.opaques) of - true -> - NewType = erl_types:t_struct_from_opaque(Type, T2), - {Map1, _} = - bind_pat_vars([Pat], [NewType], [], Map, State, Rev), - {Map1, T2}; - false -> bind_error([Pat], T1, T2, opaque) - end; - error -> bind_error([Pat], Type, t_none(), bind) - end; + bind_opaque_pats(Prototype, Type, Pat, Map, State, Rev); false -> SubTuples = t_tuple_subtypes(Tuple), %% Need to call the top function to get the try-catch wrapper @@ -1689,6 +1719,20 @@ bind_bin_segs([], _BinType, Acc, Map, _State) -> bind_error(Pats, Type, OpaqueType, Error) -> throw({error, Error, Pats, Type, OpaqueType}). +bind_opaque_pats(GenType, Type, Pat, Map, State, Rev) -> + case t_find_opaque_mismatch(GenType, Type) of + {ok, T1, T2} -> + case lists:member(T2, State#state.opaques) of + true -> + NewType = erl_types:t_struct_from_opaque(Type, [T2]), + {Map1, _} = + bind_pat_vars([Pat], [NewType], [], Map, State, Rev), + {Map1, T2}; + false -> bind_error([Pat], T1, T2, opaque) + end; + error -> bind_error([Pat], Type, t_none(), bind) + end. + %%---------------------------------------- %% Guards %% @@ -2296,7 +2340,7 @@ bind_guard_list([G|Gs], Map, Env, Eval, State, Acc) -> bind_guard_list([], Map, _Env, _Eval, _State, Acc) -> {Map, lists:reverse(Acc)}. --spec signal_guard_fail(cerl:c_call(), [erl_types:erl_type()], #state{}) -> +-spec signal_guard_fail(cerl:c_call(), [erl_types:erl_type()], state()) -> no_return(). signal_guard_fail(Guard, ArgTypes, State) -> @@ -2327,7 +2371,7 @@ is_infix_op({erlang, '>=', 2}) -> true; is_infix_op({M, F, A}) when is_atom(M), is_atom(F), is_integer(A), 0 =< A, A =< 255 -> false. --spec signal_guard_fatal_fail(cerl:c_call(), [erl_types:erl_type()], #state{}) -> +-spec signal_guard_fatal_fail(cerl:c_call(), [erl_types:erl_type()], state()) -> no_return(). signal_guard_fatal_fail(Guard, ArgTypes, State) -> @@ -2680,7 +2724,7 @@ determine_mode(Type, Opaques) -> %%% %%% =========================================================================== -state__new(Callgraph, Tree, Plt, Module, Records) -> +state__new(Callgraph, Tree, Plt, Module, Records, BehaviourTranslations) -> TreeMap = build_tree_map(Tree), Funs = dict:fetch_keys(TreeMap), FunTab = init_fun_tab(Funs, dict:new(), TreeMap, Callgraph, Plt), @@ -2690,7 +2734,8 @@ state__new(Callgraph, Tree, Plt, Module, Records) -> erl_types:t_opaque_from_records(Records), #state{callgraph = Callgraph, envs = Env, fun_tab = FunTab, opaques = Opaques, plt = Plt, races = dialyzer_races:new(), records = Records, - warning_mode = false, warnings = [], work = Work, tree_map = TreeMap}. + warning_mode = false, warnings = [], work = Work, tree_map = TreeMap, + module = Module, behaviour_api_info = BehaviourTranslations}. state__mark_fun_as_handled(#state{fun_tab = FunTab} = State, Fun0) -> Fun = get_label(Fun0), @@ -3197,7 +3242,7 @@ get_file([_|Tail]) -> get_file(Tail). is_compiler_generated(Ann) -> lists:member(compiler_generated, Ann) orelse (get_line(Ann) < 1). --spec format_args([term()], [erl_types:erl_type()], #state{}) -> +-spec format_args([term()], [erl_types:erl_type()], state()) -> nonempty_string(). format_args([], [], _State) -> @@ -3205,7 +3250,7 @@ format_args([], [], _State) -> format_args(ArgList, TypeList, State) -> "(" ++ format_args_1(ArgList, TypeList, State) ++ ")". --spec format_args_1([term(),...], [erl_types:erl_type(),...], #state{}) -> +-spec format_args_1([term(),...], [erl_types:erl_type(),...], state()) -> string(). format_args_1([Arg], [Type], State) -> @@ -3235,12 +3280,12 @@ format_arg(Arg) -> Default end. --spec format_type(erl_types:erl_type(), #state{}) -> string(). +-spec format_type(erl_types:erl_type(), state()) -> string(). format_type(Type, #state{records = R}) -> t_to_string(Type, R). --spec format_sig_args(erl_types:erl_type(), #state{}) -> string(). +-spec format_sig_args(erl_types:erl_type(), state()) -> string(). format_sig_args(Type, #state{records = R}) -> SigArgs = t_fun_args(Type), diff --git a/lib/dialyzer/src/dialyzer_dep.erl b/lib/dialyzer/src/dialyzer_dep.erl index 670433f003d5..7d5637673084 100644 --- a/lib/dialyzer/src/dialyzer_dep.erl +++ b/lib/dialyzer/src/dialyzer_dep.erl @@ -326,7 +326,7 @@ set__filter(#set{set = Set}, Fun) -> %% -record(output, {type :: 'single' | 'list', - content :: 'none' | #set{} | [{output,_,_}]}). + content :: 'none' | #set{} | [#output{}]}). output(none) -> #output{type = single, content = none}; output(S = #set{}) -> #output{type = single, content = S}; diff --git a/lib/dialyzer/src/dialyzer_gui_wx.erl b/lib/dialyzer/src/dialyzer_gui_wx.erl index 2d97f88680b6..5b807804e2c9 100644 --- a/lib/dialyzer/src/dialyzer_gui_wx.erl +++ b/lib/dialyzer/src/dialyzer_gui_wx.erl @@ -102,9 +102,9 @@ create_window(Wx, DialyzerOptions) -> MenuBar = wxMenuBar:new(), wxMenuBar:append(MenuBar, FileMenu, "File"), - wxMenuBar:append(MenuBar, WarningsMenu, "Warnings"), - wxMenuBar:append(MenuBar, PltMenu, "Plt"), - wxMenuBar:append(MenuBar, OptionsMenu, "Options"), + wxMenuBar:append(MenuBar, WarningsMenu, "Warnings"), + wxMenuBar:append(MenuBar, PltMenu, "Plt"), + wxMenuBar:append(MenuBar, OptionsMenu, "Options"), wxMenuBar:append(MenuBar, HelpMenu, "Help"), wxFrame:setMenuBar(Frame, MenuBar), ok = wxFrame:connect(Frame, command_menu_selected), @@ -152,8 +152,8 @@ create_window(Wx, DialyzerOptions) -> AddButton = wxButton:new(Frame, ?Add_Button, [{label, "Add"}]), AddDirButton = wxButton:new(Frame, ?AddDir_Button, [{label, "Add Dir"}]), AddRecButton = wxButton:new(Frame, ?AddRec_Button, [{label, "Add Recursively"}]), - ExplainWarnButton = wxButton:new(Frame, ?ExplWarn_Button, [{label, "Explain Warning"}]), - ClearWarningsButton = wxButton:new(Frame, ?ClearWarn_Button, [{label, "Clear Warnings"}]), + ExplainWarnButton = wxButton:new(Frame, ?ExplWarn_Button, [{label, "Explain Warning"}]), + ClearWarningsButton = wxButton:new(Frame, ?ClearWarn_Button, [{label, "Clear Warnings"}]), RunButton = wxButton:new(Frame, ?Run_Button, [{label, "Run"}]), StopButton = wxButton:new(Frame, ?Stop_Button, [{label, "Stop"}]), wxWindow:disable(StopButton), @@ -170,8 +170,8 @@ create_window(Wx, DialyzerOptions) -> wxButton:connect(StopButton, command_button_clicked), %%------------Set Layout ------------ - All = wxBoxSizer:new(?wxVERTICAL), - Top = wxBoxSizer:new(?wxHORIZONTAL), + All = wxBoxSizer:new(?wxVERTICAL), + Top = wxBoxSizer:new(?wxHORIZONTAL), Left = wxBoxSizer:new(?wxVERTICAL), Right = wxBoxSizer:new(?wxVERTICAL), RightUp = wxBoxSizer:new(?wxHORIZONTAL), @@ -390,7 +390,7 @@ gui_loop(#gui_state{backend_pid = BackendPid, doc_plt = DocPlt, warnings_box = WarningsBox} = State) -> receive #wx{event = #wxClose{}} -> - io:format("~p Closing window ~n", [self()]), + %% io:format("~p Closing window ~n", [self()]), ok = wxFrame:setStatusText(Frame, "Closing...",[]), wxWindow:destroy(Frame), ?RET_NOTHING_SUSPICIOUS; @@ -539,7 +539,7 @@ maybe_quit(#gui_state{frame = Frame} = State) -> %% ------------ Yes/No Question ------------ dialog(#gui_state{frame = Frame}, Message, Title) -> - MessageWin = wxMessageDialog:new(Frame,Message,[{caption, Title},{style, ?wxYES_NO bor ?wxICON_QUESTION bor ?wxNO_DEFAULT}]), + MessageWin = wxMessageDialog:new(Frame, Message, [{caption, Title},{style, ?wxYES_NO bor ?wxICON_QUESTION bor ?wxNO_DEFAULT}]), case wxDialog:showModal(MessageWin) of ?wxID_YES -> true; @@ -563,12 +563,12 @@ search_doc_plt(#gui_state{gui = Wx} = State) -> Cancel = wxButton:new(Dialog, ?Search_Cancel, [{label, "Cancel"}]), wxButton:connect(Cancel, command_button_clicked), - Layout = wxBoxSizer:new(?wxVERTICAL), - Top = wxBoxSizer:new(?wxHORIZONTAL), - ModLayout = wxBoxSizer:new(?wxVERTICAL), - FunLayout = wxBoxSizer:new(?wxVERTICAL), - ArLayout = wxBoxSizer:new(?wxVERTICAL), - Buttons = wxBoxSizer:new(?wxHORIZONTAL), + Layout = wxBoxSizer:new(?wxVERTICAL), + Top = wxBoxSizer:new(?wxHORIZONTAL), + ModLayout = wxBoxSizer:new(?wxVERTICAL), + FunLayout = wxBoxSizer:new(?wxVERTICAL), + ArLayout = wxBoxSizer:new(?wxVERTICAL), + Buttons = wxBoxSizer:new(?wxHORIZONTAL), wxSizer:add(ModLayout, ModLabel, ?BorderOpt), wxSizer:add(ModLayout,ModText, ?BorderOpt), @@ -606,7 +606,7 @@ search_plt_loop(State= #gui_state{doc_plt = DocPlt, frame = Frame}, Win, ModText A = format_search(wxTextCtrl:getValue(ArText)), if - (M == '_') or (F == '_') or (A == '_') -> + (M =:= '_') orelse (F =:= '_') orelse (A =:= '_') -> error_sms(State, "Please give:\n Module (atom)\n Function (atom)\n Arity (integer)\n"), search_plt_loop(State, Win, ModText, FunText, ArText, Search, Cancel); true -> @@ -670,7 +670,7 @@ free_editor(#gui_state{gui = Wx, frame = Frame}, Title, Contents0) -> wxFrame:connect(Win, close_window), Ok = wxButton:new(Win, ?Message_Ok, [{label, "OK"}]), wxButton:connect(Ok, command_button_clicked), - Layout = wxBoxSizer:new(?wxVERTICAL), + Layout = wxBoxSizer:new(?wxVERTICAL), wxSizer:add(Layout, Editor, ?BorderOpt), wxSizer:add(Layout, Ok, [{flag, ?wxALIGN_CENTER bor ?wxBOTTOM bor ?wxALL}, ?Border]), @@ -757,7 +757,7 @@ add_files(File, FileList, ChosenBox, Ext) -> Files. filter_mods(Mods, Extension) -> - Fun = fun(X) -> + Fun = fun(X) -> filename:extension(X) =:= Extension orelse (filelib:is_dir(X) andalso @@ -944,9 +944,9 @@ include_dialog(#gui_state{gui = Wx, frame = Frame, options = Options}) -> Dirs = [io_lib:format("~s", [X]) || X <- Options#options.include_dirs], wxListBox:set(Box, Dirs), - Layout = wxBoxSizer:new(?wxVERTICAL), - Buttons = wxBoxSizer:new(?wxHORIZONTAL), - Buttons1 = wxBoxSizer:new(?wxHORIZONTAL), + Layout = wxBoxSizer:new(?wxVERTICAL), + Buttons = wxBoxSizer:new(?wxHORIZONTAL), + Buttons1 = wxBoxSizer:new(?wxHORIZONTAL), wxSizer:add(Layout, DirLabel, [{flag, ?wxALIGN_CENTER_HORIZONTAL}]), wxSizer:add(Layout, DirPicker, [{flag, ?wxALIGN_CENTER_HORIZONTAL}]), @@ -1038,12 +1038,12 @@ macro_dialog(#gui_state{gui = Wx, frame = Frame, options = Options}) -> || {X,Y} <- Options#options.defines], wxListBox:set(Box, Macros), - Layout = wxBoxSizer:new(?wxVERTICAL), - Item = wxBoxSizer:new(?wxHORIZONTAL), - MacroItem = wxBoxSizer:new(?wxVERTICAL), - TermItem = wxBoxSizer:new(?wxVERTICAL), - Buttons = wxBoxSizer:new(?wxHORIZONTAL), - Buttons1 = wxBoxSizer:new(?wxHORIZONTAL), + Layout = wxBoxSizer:new(?wxVERTICAL), + Item = wxBoxSizer:new(?wxHORIZONTAL), + MacroItem = wxBoxSizer:new(?wxVERTICAL), + TermItem = wxBoxSizer:new(?wxVERTICAL), + Buttons = wxBoxSizer:new(?wxHORIZONTAL), + Buttons1 = wxBoxSizer:new(?wxHORIZONTAL), wxSizer:add(MacroItem, MacroLabel, ?BorderOpt), wxSizer:add(MacroItem, MacroText, ?BorderOpt), @@ -1159,7 +1159,8 @@ handle_explanation(#gui_state{rawWarnings = RawWarns, warnings_box = WarnBox, expl_pid = ExplPid} = State) -> case wxListBox:isEmpty(WarnBox) of - true -> error_sms(State, "\nThere are no warnings.\nRun the dialyzer first."); + true -> + error_sms(State, "\nThere are no warnings.\nRun the dialyzer first."); false -> case wxListBox:getSelections(WarnBox)of {0, []} -> @@ -1200,8 +1201,8 @@ show_explanation(#gui_state{gui = Wx} = State, Explanation) -> wxButton:connect(ExplButton, command_button_clicked), Ok = wxButton:new(Win, ?ExplOk, [{label, "OK"}]), wxButton:connect(Ok, command_button_clicked), - Layout = wxBoxSizer:new(?wxVERTICAL), - Buttons = wxBoxSizer:new(?wxHORIZONTAL), + Layout = wxBoxSizer:new(?wxVERTICAL), + Buttons = wxBoxSizer:new(?wxHORIZONTAL), wxSizer:add(Buttons, ExplButton, ?BorderOpt), wxSizer:add(Buttons, Ok, ?BorderOpt), wxSizer:add(Layout, Editor,[{flag, ?wxALIGN_CENTER_HORIZONTAL bor ?wxALL}, ?Border]), diff --git a/lib/dialyzer/src/dialyzer_options.erl b/lib/dialyzer/src/dialyzer_options.erl index 6531073072c8..1f587d6df02c 100644 --- a/lib/dialyzer/src/dialyzer_options.erl +++ b/lib/dialyzer/src/dialyzer_options.erl @@ -252,6 +252,8 @@ build_warnings([Opt|Opts], Warnings) -> ordsets:add_element(?WARN_RETURN_ONLY_EXIT, Warnings); race_conditions -> ordsets:add_element(?WARN_RACE_CONDITION, Warnings); + behaviours -> + ordsets:add_element(?WARN_BEHAVIOUR, Warnings); specdiffs -> S = ordsets:from_list([?WARN_CONTRACT_SUBTYPE, ?WARN_CONTRACT_SUPERTYPE, diff --git a/lib/dialyzer/src/dialyzer_plt.erl b/lib/dialyzer/src/dialyzer_plt.erl index f2e0fe1e9742..fed1e74b3378 100644 --- a/lib/dialyzer/src/dialyzer_plt.erl +++ b/lib/dialyzer/src/dialyzer_plt.erl @@ -70,109 +70,106 @@ %%---------------------------------------------------------------------- --record(dialyzer_plt, {info = table_new() :: dict(), - types = table_new() :: dict(), - contracts = table_new() :: dict()}). --opaque plt() :: #dialyzer_plt{}. +-record(plt, {info = table_new() :: dict(), + types = table_new() :: dict(), + contracts = table_new() :: dict()}). +-opaque plt() :: #plt{}. -include("dialyzer.hrl"). -type file_md5() :: {file:filename(), binary()}. -type plt_info() :: {[file_md5()], dict()}. --record(dialyzer_file_plt, {version = "" :: string(), - file_md5_list = [] :: [file_md5()], - info = dict:new() :: dict(), - contracts = dict:new() :: dict(), - types = dict:new() :: dict(), - mod_deps :: mod_deps(), - implementation_md5 = [] :: [file_md5()] - }). +-record(file_plt, {version = "" :: string(), + file_md5_list = [] :: [file_md5()], + info = dict:new() :: dict(), + contracts = dict:new() :: dict(), + types = dict:new() :: dict(), + mod_deps :: mod_deps(), + implementation_md5 = [] :: [file_md5()]}). %%---------------------------------------------------------------------- -spec new() -> plt(). new() -> - #dialyzer_plt{}. + #plt{}. -spec delete_module(plt(), module()) -> plt(). -delete_module(#dialyzer_plt{info = Info, types = Types, contracts = Contracts}, - Mod) -> - #dialyzer_plt{info = table_delete_module(Info, Mod), - types = table_delete_module2(Types, Mod), - contracts = table_delete_module(Contracts, Mod)}. +delete_module(#plt{info = Info, types = Types, contracts = Contracts}, Mod) -> + #plt{info = table_delete_module(Info, Mod), + types = table_delete_module2(Types, Mod), + contracts = table_delete_module(Contracts, Mod)}. -spec delete_list(plt(), [mfa() | integer()]) -> plt(). -delete_list(#dialyzer_plt{info = Info, types = Types, contracts = Contracts}, - List) -> - #dialyzer_plt{info = table_delete_list(Info, List), - types = Types, - contracts = table_delete_list(Contracts, List)}. +delete_list(#plt{info = Info, types = Types, contracts = Contracts}, List) -> + #plt{info = table_delete_list(Info, List), + types = Types, + contracts = table_delete_list(Contracts, List)}. -spec insert_contract_list(plt(), dialyzer_contracts:plt_contracts()) -> plt(). -insert_contract_list(#dialyzer_plt{contracts = Contracts} = PLT, List) -> - PLT#dialyzer_plt{contracts = table_insert_list(Contracts, List)}. +insert_contract_list(#plt{contracts = Contracts} = PLT, List) -> + PLT#plt{contracts = table_insert_list(Contracts, List)}. -spec lookup_contract(plt(), mfa_patt()) -> 'none' | {'value', #contract{}}. -lookup_contract(#dialyzer_plt{contracts = Contracts}, +lookup_contract(#plt{contracts = Contracts}, {M, F, _} = MFA) when is_atom(M), is_atom(F) -> table_lookup(Contracts, MFA). -spec delete_contract_list(plt(), [mfa()]) -> plt(). -delete_contract_list(#dialyzer_plt{contracts = Contracts} = PLT, List) -> - PLT#dialyzer_plt{contracts = table_delete_list(Contracts, List)}. +delete_contract_list(#plt{contracts = Contracts} = PLT, List) -> + PLT#plt{contracts = table_delete_list(Contracts, List)}. %% -spec insert(plt(), mfa() | integer(), {_, _}) -> plt(). %% -%% insert(#dialyzer_plt{info = Info} = PLT, Id, Types) -> -%% PLT#dialyzer_plt{info = table_insert(Info, Id, Types)}. +%% insert(#plt{info = Info} = PLT, Id, Types) -> +%% PLT#plt{info = table_insert(Info, Id, Types)}. -type ret_args_types() :: {erl_types:erl_type(), [erl_types:erl_type()]}. -spec insert_list(plt(), [{mfa() | integer(), ret_args_types()}]) -> plt(). -insert_list(#dialyzer_plt{info = Info} = PLT, List) -> - PLT#dialyzer_plt{info = table_insert_list(Info, List)}. +insert_list(#plt{info = Info} = PLT, List) -> + PLT#plt{info = table_insert_list(Info, List)}. -spec lookup(plt(), integer() | mfa_patt()) -> 'none' | {'value', ret_args_types()}. -lookup(#dialyzer_plt{info = Info}, {M, F, _} = MFA) when is_atom(M), is_atom(F) -> +lookup(#plt{info = Info}, {M, F, _} = MFA) when is_atom(M), is_atom(F) -> table_lookup(Info, MFA); -lookup(#dialyzer_plt{info = Info}, Label) when is_integer(Label) -> +lookup(#plt{info = Info}, Label) when is_integer(Label) -> table_lookup(Info, Label). -spec insert_types(plt(), dict()) -> plt(). insert_types(PLT, Rec) -> - PLT#dialyzer_plt{types = Rec}. + PLT#plt{types = Rec}. -spec get_types(plt()) -> dict(). -get_types(#dialyzer_plt{types = Types}) -> +get_types(#plt{types = Types}) -> Types. -type mfa_types() :: {mfa(), erl_types:erl_type(), [erl_types:erl_type()]}. -spec lookup_module(plt(), module()) -> 'none' | {'value', [mfa_types()]}. -lookup_module(#dialyzer_plt{info = Info}, M) when is_atom(M) -> +lookup_module(#plt{info = Info}, M) when is_atom(M) -> table_lookup_module(Info, M). -spec contains_module(plt(), module()) -> boolean(). -contains_module(#dialyzer_plt{info = Info, contracts = Cs}, M) when is_atom(M) -> +contains_module(#plt{info = Info, contracts = Cs}, M) when is_atom(M) -> table_contains_module(Info, M) orelse table_contains_module(Cs, M). -spec contains_mfa(plt(), mfa()) -> boolean(). -contains_mfa(#dialyzer_plt{info = Info, contracts = Contracts}, MFA) -> +contains_mfa(#plt{info = Info, contracts = Contracts}, MFA) -> (table_lookup(Info, MFA) =/= none) orelse (table_lookup(Contracts, MFA) =/= none). @@ -208,14 +205,14 @@ from_file(FileName, ReturnInfo) -> Msg = io_lib:format("Old PLT file ~s\n", [FileName]), error(Msg); ok -> - Plt = #dialyzer_plt{info = Rec#dialyzer_file_plt.info, - types = Rec#dialyzer_file_plt.types, - contracts = Rec#dialyzer_file_plt.contracts}, + Plt = #plt{info = Rec#file_plt.info, + types = Rec#file_plt.types, + contracts = Rec#file_plt.contracts}, case ReturnInfo of false -> Plt; true -> - PltInfo = {Rec#dialyzer_file_plt.file_md5_list, - Rec#dialyzer_file_plt.mod_deps}, + PltInfo = {Rec#file_plt.file_md5_list, + Rec#file_plt.mod_deps}, {Plt, PltInfo} end end; @@ -230,25 +227,25 @@ from_file(FileName, ReturnInfo) -> included_files(FileName) -> case get_record_from_file(FileName) of - {ok, #dialyzer_file_plt{file_md5_list = Md5}} -> + {ok, #file_plt{file_md5_list = Md5}} -> {ok, [File || {File, _} <- Md5]}; {error, _What} = Error -> Error end. -check_version(#dialyzer_file_plt{version=?VSN, implementation_md5=ImplMd5}) -> +check_version(#file_plt{version = ?VSN, implementation_md5 = ImplMd5}) -> case compute_new_md5(ImplMd5, [], []) of ok -> ok; {differ, _, _} -> error; {error, _} -> error end; -check_version(#dialyzer_file_plt{}) -> error. +check_version(#file_plt{}) -> error. get_record_from_file(FileName) -> case file:read_file(FileName) of {ok, Bin} -> try binary_to_term(Bin) of - #dialyzer_file_plt{} = FilePLT -> {ok, FilePLT}; + #file_plt{} = FilePLT -> {ok, FilePLT}; _ -> {error, not_valid} catch _:_ -> {error, not_valid} @@ -262,30 +259,30 @@ get_record_from_file(FileName) -> -spec merge_plts([plt()]) -> plt(). merge_plts(List) -> - InfoList = [Info || #dialyzer_plt{info = Info} <- List], - TypesList = [Types || #dialyzer_plt{types = Types} <- List], - ContractsList = [Contracts || #dialyzer_plt{contracts = Contracts} <- List], - #dialyzer_plt{info = table_merge(InfoList), - types = table_merge(TypesList), - contracts = table_merge(ContractsList)}. + InfoList = [Info || #plt{info = Info} <- List], + TypesList = [Types || #plt{types = Types} <- List], + ContractsList = [Contracts || #plt{contracts = Contracts} <- List], + #plt{info = table_merge(InfoList), + types = table_merge(TypesList), + contracts = table_merge(ContractsList)}. -spec to_file(file:filename(), plt(), mod_deps(), {[file_md5()], mod_deps()}) -> 'ok'. to_file(FileName, - #dialyzer_plt{info = Info, types = Types, contracts = Contracts}, + #plt{info = Info, types = Types, contracts = Contracts}, ModDeps, {MD5, OldModDeps}) -> NewModDeps = dict:merge(fun(_Key, OldVal, NewVal) -> ordsets:union(OldVal, NewVal) end, OldModDeps, ModDeps), ImplMd5 = compute_implementation_md5(), - Record = #dialyzer_file_plt{version = ?VSN, - file_md5_list = MD5, - info = Info, - contracts = Contracts, - types = Types, - mod_deps = NewModDeps, - implementation_md5 = ImplMd5}, + Record = #file_plt{version = ?VSN, + file_md5_list = MD5, + info = Info, + contracts = Contracts, + types = Types, + mod_deps = NewModDeps, + implementation_md5 = ImplMd5}, Bin = term_to_binary(Record, [compressed]), case file:write_file(FileName, Bin) of ok -> ok; @@ -307,7 +304,7 @@ to_file(FileName, check_plt(FileName, RemoveFiles, AddFiles) -> case get_record_from_file(FileName) of - {ok, #dialyzer_file_plt{file_md5_list = Md5, mod_deps = ModDeps} = Rec} -> + {ok, #file_plt{file_md5_list = Md5, mod_deps = ModDeps} = Rec} -> case check_version(Rec) of ok -> case compute_new_md5(Md5, RemoveFiles, AddFiles) of @@ -420,18 +417,17 @@ init_md5_list_1(Md5List, [], Acc) -> -spec get_specs(plt()) -> string(). -get_specs(#dialyzer_plt{info = Info}) -> +get_specs(#plt{info = Info}) -> %% TODO: Should print contracts as well. - List = - lists:sort([{MFA, Val} || {MFA = {_,_,_}, Val} <- table_to_list(Info)]), - lists:flatten(create_specs(List, [])). + L = lists:sort([{MFA, Val} || {{_,_,_} = MFA, Val} <- table_to_list(Info)]), + lists:flatten(create_specs(L, [])). beam_file_to_module(Filename) -> list_to_atom(filename:basename(Filename, ".beam")). -spec get_specs(plt(), module(), atom(), arity_patt()) -> 'none' | string(). -get_specs(#dialyzer_plt{info = Info}, M, F, A) when is_atom(M), is_atom(F) -> +get_specs(#plt{info = Info}, M, F, A) when is_atom(M), is_atom(F) -> MFA = {M, F, A}, case table_lookup(Info, MFA) of none -> none; @@ -526,9 +522,9 @@ table_merge([H|T]) -> table_merge([], Acc) -> Acc; -table_merge([Plt|Left], Acc) -> +table_merge([Plt|Plts], Acc) -> NewAcc = dict:merge(fun(_Key, Val, Val) -> Val end, Plt, Acc), - table_merge(Left, NewAcc). + table_merge(Plts, NewAcc). %%--------------------------------------------------------------------------- %% Debug utilities. @@ -538,7 +534,7 @@ table_merge([Plt|Left], Acc) -> pp_non_returning() -> PltFile = get_default_plt(), Plt = from_file(PltFile), - List = table_to_list(Plt#dialyzer_plt.info), + List = table_to_list(Plt#plt.info), Unit = [{MFA, erl_types:t_fun(Args, Ret)} || {MFA, {Ret, Args}} <- List, erl_types:t_is_unit(Ret)], None = [{MFA, erl_types:t_fun(Args, Ret)} || {MFA, {Ret, Args}} <- List, diff --git a/lib/dialyzer/src/dialyzer_races.erl b/lib/dialyzer/src/dialyzer_races.erl index 5857f7a03d0a..303e64e68e7d 100644 --- a/lib/dialyzer/src/dialyzer_races.erl +++ b/lib/dialyzer/src/dialyzer_races.erl @@ -50,8 +50,10 @@ -define(local, 5). -define(no_arg, no_arg). -define(no_label, no_label). +-define(bypassed, bypassed). -define(WARN_WHEREIS_REGISTER, warn_whereis_register). +-define(WARN_WHEREIS_UNREGISTER, warn_whereis_unregister). -define(WARN_ETS_LOOKUP_INSERT, warn_ets_lookup_insert). -define(WARN_MNESIA_DIRTY_READ_WRITE, warn_mnesia_dirty_read_write). -define(WARN_NO_WARN, warn_no_warn). @@ -64,27 +66,29 @@ -type mfa_or_funlbl() :: label() | mfa(). --type label_type() :: label() | [label()] | {label()} | ?no_label. --type args() :: [label_type() | [string()]]. --type core_vars() :: cerl:cerl() | ?no_arg. --type var_to_map() :: core_vars() | [cerl:cerl()]. --type core_args() :: [core_vars()] | 'empty'. --type op() :: 'bind' | 'unbind'. +-type label_type() :: label() | [label()] | {label()} | ?no_label. +-type args() :: [label_type() | [string()]]. +-type core_vars() :: cerl:cerl() | ?no_arg | ?bypassed. +-type var_to_map1() :: core_vars() | [cerl:cerl()]. +-type var_to_map2() :: cerl:cerl() | [cerl:cerl()] | ?bypassed. +-type core_args() :: [core_vars()] | 'empty'. +-type op() :: 'bind' | 'unbind'. -type dep_calls() :: 'whereis' | 'ets_lookup' | 'mnesia_dirty_read'. --type warn_calls() :: 'register' | 'ets_insert' | 'mnesia_dirty_write'. --type call() :: 'whereis' | 'register' | 'ets_new' | 'ets_lookup' - | 'ets_insert' | 'mnesia_dirty_read1' +-type warn_calls() :: 'register' | 'unregister' | 'ets_insert' + | 'mnesia_dirty_write'. +-type call() :: 'whereis' | 'register' | 'unregister' | 'ets_new' + | 'ets_lookup' | 'ets_insert' | 'mnesia_dirty_read1' | 'mnesia_dirty_read2' | 'mnesia_dirty_write1' | 'mnesia_dirty_write2' | 'function_call'. --type race_tag() :: 'whereis_register' | 'ets_lookup_insert' - | 'mnesia_dirty_read_write'. +-type race_tag() :: 'whereis_register' | 'whereis_unregister' + | 'ets_lookup_insert' | 'mnesia_dirty_read_write'. --record(beg_clause, {arg :: var_to_map(), - pats :: var_to_map(), +-record(beg_clause, {arg :: var_to_map1(), + pats :: var_to_map1(), guard :: cerl:cerl()}). --record(end_clause, {arg :: var_to_map(), - pats :: var_to_map(), +-record(end_clause, {arg :: var_to_map1(), + pats :: var_to_map1(), guard :: cerl:cerl()}). -record(end_case, {clauses :: [#end_clause{}]}). -record(curr_fun, {status :: 'in' | 'out', @@ -98,15 +102,15 @@ args :: args(), arg_types :: [erl_types:erl_type()], vars :: [core_vars()], - state :: _, + state :: _, %% XXX: recursive file_line :: file_line(), var_map :: dict()}). -record(fun_call, {caller :: mfa_or_funlbl(), callee :: mfa_or_funlbl(), arg_types :: [erl_types:erl_type()], vars :: [core_vars()]}). --record(let_tag, {var :: var_to_map(), - arg :: var_to_map()}). +-record(let_tag, {var :: var_to_map1(), + arg :: var_to_map1()}). -record(warn_call, {call_name :: warn_calls(), args :: args(), var_map :: dict()}). @@ -180,6 +184,14 @@ store_race_call(Fun, ArgTypes, Args, FileLine, State) -> fun_mfa = CurrFun, fun_label = CurrFunLabel}, {[#warn_call{call_name = register, args = VarArgs}| RaceList], RaceListSize + 1, [RaceFun|RaceTags], no_t}; + {erlang, unregister, 1} -> + VarArgs = format_args(Args, ArgTypes, CleanState, unregister), + RaceFun = #race_fun{mfa = Fun, args = VarArgs, + arg_types = ArgTypes, vars = Args, + file_line = FileLine, index = RaceListSize, + fun_mfa = CurrFun, fun_label = CurrFunLabel}, + {[#warn_call{call_name = unregister, args = VarArgs}| + RaceList], RaceListSize + 1, [RaceFun|RaceTags], no_t}; {erlang, whereis, 1} -> VarArgs = format_args(Args, ArgTypes, CleanState, whereis), {[#dep_call{call_name = whereis, args = VarArgs, @@ -280,6 +292,7 @@ race(State) -> RaceWarnTag = case Fun of {erlang, register, 2} -> ?WARN_WHEREIS_REGISTER; + {erlang, unregister, 1} -> ?WARN_WHEREIS_UNREGISTER; {ets, insert, 2} -> ?WARN_ETS_LOOKUP_INSERT; {mnesia, dirty_write, _A} -> ?WARN_MNESIA_DIRTY_READ_WRITE end, @@ -287,7 +300,7 @@ race(State) -> state__renew_curr_fun(CurrFun, state__renew_curr_fun_label(CurrFunLabel, state__renew_race_list(lists:nthtail(length(RaceList) - Index, - RaceList), State))), + RaceList), State))), DepList = fixup_race_list(RaceWarnTag, VarArgs, State1), {State2, RaceWarn} = get_race_warn(Fun, Args, ArgTypes, DepList, State), @@ -309,6 +322,7 @@ fixup_race_list(RaceWarnTag, WarnVarArgs, State) -> RaceTag = case RaceWarnTag of ?WARN_WHEREIS_REGISTER -> whereis_register; + ?WARN_WHEREIS_UNREGISTER -> whereis_unregister; ?WARN_ETS_LOOKUP_INSERT -> ets_lookup_insert; ?WARN_MNESIA_DIRTY_READ_WRITE -> mnesia_dirty_read_write end, @@ -320,11 +334,9 @@ fixup_race_list(RaceWarnTag, WarnVarArgs, State) -> lists:reverse(NewRaceList), [], CurrFun, WarnVarArgs, RaceWarnTag, dict:new(), [], [], [], 2 * ?local, NewState), - Parents = - fixup_race_backward(CurrFun, Calls, Calls, [], ?local), + Parents = fixup_race_backward(CurrFun, Calls, Calls, [], ?local), UParents = lists:usort(Parents), - Filtered = - filter_parents(UParents, UParents, Digraph), + Filtered = filter_parents(UParents, UParents, Digraph), NewParents = case lists:member(CurrFun, Filtered) of true -> Filtered; @@ -401,8 +413,7 @@ fixup_race_forward_pullout(CurrFun, CurrFunLabel, Calls, Code, RaceList, false -> {ok, Fun} = Name, {ok, Int} = Label, - case dict:find(Fun, - dialyzer_callgraph:get_race_code(Callgraph)) of + case dict:find(Fun, dialyzer_callgraph:get_race_code(Callgraph)) of error -> {NewCurrFun, NewCurrFunLabel, NewCalls, Tail, NewRaceList, NewRaceVarMap, NewFunDefVars, NewFunCallVars, NewFunArgTypes, @@ -459,7 +470,8 @@ fixup_race_forward(CurrFun, CurrFunLabel, Calls, Code, RaceList, case Head of #dep_call{call_name = whereis} -> case RaceWarnTag of - ?WARN_WHEREIS_REGISTER -> + WarnWhereis when WarnWhereis =:= ?WARN_WHEREIS_REGISTER orelse + WarnWhereis =:= ?WARN_WHEREIS_UNREGISTER -> {[Head#dep_call{var_map = RaceVarMap}|RaceList], [], NestingLevel, false}; _Other -> @@ -493,9 +505,11 @@ fixup_race_forward(CurrFun, CurrFunLabel, Calls, Code, RaceList, _Other -> {RaceList, [], NestingLevel, false} end; - #warn_call{call_name = register} -> + #warn_call{call_name = RegCall} when RegCall =:= register orelse + RegCall =:= unregister -> case RaceWarnTag of - ?WARN_WHEREIS_REGISTER -> + WarnWhereis when WarnWhereis =:= ?WARN_WHEREIS_REGISTER orelse + WarnWhereis =:= ?WARN_WHEREIS_UNREGISTER -> {[Head#warn_call{var_map = RaceVarMap}|RaceList], [], NestingLevel, false}; _Other -> @@ -575,6 +589,10 @@ fixup_race_forward(CurrFun, CurrFunLabel, Calls, Code, RaceList, {[#warn_call{call_name = register, args = WarnVarArgs, var_map = RaceVarMap}], NewDepList}; + whereis_unregister -> + {[#warn_call{call_name = unregister, args = WarnVarArgs, + var_map = RaceVarMap}], + NewDepList}; ets_lookup_insert -> NewWarnCall = [#warn_call{call_name = ets_insert, args = WarnVarArgs, @@ -760,6 +778,19 @@ get_deplist_paths(RaceList, WarnVarArgs, RaceWarnTag, RaceVarMap, CurrLevel, _ -> {[Vars, WVA2, WVA3, WVA4], false} end; + ?WARN_WHEREIS_UNREGISTER -> + [WVA1, WVA2] = WarnVarArgs1, + Vars = + lists:flatten( + [find_all_bound_vars(V, RaceVarMap1) || V <- WVA1]), + case {Vars, CurrLevel} of + {[], 0} -> + {WarnVarArgs, true}; + {[], _} -> + {WarnVarArgs, false}; + _ -> + {[Vars, WVA2], false} + end; ?WARN_ETS_LOOKUP_INSERT -> [WVA1, WVA2, WVA3, WVA4] = WarnVarArgs1, Vars1 = @@ -805,8 +836,9 @@ get_deplist_paths(RaceList, WarnVarArgs, RaceWarnTag, RaceVarMap, CurrLevel, get_deplist_paths(Tail, WarnVarArgs2, RaceWarnTag, RaceVarMap1, CurrLevel1, PublicTables, NamedTables) end; - #warn_call{call_name = register, args = WarnVarArgs1, - var_map = RaceVarMap1} -> + #warn_call{call_name = RegCall, args = WarnVarArgs1, + var_map = RaceVarMap1} when RegCall =:= register orelse + RegCall =:= unregister -> case compare_first_arg(WarnVarArgs, WarnVarArgs1, RaceVarMap1) of true -> {[], false, false}; NewWarnVarArgs -> @@ -1416,7 +1448,8 @@ lists_get(N, List) -> lists:nth(N, List). refine_race(RaceCall, WarnVarArgs, RaceWarnTag, DependencyList, RaceVarMap) -> case RaceWarnTag of - ?WARN_WHEREIS_REGISTER -> + WarnWhereis when WarnWhereis =:= ?WARN_WHEREIS_REGISTER orelse + WarnWhereis =:= ?WARN_WHEREIS_UNREGISTER -> case RaceCall of #dep_call{call_name = ets_lookup} -> DependencyList; @@ -1658,6 +1691,20 @@ compare_types(VarArgs, WarnVarArgs, RaceWarnTag, RaceVarMap) -> compare_var_list(VA1, WVA1, RaceVarMap) orelse compare_argtypes(VA2, WVA2) + end + end; + ?WARN_WHEREIS_UNREGISTER -> + [VA1, VA2] = VarArgs, + [WVA1, WVA2] = WarnVarArgs, + case any_args(VA2) of + true -> compare_var_list(VA1, WVA1, RaceVarMap); + false -> + case any_args(WVA2) of + true -> compare_var_list(VA1, WVA1, RaceVarMap); + false -> + compare_var_list(VA1, WVA1, RaceVarMap) orelse + compare_argtypes(VA2, WVA2) + end end; ?WARN_ETS_LOOKUP_INSERT -> @@ -1683,8 +1730,8 @@ compare_types(VarArgs, WarnVarArgs, RaceWarnTag, RaceVarMap) -> true -> compare_var_list(VA3, WVA3, RaceVarMap); false -> - compare_var_list(VA3, WVA3, RaceVarMap) - orelse compare_argtypes(VA4, WVA4) + compare_var_list(VA3, WVA3, RaceVarMap) orelse + compare_argtypes(VA4, WVA4) end end); ?WARN_MNESIA_DIRTY_READ_WRITE -> @@ -1818,6 +1865,7 @@ ets_tuple_argtypes1(Str, Tuple, TupleList, NestingLevel) -> end end. +format_arg(?bypassed) -> ?no_label; format_arg(Arg) -> case cerl:type(Arg) of var -> cerl_trees:get_label(Arg); @@ -1845,9 +1893,13 @@ format_args_1([Arg], [Type], CleanState) -> [format_arg(Arg), format_type(Type, CleanState)]; format_args_1([Arg|Args], [Type|Types], CleanState) -> List = - case cerl:is_literal(Arg) of - true -> [?no_label, format_cerl(Arg)]; - false -> [format_arg(Arg), format_type(Type, CleanState)] + case Arg =:= ?bypassed of + true -> [?no_label, format_type(Type, CleanState)]; + false -> + case cerl:is_literal(Arg) of + true -> [?no_label, format_cerl(Arg)]; + false -> [format_arg(Arg), format_type(Type, CleanState)] + end end, List ++ format_args_1(Args, Types, CleanState). @@ -1859,6 +1911,9 @@ format_args_2(StrArgList, Call) -> register -> lists_key_replace(2, StrArgList, string:tokens(lists:nth(2, StrArgList), " |")); + unregister -> + lists_key_replace(2, StrArgList, + string:tokens(lists:nth(2, StrArgList), " |")); ets_new -> StrArgList1 = lists_key_replace(2, StrArgList, string:tokens(lists:nth(2, StrArgList), " |")), @@ -1919,10 +1974,11 @@ mnesia_tuple_argtypes(TupleStr) -> [TupleStr2|_T] = string:tokens(TupleStr1, " ,"), lists:flatten(string:tokens(TupleStr2, " |")). --spec race_var_map(var_to_map(), cerl:cerl() | [cerl:cerl()], dict(), op()) -> dict(). +-spec race_var_map(var_to_map1(), var_to_map2(), dict(), op()) -> dict(). race_var_map(Vars1, Vars2, RaceVarMap, Op) -> - case Vars1 =:= ?no_arg of + case Vars1 =:= ?no_arg orelse Vars1 =:= ?bypassed + orelse Vars2 =:= ?bypassed of true -> RaceVarMap; false -> case is_list(Vars1) andalso is_list(Vars2) of @@ -2076,7 +2132,7 @@ race_var_map_guard(Arg, Pats, Guard, RaceVarMap, Op) -> {RaceVarMap1, RemoveClause orelse RemoveClause1}. race_var_map_guard_helper1(Arg, Pats, RaceVarMap, Op) -> - case Arg =:= ?no_arg of + case Arg =:= ?no_arg orelse Arg =:= ?bypassed of true -> {RaceVarMap, false}; false -> case cerl:type(Arg) of @@ -2139,7 +2195,7 @@ unbind_dict_vars(Var1, Var2, RaceVarMap) -> true -> unbind_dict_vars(Var1, Var2, bind_dict_vars_list(Var1, Labels -- [Var2], - dict:erase(Var1, RaceVarMap))); + dict:erase(Var1, RaceVarMap))); false -> unbind_dict_vars_helper(Labels, Var1, Var2, RaceVarMap) end @@ -2171,6 +2227,10 @@ var_analysis(FunDefArgs, FunCallArgs, WarnVarArgs, RaceWarnTag) -> [WVA1, WVA2, WVA3, WVA4] = WarnVarArgs, ArgNos = lists_key_members_lists(WVA1, FunDefArgs), [[lists_get(N, FunCallArgs) || N <- ArgNos], WVA2, WVA3, WVA4]; + ?WARN_WHEREIS_UNREGISTER -> + [WVA1, WVA2] = WarnVarArgs, + ArgNos = lists_key_members_lists(WVA1, FunDefArgs), + [[lists_get(N, FunCallArgs) || N <- ArgNos], WVA2]; ?WARN_ETS_LOOKUP_INSERT -> [WVA1, WVA2, WVA3, WVA4] = WarnVarArgs, ArgNos1 = lists_key_members_lists(WVA1, FunDefArgs), @@ -2185,8 +2245,7 @@ var_analysis(FunDefArgs, FunCallArgs, WarnVarArgs, RaceWarnTag) -> var_type_analysis(FunDefArgs, FunCallTypes, WarnVarArgs, RaceWarnTag, RaceVarMap, CleanState) -> - FunVarArgs = format_args(FunDefArgs, FunCallTypes, CleanState, - function_call), + FunVarArgs = format_args(FunDefArgs, FunCallTypes, CleanState, function_call), case RaceWarnTag of ?WARN_WHEREIS_REGISTER -> [WVA1, WVA2, WVA3, WVA4] = WarnVarArgs, @@ -2197,6 +2256,15 @@ var_type_analysis(FunDefArgs, FunCallTypes, WarnVarArgs, RaceWarnTag, NewWVA2 = string:tokens(lists:nth(N + 1, FunVarArgs), " |"), [Vars, NewWVA2, WVA3, WVA4] end; + ?WARN_WHEREIS_UNREGISTER -> + [WVA1, WVA2] = WarnVarArgs, + Vars = find_all_bound_vars(WVA1, RaceVarMap), + case lists_key_member_lists(Vars, FunVarArgs) of + 0 -> [Vars, WVA2]; + N when is_integer(N) -> + NewWVA2 = string:tokens(lists:nth(N + 1, FunVarArgs), " |"), + [Vars, NewWVA2] + end; ?WARN_ETS_LOOKUP_INSERT -> [WVA1, WVA2, WVA3, WVA4] = WarnVarArgs, Vars1 = find_all_bound_vars(WVA1, RaceVarMap), @@ -2278,6 +2346,10 @@ get_race_warnings_helper(Warnings, State) -> get_reason(lists:keysort(7, DepList), "might fail due to a possible race condition " "caused by its combination with "); + ?WARN_WHEREIS_UNREGISTER -> + get_reason(lists:keysort(7, DepList), + "might fail due to a possible race condition " + "caused by its combination with "); ?WARN_ETS_LOOKUP_INSERT -> get_reason(lists:keysort(7, DepList), "might have an unintended effect due to " ++ @@ -2335,7 +2407,7 @@ state__add_race_warning(State, RaceWarn, RaceWarnTag, FileLine) -> %%% %%% =========================================================================== --spec beg_clause_new(var_to_map(), var_to_map(), cerl:cerl()) -> +-spec beg_clause_new(var_to_map1(), var_to_map1(), cerl:cerl()) -> #beg_clause{}. beg_clause_new(Arg, Pats, Guard) -> @@ -2351,7 +2423,7 @@ cleanup(#races{race_list = RaceList}) -> end_case_new(Clauses) -> #end_case{clauses = Clauses}. --spec end_clause_new(var_to_map(), var_to_map(), cerl:cerl()) -> +-spec end_clause_new(var_to_map1(), var_to_map1(), cerl:cerl()) -> #end_clause{}. end_clause_new(Arg, Pats, Guard) -> @@ -2387,7 +2459,7 @@ get_race_list(#races{race_list = RaceList}) -> get_race_list_size(#races{race_list_size = RaceListSize}) -> RaceListSize. --spec let_tag_new(var_to_map(), var_to_map()) -> #let_tag{}. +-spec let_tag_new(var_to_map1(), var_to_map1()) -> #let_tag{}. let_tag_new(Var, Arg) -> #let_tag{var = Var, arg = Arg}. @@ -2422,5 +2494,4 @@ put_race_analysis(Analysis, Races) -> races(). put_race_list(RaceList, RaceListSize, Races) -> - Races#races{race_list = RaceList, - race_list_size = RaceListSize}. + Races#races{race_list = RaceList, race_list_size = RaceListSize}. diff --git a/lib/dialyzer/src/dialyzer_succ_typings.erl b/lib/dialyzer/src/dialyzer_succ_typings.erl index dd8480f1f294..1670a6cf8909 100644 --- a/lib/dialyzer/src/dialyzer_succ_typings.erl +++ b/lib/dialyzer/src/dialyzer_succ_typings.erl @@ -29,7 +29,7 @@ -export([analyze_callgraph/3, analyze_callgraph/4, - get_warnings/6]). + get_warnings/7]). %% These are only intended as debug functions. -export([doit/1, @@ -106,19 +106,21 @@ get_refined_success_typings(State) -> -type doc_plt() :: 'undefined' | dialyzer_plt:plt(). -spec get_warnings(dialyzer_callgraph:callgraph(), dialyzer_plt:plt(), doc_plt(), dialyzer_codeserver:codeserver(), set(), - pid()) -> + pid(), boolean()) -> {[dial_warning()], dialyzer_plt:plt(), doc_plt()}. -get_warnings(Callgraph, Plt, DocPlt, Codeserver, NoWarnUnused, Parent) -> +get_warnings(Callgraph, Plt, DocPlt, Codeserver, + NoWarnUnused, Parent, BehavioursChk) -> InitState = #st{callgraph = Callgraph, codeserver = Codeserver, no_warn_unused = NoWarnUnused, parent = Parent, plt = Plt}, NewState = get_refined_success_typings(InitState), Mods = dialyzer_callgraph:modules(NewState#st.callgraph), CWarns = dialyzer_contracts:get_invalid_contract_warnings(Mods, Codeserver, NewState#st.plt), - get_warnings_from_modules(Mods, NewState, DocPlt, CWarns). + get_warnings_from_modules(Mods, NewState, DocPlt, BehavioursChk, CWarns). -get_warnings_from_modules([M|Ms], State, DocPlt, Acc) when is_atom(M) -> +get_warnings_from_modules([M|Ms], State, DocPlt, + BehavioursChk, Acc) when is_atom(M) -> send_log(State#st.parent, io_lib:format("Getting warnings for ~w\n", [M])), #st{callgraph = Callgraph, codeserver = Codeserver, no_warn_unused = NoWarnUnused, plt = Plt} = State, @@ -131,13 +133,20 @@ get_warnings_from_modules([M|Ms], State, DocPlt, Acc) when is_atom(M) -> dialyzer_contracts:contracts_without_fun(Contracts, AllFuns, Callgraph), {Warnings2, FunTypes, RaceCode, PublicTables, NamedTables} = dialyzer_dataflow:get_warnings(ModCode, Plt, Callgraph, Records, NoWarnUnused), + Attrs = cerl:module_attrs(ModCode), + Warnings3 = if BehavioursChk -> + dialyzer_behaviours:check_callbacks(M, Attrs, + Plt, Codeserver); + true -> [] + end, NewDocPlt = insert_into_doc_plt(FunTypes, Callgraph, DocPlt), NewCallgraph = dialyzer_callgraph:renew_race_info(Callgraph, RaceCode, PublicTables, NamedTables), State1 = st__renew_state_calls(NewCallgraph, State), - get_warnings_from_modules(Ms, State1, NewDocPlt, [Warnings1,Warnings2|Acc]); -get_warnings_from_modules([], #st{plt = Plt}, DocPlt, Acc) -> + get_warnings_from_modules(Ms, State1, NewDocPlt, BehavioursChk, + [Warnings1, Warnings2, Warnings3|Acc]); +get_warnings_from_modules([], #st{plt = Plt}, DocPlt, _, Acc) -> {lists:flatten(Acc), Plt, DocPlt}. refine_succ_typings(ModulePostorder, State) -> diff --git a/lib/dialyzer/src/dialyzer_typesig.erl b/lib/dialyzer/src/dialyzer_typesig.erl index aeb20d4fae93..57d869b88aea 100644 --- a/lib/dialyzer/src/dialyzer_typesig.erl +++ b/lib/dialyzer/src/dialyzer_typesig.erl @@ -43,6 +43,7 @@ t_is_float/1, t_is_fun/1, t_is_integer/1, t_non_neg_integer/0, t_is_list/1, t_is_nil/1, t_is_none/1, t_is_number/1, + t_is_subtype/2, t_limit/2, t_list/0, t_list/1, t_list_elements/1, t_nonempty_list/1, t_maybe_improper_list/0, t_module/0, t_number/0, t_number_vals/1, @@ -51,7 +52,7 @@ t_pid/0, t_port/0, t_product/1, t_reference/0, t_subst/2, t_subtract/2, t_subtract_list/2, t_sup/1, t_sup/2, t_timeout/0, t_tuple/0, t_tuple/1, - t_unify/2, t_var/1, t_var_name/1, + t_unify/3, t_var/1, t_var_name/1, t_none/0, t_unit/0]). -include("dialyzer.hrl"). @@ -71,14 +72,20 @@ rhs :: fvar_or_type(), deps :: [dep()]}). +-type constraint() :: #constraint{}. + -record(constraint_list, {type :: 'conj' | 'disj', - list :: [_], % [constr()] but it needs recursion :-( + list :: [constr()], deps :: [dep()], id :: {'list', dep()}}). +-type constraint_list() :: #constraint_list{}. + -record(constraint_ref, {id :: type_var(), deps :: [dep()]}). --type constr() :: #constraint{} | #constraint_list{} | #constraint_ref{}. +-type constraint_ref() :: #constraint_ref{}. + +-type constr() :: constraint() | constraint_list() | constraint_ref(). -type typesig_scc() :: [{mfa(), {cerl:c_var(), cerl:c_fun()}, dict()}]. -type typesig_funmap() :: [{type_var(), type_var()}]. %% Orddict @@ -90,6 +97,7 @@ fun_arities = dict:new() :: dict(), in_match = false :: boolean(), in_guard = false :: boolean(), + module :: module(), name_map = dict:new() :: dict(), next_label :: label(), non_self_recs = [] :: [label()], @@ -98,7 +106,7 @@ records = dict:new() :: dict(), opaques = [] :: [erl_types:erl_type()], scc = [] :: [type_var()]}). - + %%----------------------------------------------------------------------------- -define(TYPE_LIMIT, 4). @@ -631,6 +639,9 @@ handle_call(Call, DefinedVars, State) -> get_plt_constr(MFA, Dst, ArgVars, State) -> Plt = state__plt(State), PltRes = dialyzer_plt:lookup(Plt, MFA), + Opaques = State#state.opaques, + Module = State#state.module, + {FunModule, _, _} = MFA, case dialyzer_plt:lookup_contract(Plt, MFA) of none -> case PltRes of @@ -651,7 +662,14 @@ get_plt_constr(MFA, Dst, ArgVars, State) -> %% Need to combine the contract with the success typing. {mk_fun_var( fun(Map) -> - ArgTypes = lookup_type_list(ArgVars, Map), + ArgTypes0 = lookup_type_list(ArgVars, Map), + ArgTypes = case FunModule =:= Module of + false -> + List = lists:zip(PltArgTypes, ArgTypes0), + [erl_types:t_unopaque_on_mismatch(T1, T2, Opaques) + || {T1, T2} <- List]; + true -> ArgTypes0 + end, CRet = dialyzer_contracts:get_contract_return(C, ArgTypes), t_inf(CRet, PltRetType, opaque) end, ArgVars), @@ -681,8 +699,8 @@ filter_match_fail([]) -> %% If there is a significant number of clauses, we cannot apply the %% list subtraction scheme since it causes the analysis to be too %% slow. Typically, this only affects automatically generated files. -%% Anyway, and the dataflow analysis doesn't suffer from this, so we -%% will get some information anyway. +%% The dataflow analysis doesn't suffer from this, so we will get some +%% information anyway. -define(MAX_NOF_CLAUSES, 15). handle_clauses(Clauses, TopVar, Arg, DefinedVars, State) -> @@ -843,7 +861,7 @@ get_underapprox_from_guard(Tree, Map) -> (cerl:is_c_var(Arg2) orelse cerl:is_literal(Arg2))) of true -> {Arg1Type, _} = get_underapprox_from_guard(Arg1, Map), - {Arg2Type, _} = get_underapprox_from_guard(Arg1, Map), + {Arg2Type, _} = get_underapprox_from_guard(Arg2, Map), case (t_is_equal(True, Arg1Type) andalso t_is_equal(True, Arg2Type)) of true -> {True, Map}; @@ -1511,13 +1529,21 @@ get_bif_constr({erlang, element, 2} = _BIF, Dst, Args, State) -> Cs = mk_constraints(Args, sub, ArgTypes), mk_conj_constraint_list([mk_constraint(Dst, sub, ReturnType)|Cs]) end; -get_bif_constr({M, F, A} = _BIF, Dst, Args, _State) -> +get_bif_constr({M, F, A} = _BIF, Dst, Args, State) -> GenType = erl_bif_types:type(M, F, A), + Opaques = State#state.opaques, case t_is_none(GenType) of true -> ?debug("Bif: ~w failed\n", [_BIF]), throw(error); false -> + UnopaqueFun = + fun(T) -> case lists:member(T, Opaques) of + true -> erl_types:t_unopaque(T, [T]); + false -> T + end + end, ReturnType = mk_fun_var(fun(Map) -> - TmpArgTypes = lookup_type_list(Args, Map), + TmpArgTypes0 = lookup_type_list(Args, Map), + TmpArgTypes = [UnopaqueFun(T) || T<- TmpArgTypes0], erl_bif_types:type(M, F, A, TmpArgTypes) end, Args), case erl_bif_types:is_known(M, F, A) of @@ -1815,7 +1841,7 @@ solve_cs([#constraint_list{} = C|Tail], Map, MapDict, State) -> {error, _NewMapDict} = Error -> Error end; solve_cs([#constraint{} = C|Tail], Map, MapDict, State) -> - case solve_one_c(C, Map) of + case solve_one_c(C, Map, State#state.opaques) of error -> ?debug("+++++++++++\nFailed: ~s :: ~s ~w ~s :: ~s\n+++++++++++\n", [format_type(C#constraint.lhs), @@ -1830,7 +1856,7 @@ solve_cs([#constraint{} = C|Tail], Map, MapDict, State) -> solve_cs([], Map, MapDict, _State) -> {ok, MapDict, Map}. -solve_one_c(#constraint{lhs = Lhs, rhs = Rhs, op = Op}, Map) -> +solve_one_c(#constraint{lhs = Lhs, rhs = Rhs, op = Op}, Map, Opaques) -> LhsType = lookup_type(Lhs, Map), RhsType = lookup_type(Rhs, Map), Inf = t_inf(LhsType, RhsType, opaque), @@ -1841,16 +1867,16 @@ solve_one_c(#constraint{lhs = Lhs, rhs = Rhs, op = Op}, Map) -> true -> error; false -> case Op of - sub -> solve_subtype(Lhs, Inf, Map); + sub -> solve_subtype(Lhs, Inf, Map, Opaques); eq -> - case solve_subtype(Lhs, Inf, Map) of + case solve_subtype(Lhs, Inf, Map, Opaques) of error -> error; - {ok, Map1} -> solve_subtype(Rhs, Inf, Map1) + {ok, Map1} -> solve_subtype(Rhs, Inf, Map1, Opaques) end end end. -solve_subtype(Type, Inf, Map) -> +solve_subtype(Type, Inf, Map, Opaques) -> %% case cerl:is_literal(Type) of %% true -> %% case t_is_subtype(t_from_term(cerl:concrete(Type)), Inf) of @@ -1858,7 +1884,7 @@ solve_subtype(Type, Inf, Map) -> %% false -> error %% end; %% false -> - try t_unify(Type, Inf) of + try t_unify(Type, Inf, Opaques) of {_, List} -> {ok, enter_type_list(List, Map)} catch throw:{mismatch, _T1, _T2} -> @@ -2046,7 +2072,7 @@ state__set_rec_dict(State, RecDict) -> state__set_opaques(#state{records = RecDict} = State, {M, _F, _A}) -> Opaques = erl_types:module_builtin_opaques(M) ++ t_opaque_from_records(RecDict), - State#state{opaques = Opaques}. + State#state{opaques = Opaques, module = M}. state__lookup_record(#state{records = Records}, Tag, Arity) -> case erl_types:lookup_record(Tag, Arity, Records) of @@ -2258,7 +2284,7 @@ mk_constraint(Lhs, Op, Rhs) -> case Deps =:= [] of true -> %% This constraint is constant. Solve it immediately. - case solve_one_c(C, dict:new()) of + case solve_one_c(C, dict:new(), []) of error -> throw(error); _ -> %% This is always true, keep it anyway for logistic reasons diff --git a/lib/dialyzer/src/dialyzer_utils.erl b/lib/dialyzer/src/dialyzer_utils.erl index fa9ad2eae290..d6ddf76956a6 100644 --- a/lib/dialyzer/src/dialyzer_utils.erl +++ b/lib/dialyzer/src/dialyzer_utils.erl @@ -47,6 +47,32 @@ -include("dialyzer.hrl"). +%%-define(DEBUG, true). + +-ifdef(DEBUG). +print_types(RecDict) -> + Keys = dict:fetch_keys(RecDict), + print_types1(Keys, RecDict). + +print_types1([], _) -> + ok; +print_types1([{type, _Name} = Key|T], RecDict) -> + {ok, {_Mod, Form, _Args}} = dict:find(Key, RecDict), + io:format("\n~w: ~w\n", [Key, erl_types:t_from_form(Form, RecDict)]), + print_types1(T, RecDict); +print_types1([{opaque, _Name} = Key|T], RecDict) -> + {ok, {_Mod, Form, _Args}} = dict:find(Key, RecDict), + io:format("\n~w: ~w\n", [Key, erl_types:t_from_form(Form, RecDict)]), + print_types1(T, RecDict); +print_types1([{record, _Name} = Key|T], RecDict) -> + {ok, [{Arity, Fields} = AF]} = dict:find(Key, RecDict), + io:format("~w: ~w\n\n", [Key, AF]), + print_types1(T, RecDict). +-define(debug(D_), print_types(D_)). +-else. +-define(debug(D_), ok). +-endif. + %% %% Types that need to be imported from somewhere else %% @@ -61,13 +87,13 @@ %% ============================================================================ -spec get_abstract_code_from_src(atom() | file:filename()) -> - {'ok', abstract_code()} | {'error', [string()]}. + {'ok', abstract_code()} | {'error', [string()]}. get_abstract_code_from_src(File) -> get_abstract_code_from_src(File, src_compiler_opts()). -spec get_abstract_code_from_src(atom() | file:filename(), comp_options()) -> - {'ok', abstract_code()} | {'error', [string()]}. + {'ok', abstract_code()} | {'error', [string()]}. get_abstract_code_from_src(File, Opts) -> case compile:file(File, [to_pp, binary|Opts]) of @@ -137,60 +163,60 @@ get_core_from_abstract_code(AbstrCode, Opts) -> %% ============================================================================ -spec get_record_and_type_info(abstract_code()) -> - {'ok', dict()} | {'error', string()}. + {'ok', dict()} | {'error', string()}. get_record_and_type_info(AbstractCode) -> Module = get_module(AbstractCode), get_record_and_type_info(AbstractCode, Module, dict:new()). -spec get_record_and_type_info(abstract_code(), atom(), dict()) -> - {'ok', dict()} | {'error', string()}. + {'ok', dict()} | {'error', string()}. + +get_record_and_type_info(AbstractCode, Module, RecDict) -> + get_record_and_type_info(AbstractCode, Module, [], RecDict). get_record_and_type_info([{attribute, _, record, {Name, Fields0}}|Left], - Module, RecDict) -> - case get_record_fields(Fields0, RecDict) of - {ok, Fields} -> - Arity = length(Fields), - Fun = fun(OldOrdDict) -> orddict:store(Arity, Fields, OldOrdDict) end, - NewRecDict = dict:update({record, Name}, Fun, [{Arity, Fields}], RecDict), - get_record_and_type_info(Left, Module, NewRecDict); - {error, Error} -> - {error, lists:flatten(io_lib:format(" Error while parsing #~w{}: ~s\n", - [Name, Error]))} - end; + Module, Records, RecDict) -> + {ok, Fields} = get_record_fields(Fields0, RecDict), + Arity = length(Fields), + NewRecDict = dict:store({record, Name}, [{Arity, Fields}], RecDict), + get_record_and_type_info(Left, Module, [{record, Name}|Records], NewRecDict); get_record_and_type_info([{attribute, _, type, {{record, Name}, Fields0, []}} - |Left], Module, RecDict) -> + |Left], Module, Records, RecDict) -> %% This overrides the original record declaration. - case get_record_fields(Fields0, RecDict) of - {ok, Fields} -> - Arity = length(Fields), - Fun = fun(OldOrdDict) -> orddict:store(Arity, Fields, OldOrdDict) end, - NewRecDict = dict:update({record, Name}, Fun, [{Arity, Fields}], RecDict), - get_record_and_type_info(Left, Module, NewRecDict); - {error, Error} -> - {error, lists:flatten(io_lib:format(" Error while parsing #~w{}: ~s\n", - [Name, Error]))} - end; + {ok, Fields} = get_record_fields(Fields0, RecDict), + Arity = length(Fields), + NewRecDict = dict:store({record, Name}, [{Arity, Fields}], RecDict), + get_record_and_type_info(Left, Module, Records, NewRecDict); get_record_and_type_info([{attribute, _, Attr, {Name, TypeForm}}|Left], - Module, RecDict) when Attr =:= 'type'; Attr =:= 'opaque' -> + Module, Records, RecDict) when Attr =:= 'type'; + Attr =:= 'opaque' -> try NewRecDict = add_new_type(Attr, Name, TypeForm, [], Module, RecDict), - get_record_and_type_info(Left, Module, NewRecDict) + get_record_and_type_info(Left, Module, Records, NewRecDict) catch throw:{error, _} = Error -> Error end; get_record_and_type_info([{attribute, _, Attr, {Name, TypeForm, Args}}|Left], - Module, RecDict) when Attr =:= 'type'; Attr =:= 'opaque' -> + Module, Records, RecDict) when Attr =:= 'type'; + Attr =:= 'opaque' -> try NewRecDict = add_new_type(Attr, Name, TypeForm, Args, Module, RecDict), - get_record_and_type_info(Left, Module, NewRecDict) + get_record_and_type_info(Left, Module, Records, NewRecDict) catch throw:{error, _} = Error -> Error end; -get_record_and_type_info([_Other|Left], Module, RecDict) -> - get_record_and_type_info(Left, Module, RecDict); -get_record_and_type_info([], _Module, RecDict) -> - {ok, RecDict}. +get_record_and_type_info([_Other|Left], Module, Records, RecDict) -> + get_record_and_type_info(Left, Module, Records, RecDict); +get_record_and_type_info([], _Module, Records, RecDict) -> + case type_record_fields(lists:reverse(Records), RecDict) of + {ok, _NewRecDict} = Ok -> + ?debug(NewRecDict), + Ok; + {Name, {error, Error}} -> + {error, lists:flatten(io_lib:format(" Error while parsing #~w{}: ~s\n", + [Name, Error]))} + end. add_new_type(TypeOrOpaque, Name, TypeForm, ArgForms, Module, RecDict) -> case erl_types:type_is_defined(TypeOrOpaque, Name, RecDict) of @@ -198,7 +224,6 @@ add_new_type(TypeOrOpaque, Name, TypeForm, ArgForms, Module, RecDict) -> throw({error, io_lib:format("Type already defined: ~w\n", [Name])}); false -> ArgTypes = [erl_types:t_from_form(X) || X <- ArgForms], - _Type = erl_types:t_from_form(TypeForm, RecDict), case lists:all(fun erl_types:t_is_var/1, ArgTypes) of true -> ArgNames = [erl_types:t_var_name(X) || X <- ArgTypes], @@ -219,21 +244,36 @@ get_record_fields([{typed_record_field, OrdRecField, TypeForm}|Left], {record_field, _Line, Name0} -> erl_parse:normalise(Name0); {record_field, _Line, Name0, _Init} -> erl_parse:normalise(Name0) end, - try - Type = erl_types:t_from_form(TypeForm, RecDict), - get_record_fields(Left, RecDict, [{Name, Type}|Acc]) - catch - throw:{error, _} = Error -> Error - end; + get_record_fields(Left, RecDict, [{Name, TypeForm}|Acc]); get_record_fields([{record_field, _Line, Name}|Left], RecDict, Acc) -> - NewAcc = [{erl_parse:normalise(Name), erl_types:t_any()}|Acc], + NewAcc = [{erl_parse:normalise(Name), {var, -1, '_'}}|Acc], get_record_fields(Left, RecDict, NewAcc); get_record_fields([{record_field, _Line, Name, _Init}|Left], RecDict, Acc) -> - NewAcc = [{erl_parse:normalise(Name), erl_types:t_any()}|Acc], + NewAcc = [{erl_parse:normalise(Name), {var, -1, '_'}}|Acc], get_record_fields(Left, RecDict, NewAcc); get_record_fields([], _RecDict, Acc) -> {ok, lists:reverse(Acc)}. +type_record_fields([], RecDict) -> + {ok, RecDict}; +type_record_fields([RecKey|Recs], RecDict) -> + {ok, [{Arity, Fields}]} = dict:find(RecKey, RecDict), + try + TypedFields = + [{FieldName, erl_types:t_from_form(FieldTypeForm, RecDict)} + || {FieldName, FieldTypeForm} <- Fields], + RecDict1 = dict:store(RecKey, [{Arity, TypedFields}], RecDict), + Fun = fun(OldOrdDict) -> + orddict:store(Arity, TypedFields, OldOrdDict) + end, + RecDict2 = dict:update(RecKey, Fun, RecDict1), + type_record_fields(Recs, RecDict2) + catch + throw:{error, _} = Error -> + {record, Name} = RecKey, + {Name, Error} + end. + -spec process_record_remote_types(dialyzer_codeserver:codeserver()) -> dialyzer_codeserver:codeserver(). process_record_remote_types(CServer) -> @@ -269,7 +309,7 @@ merge_records(NewRecords, OldRecords) -> %% ============================================================================ -spec get_spec_info(module(), abstract_code(), dict()) -> - {'ok', dict()} | {'error', string()}. + {'ok', dict()} | {'error', string()}. get_spec_info(ModName, AbstractCode, RecordsDict) -> get_spec_info(AbstractCode, dict:new(), RecordsDict, ModName, "nofile"). diff --git a/lib/dialyzer/vsn.mk b/lib/dialyzer/vsn.mk index a94695925600..e3e3f6d66896 100644 --- a/lib/dialyzer/vsn.mk +++ b/lib/dialyzer/vsn.mk @@ -1 +1 @@ -DIALYZER_VSN = 2.1.0 +DIALYZER_VSN = 2.2.0 diff --git a/lib/hipe/cerl/erl_bif_types.erl b/lib/hipe/cerl/erl_bif_types.erl index 8aa2cc76c7e0..ee0ddd69154a 100644 --- a/lib/hipe/cerl/erl_bif_types.erl +++ b/lib/hipe/cerl/erl_bif_types.erl @@ -102,6 +102,7 @@ t_list_elements/1, t_list_termination/1, t_mfa/0, + t_module/0, t_nil/0, t_node/0, t_none/0, @@ -694,6 +695,8 @@ type(erlang, binary_to_list, 3, Xs) -> fun (_) -> t_list(t_byte()) end); type(erlang, binary_to_term, 1, Xs) -> strict(arg_types(erlang, binary_to_term, 1), Xs, fun (_) -> t_any() end); +type(erlang, binary_to_term, 2, Xs) -> + strict(arg_types(erlang, binary_to_term, 2), Xs, fun (_) -> t_any() end); type(erlang, bitsize, 1, Xs) -> % XXX: TAKE OUT type(erlang, bit_size, 1, Xs); type(erlang, bit_size, 1, Xs) -> @@ -3127,20 +3130,20 @@ arith(Op, X1, X2) -> end, %% io:format("done arith ~p = ~p~n", [Op, {NewMin, NewMax}]), {ok, t_from_range(NewMin, NewMax)}; - false -> + false -> %% Some of these arithmetic operations might throw a system_limit %% exception; for example, when trying to evaluate 1 bsl 100000000. try case Op of - '+' -> [X + Y || X <- L1, Y <- L2]; - '-' -> [X - Y || X <- L1, Y <- L2]; - '*' -> [X * Y || X <- L1, Y <- L2]; - 'div' -> [X div Y || X <- L1, Y <- L2,Y =/= 0]; - 'rem' -> [X rem Y || X <- L1, Y <- L2,Y =/= 0]; - 'bsl' -> [X bsl Y || X <- L1, Y <- L2]; - 'bsr' -> [X bsr Y || X <- L1, Y <- L2]; - 'band' -> [X band Y || X <- L1, Y <- L2]; - 'bor' -> [X bor Y || X <- L1, Y <- L2]; - 'bxor' -> [X bxor Y || X <- L1, Y <- L2] + '+' -> [X + Y || X <- L1, Y <- L2]; + '-' -> [X - Y || X <- L1, Y <- L2]; + '*' -> [X * Y || X <- L1, Y <- L2]; + 'div' -> [X div Y || X <- L1, Y <- L2, Y =/= 0]; + 'rem' -> [X rem Y || X <- L1, Y <- L2, Y =/= 0]; + 'bsl' -> [X bsl Y || X <- L1, Y <- L2]; + 'bsr' -> [X bsr Y || X <- L1, Y <- L2]; + 'band' -> [X band Y || X <- L1, Y <- L2]; + 'bor' -> [X bor Y || X <- L1, Y <- L2]; + 'bxor' -> [X bxor Y || X <- L1, Y <- L2] end of AllVals -> {ok, t_integers(ordsets:from_list(AllVals))} @@ -3338,8 +3341,7 @@ arg_types(erlang, abs, 1) -> arg_types(erlang, append_element, 2) -> [t_tuple(), t_any()]; arg_types(erlang, apply, 2) -> - [t_sup(t_tuple([t_sup(t_atom(), % module name - t_tuple()), % parameterized module + [t_sup(t_tuple([t_module(), t_atom()]), t_fun()), t_list()]; @@ -3359,6 +3361,8 @@ arg_types(erlang, binary_to_list, 3) -> [t_binary(), t_pos_integer(), t_pos_integer()]; % I want fixnum, but cannot arg_types(erlang, binary_to_term, 1) -> [t_binary()]; +arg_types(erlang, binary_to_term, 2) -> + [t_binary(), t_list(t_atom('safe'))]; arg_types(erlang, bitsize, 1) -> % XXX: TAKE OUT arg_types(erlang, bit_size, 1); arg_types(erlang, bit_size, 1) -> @@ -4351,6 +4355,7 @@ structure_inspecting_args(erlang, is_pid, 1) -> [1]; structure_inspecting_args(erlang, is_port, 1) -> [1]; structure_inspecting_args(erlang, is_reference, 1) -> [1]; structure_inspecting_args(erlang, is_tuple, 1) -> [1]; +structure_inspecting_args(erlang, length, 1) -> [1]; %%structure_inspecting_args(erlang, setelement, 3) -> [2]. structure_inspecting_args(_, _, _) -> []. % XXX: assume no arg needs inspection diff --git a/lib/hipe/cerl/erl_types.erl b/lib/hipe/cerl/erl_types.erl index fac308d0c6e5..871856da834d 100644 --- a/lib/hipe/cerl/erl_types.erl +++ b/lib/hipe/cerl/erl_types.erl @@ -97,6 +97,7 @@ t_inf/3, t_inf_lists/2, t_inf_lists/3, + t_inf_lists_masked/3, t_integer/0, t_integer/1, t_non_neg_integer/0, @@ -167,6 +168,7 @@ t_opaque_match_record/2, t_opaque_matching_structure/2, t_opaque_structure/1, + %% t_parameterized_module/0, t_pid/0, t_port/0, t_maybe_improper_list/0, @@ -194,9 +196,11 @@ t_tuple_sizes/1, t_tuple_subtypes/1, t_unify/2, + t_unify/3, t_unit/0, t_unopaque/1, t_unopaque/2, + t_unopaque_on_mismatch/3, t_var/1, t_var_name/1, %% t_assign_variables_to_subtype/2, @@ -227,6 +231,8 @@ %% Limits %% +-define(REC_TYPE_LIMIT, 2). + -define(TUPLE_TAG_LIMIT, 5). -define(TUPLE_ARITY_LIMIT, 10). -define(SET_LIMIT, 13). @@ -590,33 +596,48 @@ t_opaque_tuple_tags(OpaqueStruct) -> end. %% Decompose opaque instances of type arg2 to structured types, in arg1 --spec t_struct_from_opaque(erl_type(), erl_type()) -> erl_type(). - -t_struct_from_opaque(?function(Domain, Range), Opaque) -> - ?function(t_struct_from_opaque(Domain, Opaque), - t_struct_from_opaque(Range, Opaque)); -t_struct_from_opaque(?list(Types, Term, Size), Opaque) -> - ?list(t_struct_from_opaque(Types, Opaque), Term, Size); -t_struct_from_opaque(?opaque(_) = T, Opaque) -> - case T =:= Opaque of +%% XXX: Same as t_unopaque +-spec t_struct_from_opaque(erl_type(), [erl_type()]) -> erl_type(). + +t_struct_from_opaque(?function(Domain, Range), Opaques) -> + ?function(t_struct_from_opaque(Domain, Opaques), + t_struct_from_opaque(Range, Opaques)); +t_struct_from_opaque(?list(Types, Term, Size), Opaques) -> + ?list(t_struct_from_opaque(Types, Opaques), Term, Size); +t_struct_from_opaque(?opaque(_) = T, Opaques) -> + case lists:member(T, Opaques) of true -> t_opaque_structure(T); false -> T end; -t_struct_from_opaque(?product(Types), Opaque) -> - ?product(list_struct_from_opaque(Types, Opaque)); -t_struct_from_opaque(?tuple(?any, _, _) = T, _Opaque) -> T; -t_struct_from_opaque(?tuple(Types, Arity, Tag), Opaque) -> - ?tuple(list_struct_from_opaque(Types, Opaque), Arity, Tag); -t_struct_from_opaque(?tuple_set(Set), Opaque) -> - NewSet = [{Sz, [t_struct_from_opaque(T, Opaque) || T <- Tuples]} +t_struct_from_opaque(?product(Types), Opaques) -> + ?product(list_struct_from_opaque(Types, Opaques)); +t_struct_from_opaque(?tuple(?any, _, _) = T, _Opaques) -> T; +t_struct_from_opaque(?tuple(Types, Arity, Tag), Opaques) -> + ?tuple(list_struct_from_opaque(Types, Opaques), Arity, Tag); +t_struct_from_opaque(?tuple_set(Set), Opaques) -> + NewSet = [{Sz, [t_struct_from_opaque(T, Opaques) || T <- Tuples]} || {Sz, Tuples} <- Set], ?tuple_set(NewSet); -t_struct_from_opaque(?union(List), Opaque) -> - t_sup(list_struct_from_opaque(List, Opaque)); -t_struct_from_opaque(Type, _Opaque) -> Type. - -list_struct_from_opaque(Types, Opaque) -> - [t_struct_from_opaque(Type, Opaque) || Type <- Types]. +t_struct_from_opaque(?union(List), Opaques) -> + t_sup(list_struct_from_opaque(List, Opaques)); +t_struct_from_opaque(Type, _Opaques) -> Type. + +list_struct_from_opaque(Types, Opaques) -> + [t_struct_from_opaque(Type, Opaques) || Type <- Types]. + +-spec t_unopaque_on_mismatch(erl_type(), erl_type(), [erl_type()]) -> erl_type(). + +t_unopaque_on_mismatch(GenType, Type, Opaques) -> + case t_inf(GenType, Type) of + ?none -> + Unopaqued = t_unopaque(Type, Opaques), + %% Unions might be a problem, must investigate. + case t_inf(GenType, Unopaqued) of + ?none -> Type; + _ -> Unopaqued + end; + _ -> Type + end. -spec module_builtin_opaques(module()) -> [erl_type()]. @@ -630,7 +651,7 @@ module_builtin_opaques(Module) -> -spec t_remote(module(), atom(), [_]) -> erl_type(). t_remote(Mod, Name, Args) -> - ?remote(set_singleton(#remote{mod=Mod, name=Name, args=Args})). + ?remote(set_singleton(#remote{mod = Mod, name = Name, args = Args})). -spec t_is_remote(erl_type()) -> boolean(). @@ -640,78 +661,124 @@ t_is_remote(_) -> false. -spec t_solve_remote(erl_type(), dict()) -> erl_type(). t_solve_remote(Type , Records) -> - t_solve_remote(Type, Records, ordsets:new()). + {RT, _RR} = t_solve_remote(Type, Records, []), + RT. t_solve_remote(?function(Domain, Range), R, C) -> - ?function(t_solve_remote(Domain, R, C), t_solve_remote(Range, R, C)); + {RT1, RR1} = t_solve_remote(Domain, R, C), + {RT2, RR2} = t_solve_remote(Range, R, C), + {?function(RT1, RT2), RR1 ++ RR2}; t_solve_remote(?list(Types, Term, Size), R, C) -> - ?list(t_solve_remote(Types, R, C), Term, Size); + {RT, RR} = t_solve_remote(Types, R, C), + {?list(RT, Term, Size), RR}; t_solve_remote(?product(Types), R, C) -> - ?product(list_solve_remote(Types, R, C)); + {RL, RR} = list_solve_remote(Types, R, C), + {?product(RL), RR}; t_solve_remote(?opaque(Set), R, C) -> List = ordsets:to_list(Set), - NewList = [Remote#opaque{struct = t_solve_remote(Struct, R, C)} - || Remote = #opaque{struct = Struct} <- List], - ?opaque(ordsets:from_list(NewList)); -t_solve_remote(?tuple(?any, _, _) = T, _R, _C) -> T; + {NewList, RR} = opaques_solve_remote(List, R, C), + {?opaque(ordsets:from_list(NewList)), RR}; +t_solve_remote(?tuple(?any, _, _) = T, _R, _C) -> {T, []}; t_solve_remote(?tuple(Types, Arity, Tag), R, C) -> - ?tuple(list_solve_remote(Types, R, C), Arity, Tag); -t_solve_remote(?tuple_set(Set), R, C) -> - NewSet = [{Sz, [t_solve_remote(T, R, C) || T <- Tuples]} || {Sz, Tuples} <- Set], - ?tuple_set(NewSet); + {RL, RR} = list_solve_remote(Types, R, C), + {?tuple(RL, Arity, Tag), RR}; +t_solve_remote(?tuple_set(Set), R, C) -> + {NewSet, RR} = tuples_solve_remote(Set, R, C), + {?tuple_set(NewSet), RR}; t_solve_remote(?remote(Set), R, C) -> - Cycle = ordsets:intersection(Set, C), - case ordsets:size(Cycle) of - 0 -> ok; - _ -> - CycleMsg = "Cycle detected while processing remote types: " ++ - t_to_string(?remote(C), dict:new()), - throw({error, CycleMsg}) - end, - NewCycle = ordsets:union(C, Set), - TypeFun = - fun(#remote{mod = RemoteModule, name = Name, args = Args}) -> - case dict:find(RemoteModule, R) of - error -> - Msg = io_lib:format("Cannot locate module ~w to " - "resolve the remote type: ~w:~w()~n", - [RemoteModule, RemoteModule, Name]), - throw({error, Msg}); - {ok, RemoteDict} -> - case lookup_type(Name, RemoteDict) of - {type, {_TypeMod, Type, ArgNames}} when length(Args) =:= length(ArgNames) -> - List = lists:zip(ArgNames, Args), - TmpVardict = dict:from_list(List), - NewType = t_from_form(Type, RemoteDict, TmpVardict), - t_solve_remote(NewType, R, NewCycle); - {opaque, {OpModule, Type, ArgNames}} when length(Args) =:= length(ArgNames) -> - List = lists:zip(ArgNames, Args), - TmpVardict = dict:from_list(List), - Rep = t_from_form(Type, RemoteDict, TmpVardict), - NewRep = t_solve_remote(Rep, R, NewCycle), - t_from_form({opaque, -1, Name, {OpModule, Args, NewRep}}, - RemoteDict, TmpVardict); - {type, _} -> - Msg = io_lib:format("Unknown remote type ~w\n", [Name]), - throw({error, Msg}); - {opaque, _} -> - Msg = io_lib:format("Unknown remote opaque type ~w\n", [Name]), - throw({error, Msg}); - error -> - Msg = io_lib:format("Unable to find remote type ~w:~w()\n", - [RemoteModule, Name]), - throw({error, Msg}) - end - end - end, RemoteList = ordsets:to_list(Set), - t_sup([TypeFun(RemoteType) || RemoteType <- RemoteList]); -t_solve_remote(?union(List), R, C) -> - t_sup(list_solve_remote(List, R, C)); -t_solve_remote(T, _R, _C) -> T. + {RL, RR} = list_solve_remote_type(RemoteList, R, C), + {t_sup(RL), RR}; +t_solve_remote(?union(List), R, C) -> + {RL, RR} = list_solve_remote(List, R, C), + {t_sup(RL), RR}; +t_solve_remote(T, _R, _C) -> {T, []}. + +t_solve_remote_type(#remote{mod = RemMod, name = Name, args = Args} = RemType, + R, C) -> + case dict:find(RemMod, R) of + error -> + Msg = io_lib:format("Cannot locate module ~w to " + "resolve the remote type: ~w:~w()~n", + [RemMod, RemMod, Name]), + throw({error, Msg}); + {ok, RemDict} -> + case lookup_type(Name, RemDict) of + {type, {_Mod, Type, ArgNames}} when length(Args) =:= length(ArgNames) -> + {NewType, NewCycle, NewRR} = + case unfold(RemType, C) of + true -> + List = lists:zip(ArgNames, Args), + TmpVarDict = dict:from_list(List), + {t_from_form(Type, RemDict, TmpVarDict), [RemType|C], []}; + false -> {t_any(), C, [RemType]} + end, + {RT, RR} = t_solve_remote(NewType, R, NewCycle), + RetRR = NewRR ++ RR, + RT1 = + case lists:member(RemType, RetRR) of + true -> t_limit(RT, ?REC_TYPE_LIMIT); + false -> RT + end, + {RT1, RetRR}; + {opaque, {Mod, Type, ArgNames}} when length(Args) =:= length(ArgNames) -> + List = lists:zip(ArgNames, Args), + TmpVarDict = dict:from_list(List), + {Rep, NewCycle, NewRR} = + case unfold(RemType, C) of + true -> {t_from_form(Type, RemDict, TmpVarDict), [RemType|C], []}; + false -> {t_any(), C, [RemType]} + end, + {NewRep, RR} = t_solve_remote(Rep, R, NewCycle), + RetRR = NewRR ++ RR, + RT1 = + case lists:member(RemType, RetRR) of + true -> t_limit(NewRep, ?REC_TYPE_LIMIT); + false -> NewRep + end, + {t_from_form({opaque, -1, Name, {Mod, Args, RT1}}, + RemDict, TmpVarDict), + RetRR}; + {type, _} -> + Msg = io_lib:format("Unknown remote type ~w\n", [Name]), + throw({error, Msg}); + {opaque, _} -> + Msg = io_lib:format("Unknown remote opaque type ~w\n", [Name]), + throw({error, Msg}); + error -> + Msg = io_lib:format("Unable to find remote type ~w:~w()\n", + [RemMod, Name]), + throw({error, Msg}) + end + end. -list_solve_remote(Types, R, C) -> - [t_solve_remote(Type, R, C) || Type <- Types]. +list_solve_remote([], _R, _C) -> + {[], []}; +list_solve_remote([Type|Types], R, C) -> + {RT, RR1} = t_solve_remote(Type, R, C), + {RL, RR2} = list_solve_remote(Types, R, C), + {[RT|RL], RR1 ++ RR2}. + +list_solve_remote_type([], _R, _C) -> + {[], []}; +list_solve_remote_type([Type|Types], R, C) -> + {RT, RR1} = t_solve_remote_type(Type, R, C), + {RL, RR2} = list_solve_remote_type(Types, R, C), + {[RT|RL], RR1 ++ RR2}. + +opaques_solve_remote([], _R, _C) -> + {[], []}; +opaques_solve_remote([#opaque{struct = Struct} = Remote|Tail], R, C) -> + {RT, RR1} = t_solve_remote(Struct, R, C), + {LOp, RR2} = opaques_solve_remote(Tail, R, C), + {[Remote#opaque{struct = RT}|LOp], RR1 ++ RR2}. + +tuples_solve_remote([], _R, _C) -> + {[], []}; +tuples_solve_remote([{Sz, Tuples}|Tail], R, C) -> + {RL, RR1} = list_solve_remote(Tuples, R, C), + {LSzTpls, RR2} = tuples_solve_remote(Tail, R, C), + {[{Sz, RL}|LSzTpls], RR1 ++ RR2}. %%----------------------------------------------------------------------------- %% Unit type. Signals non termination. @@ -1432,7 +1499,7 @@ t_mfa() -> -spec t_module() -> erl_type(). t_module() -> - t_atom(). + t_sup(t_atom(), t_parameterized_module()). -spec t_node() -> erl_type(). @@ -1457,6 +1524,11 @@ t_iolist(N) when N > 0 -> t_iolist(0) -> t_maybe_improper_list(t_any(), t_sup(t_binary(), t_nil())). +-spec t_parameterized_module() -> erl_type(). + +t_parameterized_module() -> + t_tuple(). + -spec t_timeout() -> erl_type(). t_timeout() -> @@ -2219,9 +2291,23 @@ t_inf(T, ?union(U2), Mode) -> ?union(U1) = force_union(T), inf_union(U1, U2, Mode); %% and as a result, the cases for ?opaque should appear *after* ?union -t_inf(?opaque(Set1), ?opaque(Set2), _Mode) -> +t_inf(?opaque(Set1) = T1, ?opaque(Set2) = T2, Mode) -> case set_intersection(Set1, Set2) of - ?none -> ?none; + ?none -> + case Mode =:= opaque of + true -> + Struct1 = t_opaque_structure(T1), + case t_inf(Struct1, T2) of + ?none -> + Struct2 = t_opaque_structure(T2), + case t_inf(Struct2, T1) of + ?none -> ?none; + _ -> T2 + end; + _ -> T1 + end; + false -> ?none + end; NewSet -> ?opaque(NewSet) end; t_inf(?opaque(_) = T1, T2, opaque) -> @@ -2272,6 +2358,12 @@ t_inf_lists_strict([T1|Left1], [T2|Left2], Acc, Mode) -> t_inf_lists_strict([], [], Acc, _Mode) -> lists:reverse(Acc). +-spec t_inf_lists_masked([erl_type()], [erl_type()], [t_inf_mode()]) -> [erl_type()]. + +t_inf_lists_masked(List1, List2, Mask) -> + List = lists:zip3(List1, List2, Mask), + [t_inf(T1, T2, Mode) || {T1, T2, Mode} <- List]. + inf_tuple_sets(L1, L2, Mode) -> case inf_tuple_sets(L1, L2, [], Mode) of [] -> ?none; @@ -2434,82 +2526,112 @@ t_subst(T, _Dict, _Fun) -> -spec t_unify(erl_type(), erl_type()) -> {erl_type(), [{_, erl_type()}]}. t_unify(T1, T2) -> - {T, Dict} = t_unify(T1, T2, dict:new()), + t_unify(T1, T2, []). + +-spec t_unify(erl_type(), erl_type(), [erl_type()]) -> {erl_type(), [{_, erl_type()}]}. + +t_unify(T1, T2, Opaques) -> + {T, Dict} = t_unify(T1, T2, dict:new(), Opaques), {t_subst(T, Dict), lists:keysort(1, dict:to_list(Dict))}. -t_unify(?var(Id) = T, ?var(Id), Dict) -> +t_unify(?var(Id) = T, ?var(Id), Dict, _Opaques) -> {T, Dict}; -t_unify(?var(Id1) = T, ?var(Id2), Dict) -> +t_unify(?var(Id1) = T, ?var(Id2), Dict, Opaques) -> case dict:find(Id1, Dict) of error -> case dict:find(Id2, Dict) of error -> {T, dict:store(Id2, T, Dict)}; - {ok, Type} -> {Type, t_unify(T, Type, Dict)} + {ok, Type} -> {Type, t_unify(T, Type, Dict, Opaques)} end; {ok, Type1} -> case dict:find(Id2, Dict) of error -> {Type1, dict:store(Id2, T, Dict)}; - {ok, Type2} -> t_unify(Type1, Type2, Dict) + {ok, Type2} -> t_unify(Type1, Type2, Dict, Opaques) end end; -t_unify(?var(Id), Type, Dict) -> +t_unify(?var(Id), Type, Dict, Opaques) -> case dict:find(Id, Dict) of error -> {Type, dict:store(Id, Type, Dict)}; - {ok, VarType} -> t_unify(VarType, Type, Dict) + {ok, VarType} -> t_unify(VarType, Type, Dict, Opaques) end; -t_unify(Type, ?var(Id), Dict) -> +t_unify(Type, ?var(Id), Dict, Opaques) -> case dict:find(Id, Dict) of error -> {Type, dict:store(Id, Type, Dict)}; - {ok, VarType} -> t_unify(VarType, Type, Dict) + {ok, VarType} -> t_unify(VarType, Type, Dict, Opaques) end; -t_unify(?function(Domain1, Range1), ?function(Domain2, Range2), Dict) -> - {Domain, Dict1} = t_unify(Domain1, Domain2, Dict), - {Range, Dict2} = t_unify(Range1, Range2, Dict1), +t_unify(?function(Domain1, Range1), ?function(Domain2, Range2), Dict, Opaques) -> + {Domain, Dict1} = t_unify(Domain1, Domain2, Dict, Opaques), + {Range, Dict2} = t_unify(Range1, Range2, Dict1, Opaques), {?function(Domain, Range), Dict2}; t_unify(?list(Contents1, Termination1, Size), - ?list(Contents2, Termination2, Size), Dict) -> - {Contents, Dict1} = t_unify(Contents1, Contents2, Dict), - {Termination, Dict2} = t_unify(Termination1, Termination2, Dict1), + ?list(Contents2, Termination2, Size), Dict, Opaques) -> + {Contents, Dict1} = t_unify(Contents1, Contents2, Dict, Opaques), + {Termination, Dict2} = t_unify(Termination1, Termination2, Dict1, Opaques), {?list(Contents, Termination, Size), Dict2}; -t_unify(?product(Types1), ?product(Types2), Dict) -> - {Types, Dict1} = unify_lists(Types1, Types2, Dict), +t_unify(?product(Types1), ?product(Types2), Dict, Opaques) -> + {Types, Dict1} = unify_lists(Types1, Types2, Dict, Opaques), {?product(Types), Dict1}; -t_unify(?tuple(?any, ?any, ?any) = T, ?tuple(?any, ?any, ?any), Dict) -> +t_unify(?tuple(?any, ?any, ?any) = T, ?tuple(?any, ?any, ?any), Dict, _Opaques) -> {T, Dict}; t_unify(?tuple(Elements1, Arity, _), - ?tuple(Elements2, Arity, _), Dict) when Arity =/= ?any -> - {NewElements, Dict1} = unify_lists(Elements1, Elements2, Dict), + ?tuple(Elements2, Arity, _), Dict, Opaques) when Arity =/= ?any -> + {NewElements, Dict1} = unify_lists(Elements1, Elements2, Dict, Opaques), {t_tuple(NewElements), Dict1}; t_unify(?tuple_set([{Arity, _}]) = T1, - ?tuple(_, Arity, _) = T2, Dict) when Arity =/= ?any -> - unify_tuple_set_and_tuple(T1, T2, Dict); + ?tuple(_, Arity, _) = T2, Dict, Opaques) when Arity =/= ?any -> + unify_tuple_set_and_tuple(T1, T2, Dict, Opaques); t_unify(?tuple(_, Arity, _) = T1, - ?tuple_set([{Arity, _}]) = T2, Dict) when Arity =/= ?any -> - unify_tuple_set_and_tuple(T2, T1, Dict); -t_unify(?tuple_set(List1), ?tuple_set(List2), Dict) -> + ?tuple_set([{Arity, _}]) = T2, Dict, Opaques) when Arity =/= ?any -> + unify_tuple_set_and_tuple(T2, T1, Dict, Opaques); +t_unify(?tuple_set(List1), ?tuple_set(List2), Dict, Opaques) -> {Tuples, NewDict} = unify_lists(lists:append([T || {_Arity, T} <- List1]), - lists:append([T || {_Arity, T} <- List2]), Dict), + lists:append([T || {_Arity, T} <- List2]), Dict, Opaques), {t_sup(Tuples), NewDict}; -t_unify(T, T, Dict) -> +t_unify(?opaque(Elements) = T, ?opaque(Elements), Dict, _Opaques) -> {T, Dict}; -t_unify(T1, T2, _) -> +t_unify(?opaque(_) = T1, ?opaque(_) = T2, _Dict, _Opaques) -> + throw({mismatch, T1, T2}); +t_unify(Type, ?opaque(_) = OpType, Dict, Opaques) -> + t_unify_with_opaque(Type, OpType, Dict, Opaques); +t_unify(?opaque(_) = OpType, Type, Dict, Opaques) -> + t_unify_with_opaque(Type, OpType, Dict, Opaques); +t_unify(T, T, Dict, _Opaques) -> + {T, Dict}; +t_unify(T1, T2, _, _) -> throw({mismatch, T1, T2}). +t_unify_with_opaque(Type, OpType, Dict, Opaques) -> + case lists:member(OpType, Opaques) of + true -> + Struct = t_opaque_structure(OpType), + try t_unify(Type, Struct, Dict, Opaques) of + {_T, Dict1} -> {OpType, Dict1} + catch + throw:{mismatch, _T1, _T2} -> + case t_inf(OpType, Type, opaque) of + ?none -> throw({mismatch, Type, OpType}); + _ -> {OpType, Dict} + end + end; + false -> + throw({mismatch, Type, OpType}) + end. + unify_tuple_set_and_tuple(?tuple_set([{Arity, List}]), - ?tuple(Elements2, Arity, _), Dict) -> + ?tuple(Elements2, Arity, _), Dict, Opaques) -> %% Can only work if the single tuple has variables at correct places. %% Collapse the tuple set. - {NewElements, Dict1} = unify_lists(sup_tuple_elements(List), Elements2, Dict), + {NewElements, Dict1} = unify_lists(sup_tuple_elements(List), Elements2, Dict, Opaques), {t_tuple(NewElements), Dict1}. -unify_lists(L1, L2, Dict) -> - unify_lists(L1, L2, Dict, []). +unify_lists(L1, L2, Dict, Opaques) -> + unify_lists(L1, L2, Dict, [], Opaques). -unify_lists([T1|Left1], [T2|Left2], Dict, Acc) -> - {NewT, NewDict} = t_unify(T1, T2, Dict), - unify_lists(Left1, Left2, NewDict, [NewT|Acc]); -unify_lists([], [], Dict, Acc) -> +unify_lists([T1|Left1], [T2|Left2], Dict, Acc, Opaques) -> + {NewT, NewDict} = t_unify(T1, T2, Dict, Opaques), + unify_lists(Left1, Left2, NewDict, [NewT|Acc], Opaques); +unify_lists([], [], Dict, Acc, _Opaques) -> {lists:reverse(Acc), Dict}. %%t_assign_variables_to_subtype(T1, T2) -> @@ -3237,123 +3359,218 @@ t_from_form(Form, RecDict) -> -spec t_from_form(parse_form(), dict(), dict()) -> erl_type(). -t_from_form({var, _L, '_'}, _RecDict, _VarDict) -> t_any(); -t_from_form({var, _L, Name}, _RecDict, VarDict) -> +t_from_form(Form, RecDict, VarDict) -> + {T, _R} = t_from_form(Form, [], RecDict, VarDict), + T. + +-type type_names() :: [{'type' | 'opaque' | 'record', atom()}]. +-spec t_from_form(parse_form(), type_names(), dict(), dict()) -> + {erl_type(), type_names()}. + +t_from_form({var, _L, '_'}, _TypeNames, _RecDict, _VarDict) -> + {t_any(), []}; +t_from_form({var, _L, Name}, _TypeNames, _RecDict, VarDict) -> case dict:find(Name, VarDict) of - error -> t_var(Name); - {ok, Val} -> Val + error -> {t_var(Name), []}; + {ok, Val} -> {Val, []} end; -t_from_form({ann_type, _L, [_Var, Type]}, RecDict, VarDict) -> - t_from_form(Type, RecDict, VarDict); -t_from_form({paren_type, _L, [Type]}, RecDict, VarDict) -> - t_from_form(Type, RecDict, VarDict); +t_from_form({ann_type, _L, [_Var, Type]}, TypeNames, RecDict, VarDict) -> + t_from_form(Type, TypeNames, RecDict, VarDict); +t_from_form({paren_type, _L, [Type]}, TypeNames, RecDict, VarDict) -> + t_from_form(Type, TypeNames, RecDict, VarDict); t_from_form({remote_type, _L, [{atom, _, Module}, {atom, _, Type}, Args]}, - RecDict, VarDict) -> - t_remote(Module, Type, [t_from_form(A, RecDict, VarDict) || A <- Args]); -t_from_form({atom, _L, Atom}, _RecDict, _VarDict) -> t_atom(Atom); -t_from_form({integer, _L, Int}, _RecDict, _VarDict) -> t_integer(Int); -t_from_form({type, _L, any, []}, _RecDict, _VarDict) -> t_any(); -t_from_form({type, _L, arity, []}, _RecDict, _VarDict) -> t_arity(); -t_from_form({type, _L, array, []}, _RecDict, _VarDict) -> t_array(); -t_from_form({type, _L, atom, []}, _RecDict, _VarDict) -> t_atom(); -t_from_form({type, _L, binary, []}, _RecDict, _VarDict) -> t_binary(); + TypeNames, RecDict, VarDict) -> + {L, R} = list_from_form(Args, TypeNames, RecDict, VarDict), + {t_remote(Module, Type, L), R}; +t_from_form({atom, _L, Atom}, _TypeNames, _RecDict, _VarDict) -> + {t_atom(Atom), []}; +t_from_form({integer, _L, Int}, _TypeNames, _RecDict, _VarDict) -> + {t_integer(Int), []}; +t_from_form({type, _L, any, []}, _TypeNames, _RecDict, _VarDict) -> + {t_any(), []}; +t_from_form({type, _L, arity, []}, _TypeNames, _RecDict, _VarDict) -> + {t_arity(), []}; +t_from_form({type, _L, array, []}, _TypeNames, _RecDict, _VarDict) -> + {t_array(), []}; +t_from_form({type, _L, atom, []}, _TypeNames, _RecDict, _VarDict) -> + {t_atom(), []}; +t_from_form({type, _L, binary, []}, _TypeNames, _RecDict, _VarDict) -> + {t_binary(), []}; t_from_form({type, _L, binary, [{integer, _, Base}, {integer, _, Unit}]}, - _RecDict, _VarDict) -> - t_bitstr(Unit, Base); -t_from_form({type, _L, bitstring, []}, _RecDict, _VarDict) -> t_bitstr(); -t_from_form({type, _L, bool, []}, _RecDict, _VarDict) -> t_boolean(); % XXX: Temporarily -t_from_form({type, _L, boolean, []}, _RecDict, _VarDict) -> t_boolean(); -t_from_form({type, _L, byte, []}, _RecDict, _VarDict) -> t_byte(); -t_from_form({type, _L, char, []}, _RecDict, _VarDict) -> t_char(); -t_from_form({type, _L, dict, []}, _RecDict, _VarDict) -> t_dict(); -t_from_form({type, _L, digraph, []}, _RecDict, _VarDict) -> t_digraph(); -t_from_form({type, _L, float, []}, _RecDict, _VarDict) -> t_float(); -t_from_form({type, _L, function, []}, _RecDict, _VarDict) -> t_fun(); -t_from_form({type, _L, 'fun', []}, _RecDict, _VarDict) -> t_fun(); -t_from_form({type, _L, 'fun', [{type, _, any, []}, Range]}, RecDict, VarDict) -> - t_fun(t_from_form(Range, RecDict, VarDict)); -t_from_form({type, _L, 'fun', [{type, _, product, Domain}, Range]}, - RecDict, VarDict) -> - t_fun([t_from_form(D, RecDict, VarDict) || D <- Domain], - t_from_form(Range, RecDict, VarDict)); -t_from_form({type, _L, gb_set, []}, _RecDict, _VarDict) -> t_gb_set(); -t_from_form({type, _L, gb_tree, []}, _RecDict, _VarDict) -> t_gb_tree(); -t_from_form({type, _L, identifier, []}, _RecDict, _VarDict) -> t_identifier(); -t_from_form({type, _L, integer, []}, _RecDict, _VarDict) -> t_integer(); -t_from_form({type, _L, iodata, []}, _RecDict, _VarDict) -> t_iodata(); -t_from_form({type, _L, iolist, []}, _RecDict, _VarDict) -> t_iolist(); -t_from_form({type, _L, list, []}, _RecDict, _VarDict) -> t_list(); -t_from_form({type, _L, list, [Type]}, RecDict, VarDict) -> - t_list(t_from_form(Type, RecDict, VarDict)); -t_from_form({type, _L, mfa, []}, _RecDict, _VarDict) -> t_mfa(); -t_from_form({type, _L, module, []}, _RecDict, _VarDict) -> t_module(); -t_from_form({type, _L, nil, []}, _RecDict, _VarDict) -> t_nil(); -t_from_form({type, _L, neg_integer, []}, _RecDict, _VarDict) -> t_neg_integer(); -t_from_form({type, _L, non_neg_integer, []}, _RecDict, _VarDict) -> - t_non_neg_integer(); -t_from_form({type, _L, no_return, []}, _RecDict, _VarDict) -> t_unit(); -t_from_form({type, _L, node, []}, _RecDict, _VarDict) -> t_node(); -t_from_form({type, _L, none, []}, _RecDict, _VarDict) -> t_none(); -t_from_form({type, _L, nonempty_list, []}, _RecDict, _VarDict) -> - t_nonempty_list(); -t_from_form({type, _L, nonempty_list, [Type]}, RecDict, VarDict) -> - t_nonempty_list(t_from_form(Type, RecDict, VarDict)); -t_from_form({type, _L, nonempty_improper_list, [Cont, Term]}, - RecDict, VarDict) -> - t_cons(t_from_form(Cont, RecDict, VarDict), - t_from_form(Term, RecDict, VarDict)); -t_from_form({type, _L, nonempty_maybe_improper_list, []}, _RecDict, _VarDict) -> - t_cons(?any, ?any); -t_from_form({type, _L, nonempty_maybe_improper_list, [Cont, Term]}, - RecDict, VarDict) -> - t_cons(t_from_form(Cont, RecDict, VarDict), - t_from_form(Term, RecDict, VarDict)); -t_from_form({type, _L, nonempty_string, []}, _RecDict, _VarDict) -> - t_nonempty_string(); -t_from_form({type, _L, number, []}, _RecDict, _VarDict) -> t_number(); -t_from_form({type, _L, pid, []}, _RecDict, _VarDict) -> t_pid(); -t_from_form({type, _L, port, []}, _RecDict, _VarDict) -> t_port(); -t_from_form({type, _L, pos_integer, []}, _RecDict, _VarDict) -> t_pos_integer(); -t_from_form({type, _L, maybe_improper_list, []}, _RecDict, _VarDict) -> - t_maybe_improper_list(); -t_from_form({type, _L, maybe_improper_list, [Content, Termination]}, - RecDict, VarDict) -> - t_maybe_improper_list(t_from_form(Content, RecDict, VarDict), - t_from_form(Termination, RecDict, VarDict)); -t_from_form({type, _L, product, Elements}, RecDict, VarDict) -> - t_product([t_from_form(E, RecDict, VarDict) || E <- Elements]); -t_from_form({type, _L, queue, []}, _RecDict, _VarDict) -> t_queue(); + _TypeNames, _RecDict, _VarDict) -> + {t_bitstr(Unit, Base), []}; +t_from_form({type, _L, bitstring, []}, _TypeNames, _RecDict, _VarDict) -> + {t_bitstr(), []}; +t_from_form({type, _L, bool, []}, _TypeNames, _RecDict, _VarDict) -> + {t_boolean(), []}; % XXX: Temporarily +t_from_form({type, _L, boolean, []}, _TypeNames, _RecDict, _VarDict) -> + {t_boolean(), []}; +t_from_form({type, _L, byte, []}, _TypeNames, _RecDict, _VarDict) -> + {t_byte(), []}; +t_from_form({type, _L, char, []}, _TypeNames, _RecDict, _VarDict) -> + {t_char(), []}; +t_from_form({type, _L, dict, []}, _TypeNames, _RecDict, _VarDict) -> + {t_dict(), []}; +t_from_form({type, _L, digraph, []}, _TypeNames, _RecDict, _VarDict) -> + {t_digraph(), []}; +t_from_form({type, _L, float, []}, _TypeNames, _RecDict, _VarDict) -> + {t_float(), []}; +t_from_form({type, _L, function, []}, _TypeNames, _RecDict, _VarDict) -> + {t_fun(), []}; +t_from_form({type, _L, 'fun', []}, _TypeNames, _RecDict, _VarDict) -> + {t_fun(), []}; +t_from_form({type, _L, 'fun', [{type, _, any, []}, Range]}, TypeNames, + RecDict, VarDict) -> + {T, R} = t_from_form(Range, TypeNames, RecDict, VarDict), + {t_fun(T), R}; +t_from_form({type, _L, 'fun', [{type, _, product, Domain}, Range]}, + TypeNames, RecDict, VarDict) -> + {L, R1} = list_from_form(Domain, TypeNames, RecDict, VarDict), + {T, R2} = t_from_form(Range, TypeNames, RecDict, VarDict), + {t_fun(L, T), R1 ++ R2}; +t_from_form({type, _L, gb_set, []}, _TypeNames, _RecDict, _VarDict) -> + {t_gb_set(), []}; +t_from_form({type, _L, gb_tree, []}, _TypeNames, _RecDict, _VarDict) -> + {t_gb_tree(), []}; +t_from_form({type, _L, identifier, []}, _TypeNames, _RecDict, _VarDict) -> + {t_identifier(), []}; +t_from_form({type, _L, integer, []}, _TypeNames, _RecDict, _VarDict) -> + {t_integer(), []}; +t_from_form({type, _L, iodata, []}, _TypeNames, _RecDict, _VarDict) -> + {t_iodata(), []}; +t_from_form({type, _L, iolist, []}, _TypeNames, _RecDict, _VarDict) -> + {t_iolist(), []}; +t_from_form({type, _L, list, []}, _TypeNames, _RecDict, _VarDict) -> + {t_list(), []}; +t_from_form({type, _L, list, [Type]}, TypeNames, RecDict, VarDict) -> + {T, R} = t_from_form(Type, TypeNames, RecDict, VarDict), + {t_list(T), R}; +t_from_form({type, _L, mfa, []}, _TypeNames, _RecDict, _VarDict) -> + {t_mfa(), []}; +t_from_form({type, _L, module, []}, _TypeNames, _RecDict, _VarDict) -> + {t_module(), []}; +t_from_form({type, _L, nil, []}, _TypeNames, _RecDict, _VarDict) -> + {t_nil(), []}; +t_from_form({type, _L, neg_integer, []}, _TypeNames, _RecDict, _VarDict) -> + {t_neg_integer(), []}; +t_from_form({type, _L, non_neg_integer, []}, _TypeNames, _RecDict, _VarDict) -> + {t_non_neg_integer(), []}; +t_from_form({type, _L, no_return, []}, _TypeNames, _RecDict, _VarDict) -> + {t_unit(), []}; +t_from_form({type, _L, node, []}, _TypeNames, _RecDict, _VarDict) -> + {t_node(), []}; +t_from_form({type, _L, none, []}, _TypeNames, _RecDict, _VarDict) -> + {t_none(), []}; +t_from_form({type, _L, nonempty_list, []}, _TypeNames, _RecDict, _VarDict) -> + {t_nonempty_list(), []}; +t_from_form({type, _L, nonempty_list, [Type]}, TypeNames, RecDict, VarDict) -> + {T, R} = t_from_form(Type, TypeNames, RecDict, VarDict), + {t_nonempty_list(T), R}; +t_from_form({type, _L, nonempty_improper_list, [Cont, Term]}, TypeNames, + RecDict, VarDict) -> + {T1, R1} = t_from_form(Cont, TypeNames, RecDict, VarDict), + {T2, R2} = t_from_form(Term, TypeNames, RecDict, VarDict), + {t_cons(T1, T2), R1 ++ R2}; +t_from_form({type, _L, nonempty_maybe_improper_list, []}, _TypeNames, + _RecDict, _VarDict) -> + {t_cons(?any, ?any), []}; +t_from_form({type, _L, nonempty_maybe_improper_list, [Cont, Term]}, TypeNames, + RecDict, VarDict) -> + {T1, R1} = t_from_form(Cont, TypeNames, RecDict, VarDict), + {T2, R2} = t_from_form(Term, TypeNames, RecDict, VarDict), + {t_cons(T1, T2), R1 ++ R2}; +t_from_form({type, _L, nonempty_string, []}, _TypeNames, _RecDict, _VarDict) -> + {t_nonempty_string(), []}; +t_from_form({type, _L, number, []}, _TypeNames, _RecDict, _VarDict) -> + {t_number(), []}; +t_from_form({type, _L, pid, []}, _TypeNames, _RecDict, _VarDict) -> + {t_pid(), []}; +t_from_form({type, _L, port, []}, _TypeNames, _RecDict, _VarDict) -> + {t_port(), []}; +t_from_form({type, _L, pos_integer, []}, _TypeNames, _RecDict, _VarDict) -> + {t_pos_integer(), []}; +t_from_form({type, _L, maybe_improper_list, []}, _TypeNames, _RecDict, + _VarDict) -> + {t_maybe_improper_list(), []}; +t_from_form({type, _L, maybe_improper_list, [Content, Termination]}, TypeNames, + RecDict, VarDict) -> + {T1, R1} = t_from_form(Content, TypeNames, RecDict, VarDict), + {T2, R2} = t_from_form(Termination, TypeNames, RecDict, VarDict), + {t_maybe_improper_list(T1, T2), R1 ++ R2}; +t_from_form({type, _L, product, Elements}, TypeNames, RecDict, VarDict) -> + {L, R} = list_from_form(Elements, TypeNames, RecDict, VarDict), + {t_product(L), R}; +t_from_form({type, _L, queue, []}, _TypeNames, _RecDict, _VarDict) -> + {t_queue(), []}; t_from_form({type, _L, range, [{integer, _, From}, {integer, _, To}]}, - _RecDict, _VarDict) -> - t_from_range(From, To); -t_from_form({type, _L, record, [Name|Fields]}, RecDict, VarDict) -> - record_from_form(Name, Fields, RecDict, VarDict); -t_from_form({type, _L, reference, []}, _RecDict, _VarDict) -> t_reference(); -t_from_form({type, _L, set, []}, _RecDict, _VarDict) -> t_set(); -t_from_form({type, _L, string, []}, _RecDict, _VarDict) -> t_string(); -t_from_form({type, _L, term, []}, _RecDict, _VarDict) -> t_any(); -t_from_form({type, _L, tid, []}, _RecDict, _VarDict) -> t_tid(); -t_from_form({type, _L, timeout, []}, _RecDict, _VarDict) -> t_timeout(); -t_from_form({type, _L, tuple, any}, _RecDict, _VarDict) -> t_tuple(); -t_from_form({type, _L, tuple, Args}, RecDict, VarDict) -> - t_tuple([t_from_form(A, RecDict, VarDict) || A <- Args]); -t_from_form({type, _L, union, Args}, RecDict, VarDict) -> - t_sup([t_from_form(A, RecDict, VarDict) || A <- Args]); -t_from_form({type, _L, Name, Args}, RecDict, VarDict) -> + _TypeNames, _RecDict, _VarDict) -> + {t_from_range(From, To), []}; +t_from_form({type, _L, record, [Name|Fields]}, TypeNames, RecDict, VarDict) -> + record_from_form(Name, Fields, TypeNames, RecDict, VarDict); +t_from_form({type, _L, reference, []}, _TypeNames, _RecDict, _VarDict) -> + {t_reference(), []}; +t_from_form({type, _L, set, []}, _TypeNames, _RecDict, _VarDict) -> + {t_set(), []}; +t_from_form({type, _L, string, []}, _TypeNames, _RecDict, _VarDict) -> + {t_string(), []}; +t_from_form({type, _L, term, []}, _TypeNames, _RecDict, _VarDict) -> + {t_any(), []}; +t_from_form({type, _L, tid, []}, _TypeNames, _RecDict, _VarDict) -> + {t_tid(), []}; +t_from_form({type, _L, timeout, []}, _TypeNames, _RecDict, _VarDict) -> + {t_timeout(), []}; +t_from_form({type, _L, tuple, any}, _TypeNames, _RecDict, _VarDict) -> + {t_tuple(), []}; +t_from_form({type, _L, tuple, Args}, TypeNames, RecDict, VarDict) -> + {L, R} = list_from_form(Args, TypeNames, RecDict, VarDict), + {t_tuple(L), R}; +t_from_form({type, _L, union, Args}, TypeNames, RecDict, VarDict) -> + {L, R} = list_from_form(Args, TypeNames, RecDict, VarDict), + {t_sup(L), R}; +t_from_form({type, _L, Name, Args}, TypeNames, RecDict, VarDict) -> case lookup_type(Name, RecDict) of {type, {_Module, Type, ArgNames}} when length(Args) =:= length(ArgNames) -> - List = lists:zipwith(fun(ArgName, ArgType) -> - {ArgName, t_from_form(ArgType, RecDict, VarDict)} - end, ArgNames, Args), - TmpVardict = dict:from_list(List), - t_from_form(Type, RecDict, TmpVardict); + case unfold({type, Name}, TypeNames) of + true -> + List = lists:zipwith( + fun(ArgName, ArgType) -> + {Ttemp, _R} = t_from_form(ArgType, TypeNames, + RecDict, VarDict), + {ArgName, Ttemp} + end, + ArgNames, Args), + TmpVarDict = dict:from_list(List), + {T, R} = t_from_form(Type, [{type, Name}|TypeNames], RecDict, + TmpVarDict), + case lists:member({type, Name}, R) of + true -> {t_limit(T, ?REC_TYPE_LIMIT), R}; + false -> {T, R} + end; + false -> {t_any(), [{type, Name}]} + end; {opaque, {Module, Type, ArgNames}} when length(Args) =:= length(ArgNames) -> - List = lists:zipwith(fun(ArgName, ArgType) -> - {ArgName, t_from_form(ArgType, RecDict, VarDict)} - end, ArgNames, Args), - TmpVardict = dict:from_list(List), - Rep = t_from_form(Type, RecDict, TmpVardict), - t_from_form({opaque, -1, Name, {Module, Args, Rep}}, RecDict, VarDict); + {Rep, Rret} = + case unfold({opaque, Name}, TypeNames) of + true -> + List = lists:zipwith( + fun(ArgName, ArgType) -> + {Ttemp, _R} = t_from_form(ArgType, TypeNames, + RecDict, VarDict), + {ArgName, Ttemp} + end, + ArgNames, Args), + TmpVarDict = dict:from_list(List), + {T, R} = t_from_form(Type, [{opaque, Name}|TypeNames], RecDict, + TmpVarDict), + case lists:member({opaque, Name}, R) of + true -> {t_limit(T, ?REC_TYPE_LIMIT), R}; + false -> {T, R} + end; + false -> {t_any(), [{opaque, Name}]} + end, + Tret = t_from_form({opaque, -1, Name, {Module, Args, Rep}}, + RecDict, VarDict), + {Tret, Rret}; {type, _} -> throw({error, io_lib:format("Unknown type ~w\n", [Name])}); {opaque, _} -> @@ -3361,48 +3578,70 @@ t_from_form({type, _L, Name, Args}, RecDict, VarDict) -> error -> throw({error, io_lib:format("Unable to find type ~w\n", [Name])}) end; -t_from_form({opaque, _L, Name, {Mod, Args, Rep}}, _RecDict, _VarDict) -> +t_from_form({opaque, _L, Name, {Mod, Args, Rep}}, _TypeNames, _RecDict, + _VarDict) -> case Args of - [] -> t_opaque(Mod, Name, Args, Rep); + [] -> {t_opaque(Mod, Name, Args, Rep), []}; _ -> throw({error, "Polymorphic opaque types not supported yet"}) end. -record_from_form({atom, _, Name}, ModFields, RecDict, VarDict) -> - case lookup_record(Name, RecDict) of - {ok, DeclFields} -> - case get_mod_record(ModFields, DeclFields, RecDict, VarDict) of - {error, FieldName} -> - throw({error, io_lib:format("Illegal declaration of ~w#{~w}\n", - [Name, FieldName])}); - {ok, NewFields} -> - t_tuple([t_atom(Name)|[Type || {_FieldName, Type} <- NewFields]]) +record_from_form({atom, _, Name}, ModFields, TypeNames, RecDict, VarDict) -> + case unfold({record, Name}, TypeNames) of + true -> + case lookup_record(Name, RecDict) of + {ok, DeclFields} -> + TypeNames1 = [{record, Name}|TypeNames], + AreTyped = [is_erl_type(FieldType) + || {_FieldName, FieldType} <- DeclFields], + {DeclFields1, R1} = + case lists:all(fun(Elem) -> Elem end, AreTyped) of + true -> {DeclFields, []}; + false -> fields_from_form(DeclFields, TypeNames1, + RecDict, dict:new()) + end, + {GetModRec, R2} = get_mod_record(ModFields, DeclFields1, + TypeNames1, RecDict, VarDict), + case GetModRec of + {error, FieldName} -> + throw({error, io_lib:format("Illegal declaration of ~w#{~w}\n", + [Name, FieldName])}); + {ok, NewFields} -> + {t_tuple( + [t_atom(Name)|[Type || {_FieldName, Type} <- NewFields]]), + R1 ++ R2} + end; + error -> + throw({error, erlang:error(io_lib:format("Unknown record #~w{}\n", + [Name]))}) end; - error -> - throw({error, - erlang:error(io_lib:format("Unknown record #~w{}\n", [Name]))}) + false -> {t_any(), []} end. -get_mod_record([], DeclFields, _RecDict, _VarDict) -> - {ok, DeclFields}; -get_mod_record(ModFields, DeclFields, RecDict, VarDict) -> +get_mod_record([], DeclFields, _TypeNames, _RecDict, _VarDict) -> + {{ok, DeclFields}, []}; +get_mod_record(ModFields, DeclFields, TypeNames, RecDict, VarDict) -> DeclFieldsDict = orddict:from_list(DeclFields), - ModFieldsDict = build_field_dict(ModFields, RecDict, VarDict), + {ModFieldsDict, R} = build_field_dict(ModFields, TypeNames, + RecDict, VarDict), case get_mod_record(DeclFieldsDict, ModFieldsDict, []) of - {error, _FieldName} = Error -> Error; + {error, _FieldName} = Error -> {Error, R}; {ok, FinalOrdDict} -> - {ok, [{FieldName, orddict:fetch(FieldName, FinalOrdDict)} - || {FieldName, _} <- DeclFields]} + {{ok, [{FieldName, orddict:fetch(FieldName, FinalOrdDict)} + || {FieldName, _} <- DeclFields]}, + R} end. -build_field_dict(FieldTypes, RecDict, VarDict) -> - build_field_dict(FieldTypes, RecDict, VarDict, []). +build_field_dict(FieldTypes, TypeNames, RecDict, VarDict) -> + build_field_dict(FieldTypes, TypeNames, RecDict, VarDict, []). build_field_dict([{type, _, field_type, [{atom, _, Name}, Type]}|Left], - RecDict, VarDict, Acc) -> - NewAcc = [{Name, t_from_form(Type, RecDict, VarDict)}|Acc], - build_field_dict(Left, RecDict, VarDict, NewAcc); -build_field_dict([], _RecDict, _VarDict, Acc) -> - orddict:from_list(Acc). + TypeNames, RecDict, VarDict, Acc) -> + {T, R1} = t_from_form(Type, TypeNames, RecDict, VarDict), + NewAcc = [{Name, T}|Acc], + {D, R2} = build_field_dict(Left, TypeNames, RecDict, VarDict, NewAcc), + {D, R1 ++ R2}; +build_field_dict([], _TypeNames, _RecDict, _VarDict, Acc) -> + {orddict:from_list(Acc), []}. get_mod_record([{FieldName, DeclType}|Left1], [{FieldName, ModType}|Left2], Acc) -> @@ -3419,6 +3658,20 @@ get_mod_record(DeclFields, [], Acc) -> get_mod_record(_, [{FieldName2, _ModType}|_], _Acc) -> {error, FieldName2}. +fields_from_form([], _TypeNames, _RecDict, _VarDict) -> + {[], []}; +fields_from_form([{Name, Type}|Tail], TypeNames, RecDict, VarDict) -> + {T, R1} = t_from_form(Type, TypeNames, RecDict, VarDict), + {F, R2} = fields_from_form(Tail, TypeNames, RecDict, VarDict), + {[{Name, T}|F], R1 ++ R2}. + +list_from_form([], _TypeNames, _RecDict, _VarDict) -> + {[], []}; +list_from_form([H|Tail], TypeNames, RecDict, VarDict) -> + {T, R1} = t_from_form(H, TypeNames, RecDict, VarDict), + {L, R2} = list_from_form(Tail, TypeNames, RecDict, VarDict), + {[T|L], R1 ++ R2}. + -spec t_form_to_string(parse_form()) -> string(). t_form_to_string({var, _L, '_'}) -> "_"; @@ -3510,11 +3763,19 @@ any_none_or_unit([?unit|_]) -> true; any_none_or_unit([_|Left]) -> any_none_or_unit(Left); any_none_or_unit([]) -> false. --spec lookup_record(atom(), dict()) -> 'error' | {'ok', [{atom(), erl_type()}]}. +is_erl_type(?any) -> true; +is_erl_type(?none) -> true; +is_erl_type(?unit) -> true; +is_erl_type(#c{}) -> true; +is_erl_type(_) -> false. + +-spec lookup_record(atom(), dict()) -> + 'error' | {'ok', [{atom(), parse_form() | erl_type()}]}. lookup_record(Tag, RecDict) when is_atom(Tag) -> case dict:find({record, Tag}, RecDict) of - {ok, [{_Arity, Fields}]} -> {ok, Fields}; + {ok, [{_Arity, Fields}]} -> + {ok, Fields}; {ok, List} when is_list(List) -> %% This will have to do, since we do not know which record we %% are looking for. @@ -3547,6 +3808,9 @@ lookup_type(Name, RecDict) -> type_is_defined(TypeOrOpaque, Name, RecDict) -> dict:is_key({TypeOrOpaque, Name}, RecDict). +unfold(TypeName, TypeNames) -> + not lists:member(TypeName, TypeNames). + %% ----------------------------------- %% Set %% From b366554ee70509836d7550107163e14aed66c83f Mon Sep 17 00:00:00 2001 From: Kostis Sagonas Date: Sun, 14 Feb 2010 07:22:10 +0100 Subject: [PATCH 5/8] syntax_tools: Remove $Id$ annotations --- lib/syntax_tools/doc/Makefile | 2 -- lib/syntax_tools/examples/Makefile | 2 -- lib/syntax_tools/src/erl_comment_scan.erl | 3 +-- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/syntax_tools/doc/Makefile b/lib/syntax_tools/doc/Makefile index 27f32988c811..6afd16f669bb 100644 --- a/lib/syntax_tools/doc/Makefile +++ b/lib/syntax_tools/doc/Makefile @@ -13,8 +13,6 @@ # Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings # AB. All Rights Reserved.'' # -# $Id: Makefile,v 1.1.1.1 2004/10/04 13:56:14 richardc Exp $ -# include $(ERL_TOP)/make/target.mk include $(ERL_TOP)/make/$(TARGET)/otp.mk diff --git a/lib/syntax_tools/examples/Makefile b/lib/syntax_tools/examples/Makefile index a52a52a50c58..7cfe9185c265 100644 --- a/lib/syntax_tools/examples/Makefile +++ b/lib/syntax_tools/examples/Makefile @@ -13,8 +13,6 @@ # Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings # AB. All Rights Reserved.'' # -# $Id$ -# include $(ERL_TOP)/make/target.mk include $(ERL_TOP)/make/$(TARGET)/otp.mk diff --git a/lib/syntax_tools/src/erl_comment_scan.erl b/lib/syntax_tools/src/erl_comment_scan.erl index df1449da4e1d..22d677757072 100644 --- a/lib/syntax_tools/src/erl_comment_scan.erl +++ b/lib/syntax_tools/src/erl_comment_scan.erl @@ -14,8 +14,7 @@ %% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 %% USA %% -%% $Id$ -%% +%% ===================================================================== %% @copyright 1997-2006 Richard Carlsson %% @author Richard Carlsson %% @end From 649d313771dc4e53ddfa3ba61504743f38dc8cff Mon Sep 17 00:00:00 2001 From: Kostis Sagonas Date: Sun, 14 Feb 2010 07:25:03 +0100 Subject: [PATCH 6/8] syntax_tools: Support the --enable-native-libs configure option While at it, turn on some more warnings. --- lib/syntax_tools/src/Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/syntax_tools/src/Makefile b/lib/syntax_tools/src/Makefile index 62a24d98c02e..50369e633e90 100644 --- a/lib/syntax_tools/src/Makefile +++ b/lib/syntax_tools/src/Makefile @@ -23,7 +23,10 @@ RELSYSDIR = $(RELEASE_PATH)/lib/syntax_tools-$(VSN) EBIN = ../ebin -ERL_COMPILE_FLAGS += +warn_unused_vars +nowarn_shadow_vars +warn_unused_import +warn_obsolete_guard +ifeq ($(NATIVE_LIBS_ENABLED),yes) +ERL_COMPILE_FLAGS += +native +endif +ERL_COMPILE_FLAGS += +warn_unused_vars +nowarn_shadow_vars +warn_unused_import +warn_missing_spec +warn_untyped_record SOURCES=erl_syntax.erl erl_prettypr.erl erl_syntax_lib.erl \ erl_comment_scan.erl erl_recomment.erl erl_tidy.erl \ From 8b7dd064e2d44b600f05a9135aac08a539a19ffa Mon Sep 17 00:00:00 2001 From: Kostis Sagonas Date: Sun, 14 Feb 2010 07:32:29 +0100 Subject: [PATCH 7/8] syntax_tools: Add types and specs for most exported functions While at it, consistently replace "bool()" with "boolean()" in the Edoc specs. --- lib/syntax_tools/src/epp_dodger.erl | 71 ++- lib/syntax_tools/src/erl_comment_scan.erl | 15 +- lib/syntax_tools/src/erl_prettypr.erl | 55 +- lib/syntax_tools/src/erl_recomment.erl | 45 +- lib/syntax_tools/src/erl_syntax.erl | 582 ++++++++++++++++++++-- lib/syntax_tools/src/erl_syntax_lib.erl | 150 +++++- lib/syntax_tools/src/erl_tidy.erl | 32 +- lib/syntax_tools/src/igor.erl | 162 +++--- lib/syntax_tools/src/prettypr.erl | 74 ++- 9 files changed, 965 insertions(+), 221 deletions(-) diff --git a/lib/syntax_tools/src/epp_dodger.erl b/lib/syntax_tools/src/epp_dodger.erl index 7aef54957436..6b0f2034f85a 100644 --- a/lib/syntax_tools/src/epp_dodger.erl +++ b/lib/syntax_tools/src/epp_dodger.erl @@ -90,6 +90,9 @@ %% This is a so-called Erlang I/O ErrorInfo structure; see the {@link %% //stdlib/io} module for details. +-type errorinfo() :: term(). % {integer(), atom(), term()}. + +-type option() :: atom() | {atom(), term()}. %% ===================================================================== %% @spec parse_file(File) -> {ok, Forms} | {error, errorinfo()} @@ -98,6 +101,9 @@ %% %% @equiv parse_file(File, []) +-spec parse_file(file:filename()) -> + {'ok', erl_syntax:forms()} | {'error', errorinfo()}. + parse_file(File) -> parse_file(File, []). @@ -109,11 +115,11 @@ parse_file(File) -> %% @doc Reads and parses a file. If successful, `{ok, Forms}' %% is returned, where `Forms' is a list of abstract syntax %% trees representing the "program forms" of the file (cf. -%% `erl_syntax:is_form/1'). Otherwise, `{error, -%% errorinfo()}' is returned, typically if the file could not be -%% opened. Note that parse errors show up as error markers in the -%% returned list of forms; they do not cause this function to fail or -%% return `{error,errorinfo()}'. +%% `erl_syntax:is_form/1'). Otherwise, `{error, errorinfo()}' is +%% returned, typically if the file could not be opened. Note that +%% parse errors show up as error markers in the returned list of +%% forms; they do not cause this function to fail or return +%% `{error, errorinfo()}'. %% %% Options: %%
@@ -135,6 +141,9 @@ parse_file(File) -> %% @see quick_parse_file/1 %% @see erl_syntax:is_form/1 +-spec parse_file(file:filename(), [option()]) -> + {'ok', erl_syntax:forms()} | {'error', errorinfo()}. + parse_file(File, Options) -> parse_file(File, fun parse/3, Options). @@ -144,6 +153,9 @@ parse_file(File, Options) -> %% %% @equiv quick_parse_file(File, []) +-spec quick_parse_file(file:filename()) -> + {'ok', erl_syntax:forms()} | {'error', errorinfo()}. + quick_parse_file(File) -> quick_parse_file(File, []). @@ -167,6 +179,9 @@ quick_parse_file(File) -> %% @see quick_parse/2 %% @see parse_file/2 +-spec quick_parse_file(file:filename(), [option()]) -> + {'ok', erl_syntax:forms()} | {'error', errorinfo()}. + quick_parse_file(File, Options) -> parse_file(File, fun quick_parse/3, Options ++ [no_fail]). @@ -185,6 +200,8 @@ parse_file(File, Parser, Options) -> %% @spec parse(IODevice) -> {ok, Forms} | {error, errorinfo()} %% @equiv parse(IODevice, 1) +-spec parse(file:io_device()) -> {'ok', erl_syntax:forms()}. + parse(Dev) -> parse(Dev, 1). @@ -196,6 +213,8 @@ parse(Dev) -> %% @equiv parse(IODevice, StartLine, []) %% @see parse/1 +-spec parse(file:io_device(), integer()) -> {'ok', erl_syntax:forms()}. + parse(Dev, L) -> parse(Dev, L, []). @@ -216,12 +235,18 @@ parse(Dev, L) -> %% @see parse_form/2 %% @see quick_parse/3 +-spec parse(file:io_device(), integer(), [option()]) -> + {'ok', erl_syntax:forms()}. + parse(Dev, L0, Options) -> parse(Dev, L0, fun parse_form/3, Options). %% @spec quick_parse(IODevice) -> {ok, Forms} | {error, errorinfo()} %% @equiv quick_parse(IODevice, 1) +-spec quick_parse(file:io_device()) -> + {'ok', erl_syntax:forms()}. + quick_parse(Dev) -> quick_parse(Dev, 1). @@ -234,6 +259,9 @@ quick_parse(Dev) -> %% @equiv quick_parse(IODevice, StartLine, []) %% @see quick_parse/1 +-spec quick_parse(file:io_device(), integer()) -> + {'ok', erl_syntax:forms()}. + quick_parse(Dev, L) -> quick_parse(Dev, L, []). @@ -252,6 +280,9 @@ quick_parse(Dev, L) -> %% @see quick_parse_form/2 %% @see parse/3 +-spec quick_parse(file:io_device(), integer(), [option()]) -> + {'ok', erl_syntax:forms()}. + quick_parse(Dev, L0, Options) -> parse(Dev, L0, fun quick_parse_form/3, Options). @@ -284,6 +315,10 @@ parse(Dev, L0, Fs, Parser, Options) -> %% %% @see quick_parse_form/2 +-spec parse_form(file:io_device(), integer()) -> + {'ok', erl_syntax:forms(), integer()} + | {'eof', integer()} | {'error', errorinfo(), integer()}. + parse_form(Dev, L0) -> parse_form(Dev, L0, []). @@ -310,6 +345,10 @@ parse_form(Dev, L0) -> %% @see parse_form/2 %% @see quick_parse_form/3 +-spec parse_form(file:io_device(), integer(), [option()]) -> + {'ok', erl_syntax:forms(), integer()} + | {'eof', integer()} | {'error', errorinfo(), integer()}. + parse_form(Dev, L0, Options) -> parse_form(Dev, L0, fun normal_parser/2, Options). @@ -326,6 +365,10 @@ parse_form(Dev, L0, Options) -> %% %% @see parse_form/2 +-spec quick_parse_form(file:io_device(), integer()) -> + {'ok', erl_syntax:forms(), integer()} + | {'eof', integer()} | {'error', errorinfo(), integer()}. + quick_parse_form(Dev, L0) -> quick_parse_form(Dev, L0, []). @@ -347,6 +390,10 @@ quick_parse_form(Dev, L0) -> %% @see quick_parse_form/2 %% @see parse_form/3 +-spec quick_parse_form(file:io_device(), integer(), [option()]) -> + {'ok', erl_syntax:forms(), integer()} + | {'eof', integer()} | {'error', errorinfo(), integer()}. + quick_parse_form(Dev, L0, Options) -> parse_form(Dev, L0, fun quick_parser/2, Options). @@ -751,11 +798,13 @@ fix_define([{atom, L, ?pp_form}, {'(', _}, {')', _}, {'->', _}, fix_define(_Ts) -> error. -%% @spec (Tokens::[term()]) -> string() +%% @spec tokens_to_string(Tokens::[term()]) -> string() %% %% @doc Generates a string corresponding to the given token sequence. %% The string can be re-tokenized to yield the same token list again. +-spec tokens_to_string([term()]) -> string(). + tokens_to_string([{atom,_,A} | Ts]) -> io_lib:write_atom(A) ++ " " ++ tokens_to_string(Ts); tokens_to_string([{string, _, S} | Ts]) -> @@ -764,21 +813,23 @@ tokens_to_string([{float, _, F} | Ts]) -> float_to_list(F) ++ " " ++ tokens_to_string(Ts); tokens_to_string([{integer, _, N} | Ts]) -> integer_to_list(N) ++ " " ++ tokens_to_string(Ts); -tokens_to_string([{var,_,A} | Ts]) -> +tokens_to_string([{var, _, A} | Ts]) -> atom_to_list(A) ++ " " ++ tokens_to_string(Ts); -tokens_to_string([{dot,_} | Ts]) -> +tokens_to_string([{dot, _} | Ts]) -> ".\n" ++ tokens_to_string(Ts); -tokens_to_string([{A,_} | Ts]) -> +tokens_to_string([{A, _} | Ts]) -> atom_to_list(A) ++ " " ++ tokens_to_string(Ts); tokens_to_string([]) -> "". -%% @spec (Descriptor::term()) -> string() +%% @spec format_error(Descriptor::term()) -> string() %% @hidden %% @doc Callback function for formatting error descriptors. Not for %% normal use. +-spec format_error(term()) -> string(). + format_error(macro_args) -> errormsg("macro call missing end parenthesis"); format_error({unknown, Reason}) -> diff --git a/lib/syntax_tools/src/erl_comment_scan.erl b/lib/syntax_tools/src/erl_comment_scan.erl index 22d677757072..09ce21a42823 100644 --- a/lib/syntax_tools/src/erl_comment_scan.erl +++ b/lib/syntax_tools/src/erl_comment_scan.erl @@ -27,6 +27,11 @@ -export([file/1, join_lines/1, scan_lines/1, string/1]). +%% ===================================================================== + +-type comment() :: {integer(), integer(), integer(), [string()]}. +-type commentLine() :: {integer(), integer(), integer(), string()}. + %% ===================================================================== %% @spec file(FileName::file:filename()) -> [Comment] %% @@ -58,6 +63,8 @@ %% error occurred, where `Reason' is an atom corresponding to %% a Posix error code; see the module {@link //kernel/file} for details. +-spec file(file:filename()) -> [comment()]. + file(Name) -> Name1 = filename(Name), case catch {ok, file:read_file(Name1)} of @@ -79,7 +86,7 @@ file(Name) -> %% ===================================================================== -%% string(string()) -> [Comment] +%% @spec string(string()) -> [Comment] %% %% Comment = {Line, Column, Indentation, Text} %% Line = integer() @@ -93,6 +100,8 @@ file(Name) -> %% %% @see file/1 +-spec string(string()) -> [comment()]. + string(Text) -> lists:reverse(join_lines(scan_lines(Text))). @@ -115,6 +124,8 @@ string(Text) -> %% to (but not including) the line-terminating newline. For details on %% `Line', `Column' and `Indent', see {@link file/1}. +-spec scan_lines(string()) -> [commentLine()]. + scan_lines(Text) -> scan_lines(Text, 1, 0, 0, []). @@ -230,6 +241,8 @@ scan_char([], _L, _Col, Ack) -> %% %% @see scan_lines/1 +-spec join_lines([commentLine()]) -> [comment()]. + join_lines([{L, Col, Ind, Txt} | Lines]) -> join_lines(Lines, [Txt], L, Col, Ind); join_lines([]) -> diff --git a/lib/syntax_tools/src/erl_prettypr.erl b/lib/syntax_tools/src/erl_prettypr.erl index 8d2f4facea79..606441bcf12d 100644 --- a/lib/syntax_tools/src/erl_prettypr.erl +++ b/lib/syntax_tools/src/erl_prettypr.erl @@ -48,25 +48,30 @@ -define(NOUSER, undefined). -define(NOHOOK, none). --record(ctxt, {prec = 0, - sub_indent = 2, - break_indent = 4, - clause = undefined, - hook = ?NOHOOK, - paper = ?PAPER, - ribbon = ?RIBBON, - user = ?NOUSER}). +-type hook() :: 'none' + | fun((erl_syntax:syntaxTree(), _, _) -> prettypr:document()). +-record(ctxt, {prec = 0 :: integer(), + sub_indent = 2 :: non_neg_integer(), + break_indent = 4 :: non_neg_integer(), + clause = undefined, + hook = ?NOHOOK :: hook(), + paper = ?PAPER :: integer(), + ribbon = ?RIBBON :: integer(), + user = ?NOUSER :: term()}). +-type context() :: #ctxt{}. %% ===================================================================== %% The following functions examine and modify contexts: -%% @spec (context()) -> context() +%% @spec (context()) -> integer() %% @doc Returns the operator precedence field of the prettyprinter %% context. %% %% @see set_ctxt_precedence/2 +-spec get_ctxt_precedence(context()) -> integer(). + get_ctxt_precedence(Ctxt) -> Ctxt#ctxt.prec. @@ -78,6 +83,8 @@ get_ctxt_precedence(Ctxt) -> %% @see //stdlib/erl_parse %% @see get_ctxt_precedence/1 +-spec set_ctxt_precedence(context(), integer()) -> context(). + set_ctxt_precedence(Ctxt, Prec) -> set_prec(Ctxt, Prec). @@ -91,6 +98,8 @@ reset_prec(Ctxt) -> %% @doc Returns the paper widh field of the prettyprinter context. %% @see set_ctxt_paperwidth/2 +-spec get_ctxt_paperwidth(context()) -> integer(). + get_ctxt_paperwidth(Ctxt) -> Ctxt#ctxt.paper. @@ -104,6 +113,8 @@ get_ctxt_paperwidth(Ctxt) -> %% %% @see get_ctxt_paperwidth/1 +-spec set_ctxt_paperwidth(context(), integer()) -> context(). + set_ctxt_paperwidth(Ctxt, W) -> Ctxt#ctxt{paper = W}. @@ -111,6 +122,8 @@ set_ctxt_paperwidth(Ctxt, W) -> %% @doc Returns the line widh field of the prettyprinter context. %% @see set_ctxt_linewidth/2 +-spec get_ctxt_linewidth(context()) -> integer(). + get_ctxt_linewidth(Ctxt) -> Ctxt#ctxt.ribbon. @@ -124,6 +137,8 @@ get_ctxt_linewidth(Ctxt) -> %% %% @see get_ctxt_linewidth/1 +-spec set_ctxt_linewidth(context(), integer()) -> context(). + set_ctxt_linewidth(Ctxt, W) -> Ctxt#ctxt{ribbon = W}. @@ -131,6 +146,8 @@ set_ctxt_linewidth(Ctxt, W) -> %% @doc Returns the hook function field of the prettyprinter context. %% @see set_ctxt_hook/2 +-spec get_ctxt_hook(context()) -> hook(). + get_ctxt_hook(Ctxt) -> Ctxt#ctxt.hook. @@ -138,6 +155,8 @@ get_ctxt_hook(Ctxt) -> %% @doc Updates the hook function field of the prettyprinter context. %% @see get_ctxt_hook/1 +-spec set_ctxt_hook(context(), hook()) -> context(). + set_ctxt_hook(Ctxt, Hook) -> Ctxt#ctxt{hook = Hook}. @@ -145,6 +164,8 @@ set_ctxt_hook(Ctxt, Hook) -> %% @doc Returns the user data field of the prettyprinter context. %% @see set_ctxt_user/2 +-spec get_ctxt_user(context()) -> term(). + get_ctxt_user(Ctxt) -> Ctxt#ctxt.user. @@ -152,6 +173,8 @@ get_ctxt_user(Ctxt) -> %% @doc Updates the user data field of the prettyprinter context. %% @see get_ctxt_user/1 +-spec set_ctxt_user(context(), term()) -> context(). + set_ctxt_user(Ctxt, X) -> Ctxt#ctxt{user = X}. @@ -160,6 +183,8 @@ set_ctxt_user(Ctxt, X) -> %% @spec format(Tree::syntaxTree()) -> string() %% @equiv format(Tree, []) +-spec format(erl_syntax:syntaxTree()) -> string(). + format(Node) -> format(Node, []). @@ -237,6 +262,8 @@ format(Node) -> %% @see get_ctxt_user/1 %% @see set_ctxt_user/2 +-spec format(erl_syntax:syntaxTree(), [term()]) -> string(). + format(Node, Options) -> W = proplists:get_value(paper, Options, ?PAPER), L = proplists:get_value(ribbon, Options, ?RIBBON), @@ -247,6 +274,8 @@ format(Node, Options) -> %% @spec best(Tree::syntaxTree()) -> empty | document() %% @equiv best(Tree, []) +-spec best(erl_syntax:syntaxTree()) -> 'empty' | prettypr:document(). + best(Node) -> best(Node, []). @@ -266,6 +295,8 @@ best(Node) -> %% @see format/2 %% @see prettypr:best/3 +-spec best(erl_syntax:syntaxTree(), [term()]) -> 'empty' | prettypr:document(). + best(Node, Options) -> W = proplists:get_value(paper, Options, ?PAPER), L = proplists:get_value(ribbon, Options, ?RIBBON), @@ -276,6 +307,8 @@ best(Node, Options) -> %% @spec layout(Tree::syntaxTree()) -> document() %% @equiv layout(Tree, []) +-spec layout(erl_syntax:syntaxTree()) -> prettypr:document(). + layout(Node) -> layout(Node, []). @@ -300,6 +333,8 @@ layout(Node) -> %% @see format/2 %% @see layout/1 +-spec layout(erl_syntax:syntaxTree(), [term()]) -> prettypr:document(). + layout(Node, Options) -> lay(Node, #ctxt{hook = proplists:get_value(hook, Options, ?NOHOOK), @@ -593,7 +628,7 @@ lay_2(Node, Ctxt) -> fun_expr -> Ctxt1 = reset_prec(Ctxt), D = lay_clauses(erl_syntax:fun_expr_clauses(Node), - fun_expr, Ctxt1), + fun_expr, Ctxt1), sep([follow(text("fun"), D, Ctxt1#ctxt.sub_indent), text("end")]); diff --git a/lib/syntax_tools/src/erl_recomment.erl b/lib/syntax_tools/src/erl_recomment.erl index 62ec7da200f8..145bbc6f37bb 100644 --- a/lib/syntax_tools/src/erl_recomment.erl +++ b/lib/syntax_tools/src/erl_recomment.erl @@ -47,6 +47,9 @@ %% comments. Comments within function definitions or declarations %% ("forms") are simply ignored. +-spec quick_recomment_forms(erl_syntax:forms(), [erl_comment_scan:comment()]) -> + erl_syntax:syntaxTree(). + quick_recomment_forms(Tree, Cs) -> recomment_forms(Tree, Cs, false). @@ -109,6 +112,9 @@ quick_recomment_forms(Tree, Cs) -> %% @see recomment_tree/2 %% @see quick_recomment_forms/2 +-spec recomment_forms(erl_syntax:forms(), [erl_comment_scan:comment()]) -> + erl_syntax:syntaxTree(). + recomment_forms(Tree, Cs) -> recomment_forms(Tree, Cs, true). @@ -209,7 +215,7 @@ comment_delta(Text) -> %% the source file itself, but have been included by preprocessing. This %% way, comments will not be inserted into such parts by mistake. --record(filter, {file = undefined, line = 0}). +-record(filter, {file = undefined, line = 0 :: integer()}). filter_forms(Fs) -> filter_forms(Fs, false, #filter{}). @@ -330,6 +336,9 @@ check_file_attr_2(L) -> %% %% @see recomment_forms/2 +-spec recomment_tree(erl_syntax:syntaxTree(), [erl_comment_scan:comment()]) -> + {erl_syntax:syntaxTree(), [erl_comment_scan:comment()]}. + recomment_tree(Tree, Cs) -> {Tree1, Cs1} = insert_comments(Cs, build_tree(Tree)), {revert_tree(Tree1), Cs1}. @@ -592,23 +601,23 @@ expand_comment(C) -> %% syntax tree for any such tree that can have no subtrees, i.e., such %% that `erl_syntax:is_leaf' yields `true'. --record(leaf, {min = 0, - max = 0, - precomments = [], - postcomments = [], - value}). - --record(tree, {min = 0, - max = 0, - type, - attrs, - precomments = [], - postcomments = [], - subtrees = []}). - --record(list, {min = 0, - max = 0, - subtrees = []}). +-record(leaf, {min = 0 :: integer(), + max = 0 :: integer(), + precomments = [] :: [erl_syntax:syntaxTree()], + postcomments = [] :: [erl_syntax:syntaxTree()], + value :: erl_syntax:syntaxTree()}). + +-record(tree, {min = 0 :: integer(), + max = 0 :: integer(), + type :: atom(), + attrs :: erl_syntax:syntaxTreeAttributes(), + precomments = [] :: [erl_syntax:syntaxTree()], + postcomments = [] :: [erl_syntax:syntaxTree()], + subtrees = [] :: [erl_syntax:syntaxTree()]}). + +-record(list, {min = 0 :: integer(), + max = 0 :: integer(), + subtrees = [] :: [erl_syntax:syntaxTree()]}). leaf_node(Min, Max, Value) -> #leaf{min = Min, diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl index 6ceb3ddcaf35..9a2967d55023 100644 --- a/lib/syntax_tools/src/erl_syntax.erl +++ b/lib/syntax_tools/src/erl_syntax.erl @@ -343,8 +343,8 @@ %% %% type(Com) = comment --record(com, {pre = [], - post = []}). +-record(com, {pre = [] :: [syntaxTree()], + post = [] :: [syntaxTree()]}). %% `attr' records store node attributes as an aggregate. %% @@ -357,9 +357,10 @@ %% where `Pos' `Ann' and `Comments' are the corresponding values of a %% `tree' or `wrapper' record. --record(attr, {pos = 0, - ann = [], - com = none}). +-record(attr, {pos = 0 :: term(), + ann = [] :: [term()], + com = none :: 'none' | #com{}}). +-type syntaxTreeAttributes() :: #attr{}. %% `tree' records represent new-form syntax tree nodes. %% @@ -371,9 +372,9 @@ %% %% is_tree(Tree) = true --record(tree, {type, +-record(tree, {type :: atom(), attr = #attr{} :: #attr{}, - data}). + data :: term()}). %% `wrapper' records are used for attaching new-form node information to %% `erl_parse' trees. @@ -386,10 +387,13 @@ %% %% is_tree(Wrapper) = false --record(wrapper, {type, +-record(wrapper, {type :: atom(), attr = #attr{} :: #attr{}, - tree}). + tree :: term()}). +%% ===================================================================== + +-type syntaxTree() :: #tree{} | #wrapper{} | tuple(). % XXX: refine %% ===================================================================== %% @@ -532,6 +536,8 @@ %% @see variable/1 %% @see warning_marker/1 +-spec type(syntaxTree()) -> atom(). + type(#tree{type = T}) -> T; type(#wrapper{type = T}) -> @@ -599,7 +605,7 @@ type(Node) -> %% ===================================================================== -%% @spec is_leaf(Node::syntaxTree()) -> bool() +%% @spec is_leaf(Node::syntaxTree()) -> boolean() %% %% @doc Returns true if Node is a leaf node, %% otherwise false. The currently recognised leaf node @@ -635,6 +641,8 @@ type(Node) -> %% @see type/1 %% @see is_literal/1 +-spec is_leaf(syntaxTree()) -> boolean(). + is_leaf(Node) -> case type(Node) of atom -> true; @@ -657,7 +665,7 @@ is_leaf(Node) -> %% ===================================================================== -%% @spec is_form(Node::syntaxTree()) -> bool() +%% @spec is_form(Node::syntaxTree()) -> boolean() %% %% @doc Returns true if Node is a syntax tree %% representing a so-called "source code form", otherwise @@ -688,6 +696,8 @@ is_leaf(Node) -> %% @see rule/2 %% @see warning_marker/1 +-spec is_form(syntaxTree()) -> boolean(). + is_form(Node) -> case type(Node) of attribute -> true; @@ -722,6 +732,8 @@ is_form(Node) -> %% number *of the error descriptor*; this is all handled transparently %% by `get_pos' and `set_pos'. +-spec get_pos(syntaxTree()) -> term(). + get_pos(#tree{attr = Attr}) -> Attr#attr.pos; get_pos(#wrapper{attr = Attr}) -> @@ -745,6 +757,8 @@ get_pos(Node) -> %% @see get_pos/1 %% @see copy_pos/2 +-spec set_pos(syntaxTree(), term()) -> syntaxTree(). + set_pos(Node, Pos) -> case Node of #tree{attr = Attr} -> @@ -771,6 +785,8 @@ set_pos(Node, Pos) -> %% @see get_pos/1 %% @see set_pos/2 +-spec copy_pos(syntaxTree(), syntaxTree()) -> syntaxTree(). + copy_pos(Source, Target) -> set_pos(Target, get_pos(Source)). @@ -818,6 +834,8 @@ set_com(Node, Com) -> %% @see get_postcomments/1 %% @see get_attrs/1 +-spec get_precomments(syntaxTree()) -> [syntaxTree()]. + get_precomments(#tree{attr = Attr}) -> get_precomments_1(Attr); get_precomments(#wrapper{attr = Attr}) -> get_precomments_1(Attr); get_precomments(_) -> []. @@ -842,6 +860,8 @@ get_precomments_1(#attr{com = #com{pre = Cs}}) -> Cs. %% @see remove_comments/1 %% @see join_comments/2 +-spec set_precomments(syntaxTree(), [syntaxTree()]) -> syntaxTree(). + set_precomments(Node, Cs) -> case Node of #tree{attr = Attr} -> @@ -875,6 +895,8 @@ set_precomments_1(#attr{com = Com} = Attr, Cs) -> %% @see add_postcomments/2 %% @see join_comments/2 +-spec add_precomments([syntaxTree()], syntaxTree()) -> syntaxTree(). + add_precomments(Cs, Node) -> case Node of #tree{attr = Attr} -> @@ -916,6 +938,8 @@ add_precomments_1(Cs, #attr{com = Com} = Attr) -> %% @see get_precomments/1 %% @see get_attrs/1 +-spec get_postcomments(syntaxTree()) -> [syntaxTree()]. + get_postcomments(#tree{attr = Attr}) -> get_postcomments_1(Attr); get_postcomments(#wrapper{attr = Attr}) -> get_postcomments_1(Attr); get_postcomments(_) -> []. @@ -940,6 +964,8 @@ get_postcomments_1(#attr{com = #com{post = Cs}}) -> Cs. %% @see remove_comments/1 %% @see join_comments/2 +-spec set_postcomments(syntaxTree(), [syntaxTree()]) -> syntaxTree(). + set_postcomments(Node, Cs) -> case Node of #tree{attr = Attr} -> @@ -973,6 +999,8 @@ set_postcomments_1(#attr{com = Com} = Attr, Cs) -> %% @see add_precomments/2 %% @see join_comments/2 +-spec add_postcomments([syntaxTree()], syntaxTree()) -> syntaxTree(). + add_postcomments(Cs, Node) -> case Node of #tree{attr = Attr} -> @@ -990,7 +1018,7 @@ add_postcomments_1(Cs, #attr{com = Com} = Attr) -> %% ===================================================================== -%% @spec has_comments(Node::syntaxTree()) -> bool() +%% @spec has_comments(Node::syntaxTree()) -> boolean() %% %% @doc Yields false if the node has no associated %% comments, and true otherwise. @@ -1003,6 +1031,8 @@ add_postcomments_1(Cs, #attr{com = Com} = Attr) -> %% @see get_postcomments/1 %% @see remove_comments/1 +-spec has_comments(syntaxTree()) -> boolean(). + has_comments(#tree{attr = Attr}) -> case Attr#attr.com of none -> false; @@ -1030,6 +1060,8 @@ has_comments(_) -> false. %% @see set_precomments/2 %% @see set_postcomments/2 +-spec remove_comments(syntaxTree()) -> syntaxTree(). + remove_comments(Node) -> case Node of #tree{attr = Attr} -> @@ -1059,6 +1091,8 @@ remove_comments(Node) -> %% @see set_precomments/2 %% @see set_postcomments/2 +-spec copy_comments(syntaxTree(), syntaxTree()) -> syntaxTree(). + copy_comments(Source, Target) -> set_com(Target, get_com(Source)). @@ -1081,6 +1115,8 @@ copy_comments(Source, Target) -> %% @see add_precomments/2 %% @see add_postcomments/2 +-spec join_comments(syntaxTree(), syntaxTree()) -> syntaxTree(). + join_comments(Source, Target) -> add_postcomments( get_postcomments(Source), @@ -1097,6 +1133,8 @@ join_comments(Source, Target) -> %% @see set_ann/2 %% @see get_attrs/1 +-spec get_ann(syntaxTree()) -> [term()]. + get_ann(#tree{attr = Attr}) -> Attr#attr.ann; get_ann(#wrapper{attr = Attr}) -> Attr#attr.ann; get_ann(_) -> []. @@ -1113,6 +1151,8 @@ get_ann(_) -> []. %% @see add_ann/2 %% @see copy_ann/2 +-spec set_ann(syntaxTree(), [term()]) -> syntaxTree(). + set_ann(Node, As) -> case Node of #tree{attr = Attr} -> @@ -1138,6 +1178,8 @@ set_ann(Node, As) -> %% @see get_ann/1 %% @see set_ann/2 +-spec add_ann(term(), syntaxTree()) -> syntaxTree(). + add_ann(A, Node) -> case Node of #tree{attr = Attr} -> @@ -1164,6 +1206,8 @@ add_ann(A, Node) -> %% @see get_ann/1 %% @see set_ann/2 +-spec copy_ann(syntaxTree(), syntaxTree()) -> syntaxTree(). + copy_ann(Source, Target) -> set_ann(Target, get_ann(Source)). @@ -1192,6 +1236,8 @@ copy_ann(Source, Target) -> %% @see get_precomments/1 %% @see get_postcomments/1 +-spec get_attrs(syntaxTree()) -> syntaxTreeAttributes(). + get_attrs(#tree{attr = Attr}) -> Attr; get_attrs(#wrapper{attr = Attr}) -> Attr; get_attrs(Node) -> #attr{pos = get_pos(Node), @@ -1209,6 +1255,8 @@ get_attrs(Node) -> #attr{pos = get_pos(Node), %% @see get_attrs/1 %% @see copy_attrs/2 +-spec set_attrs(syntaxTree(), syntaxTreeAttributes()) -> syntaxTree(). + set_attrs(Node, Attr) -> case Node of #tree{} -> @@ -1233,6 +1281,8 @@ set_attrs(Node, Attr) -> %% @see get_attrs/1 %% @see set_attrs/2 +-spec copy_attrs(syntaxTree(), syntaxTree()) -> syntaxTree(). + copy_attrs(S, T) -> set_attrs(T, get_attrs(S)). @@ -1241,6 +1291,8 @@ copy_attrs(S, T) -> %% @spec comment(Strings) -> syntaxTree() %% @equiv comment(none, Strings) +-spec comment([string()]) -> syntaxTree(). + comment(Strings) -> comment(none, Strings). @@ -1268,7 +1320,9 @@ comment(Strings) -> %% @see comment/1 %% @see is_form/1 --record(comment, {pad, text}). +-type padding() :: 'none' | integer(). + +-record(comment, {pad :: padding(), text :: [string()]}). %% type(Node) = comment %% data(Node) = #comment{pad :: Padding, text :: Strings} @@ -1276,6 +1330,8 @@ comment(Strings) -> %% Padding = none | integer() %% Strings = [string()] +-spec comment(padding(), [string()]) -> syntaxTree(). + comment(Pad, Strings) -> tree(comment, #comment{pad = Pad, text = Strings}). @@ -1287,6 +1343,8 @@ comment(Pad, Strings) -> %% %% @see comment/2 +-spec comment_text(syntaxTree()) -> [string()]. + comment_text(Node) -> (data(Node))#comment.text. @@ -1300,6 +1358,8 @@ comment_text(Node) -> %% %% @see comment/2 +-spec comment_padding(syntaxTree()) -> padding(). + comment_padding(Node) -> (data(Node))#comment.pad. @@ -1333,6 +1393,8 @@ comment_padding(Node) -> %% Form = syntaxTree() %% is_form(Form) = true +-spec form_list([syntaxTree()]) -> syntaxTree(). + form_list(Forms) -> tree(form_list, Forms). @@ -1344,6 +1406,8 @@ form_list(Forms) -> %% %% @see form_list/1 +-spec form_list_elements(syntaxTree()) -> [syntaxTree()]. + form_list_elements(Node) -> data(Node). @@ -1358,6 +1422,8 @@ form_list_elements(Node) -> %% %% @see form_list/1 +-spec flatten_form_list(syntaxTree()) -> syntaxTree(). + flatten_form_list(Node) -> Fs = form_list_elements(Node), Fs1 = lists:reverse(flatten_form_list_1(Fs, [])), @@ -1389,6 +1455,8 @@ flatten_form_list_1([], As) -> %% type(Node) = text %% data(Node) = string() +-spec text(string()) -> syntaxTree(). + text(String) -> tree(text, String). @@ -1401,6 +1469,8 @@ text(String) -> %% %% @see text/1 +-spec text_string(syntaxTree()) -> string(). + text_string(Node) -> data(Node). @@ -1432,6 +1502,8 @@ text_string(Node) -> %% %% Name = atom() \ '_' +-spec variable(atom() | string()) -> syntaxTree(). + variable(Name) when is_atom(Name) -> tree(variable, Name); variable(Name) -> @@ -1450,6 +1522,8 @@ revert_variable(Node) -> %% %% @see variable/1 +-spec variable_name(syntaxTree()) -> atom(). + variable_name(Node) -> case unwrap(Node) of {var, _, Name} -> @@ -1466,6 +1540,8 @@ variable_name(Node) -> %% %% @see variable/1 +-spec variable_literal(syntaxTree()) -> string(). + variable_literal(Node) -> case unwrap(Node) of {var, _, Name} -> @@ -1491,6 +1567,8 @@ variable_literal(Node) -> %% %% {var, Pos, '_'} +-spec underscore() -> syntaxTree(). + underscore() -> tree(underscore, []). @@ -1518,6 +1596,8 @@ revert_underscore(Node) -> %% %% Value = integer() +-spec integer(integer()) -> syntaxTree(). + integer(Value) -> tree(integer, Value). @@ -1527,7 +1607,7 @@ revert_integer(Node) -> %% ===================================================================== -%% @spec is_integer(Node::syntaxTree(), Value::integer()) -> bool() +%% @spec is_integer(Node::syntaxTree(), Value::integer()) -> boolean() %% %% @doc Returns true if Node has type %% integer and represents Value, otherwise @@ -1535,6 +1615,8 @@ revert_integer(Node) -> %% %% @see integer/1 +-spec is_integer(syntaxTree(), integer()) -> boolean(). + is_integer(Node, Value) -> case unwrap(Node) of {integer, _, Value} -> @@ -1553,6 +1635,8 @@ is_integer(Node, Value) -> %% %% @see integer/1 +-spec integer_value(syntaxTree()) -> integer(). + integer_value(Node) -> case unwrap(Node) of {integer, _, Value} -> @@ -1570,6 +1654,8 @@ integer_value(Node) -> %% %% @see integer/1 +-spec integer_literal(syntaxTree()) -> string(). + integer_literal(Node) -> integer_to_list(integer_value(Node)). @@ -1600,6 +1686,8 @@ integer_literal(Node) -> %% overridden by the type conversion BIF of the same name, so always use %% `make_float/1' for local calls. +-spec float(float()) -> syntaxTree(). + float(Value) -> make_float(Value). @@ -1620,6 +1708,8 @@ revert_float(Node) -> %% %% @see float/1 +-spec float_value(syntaxTree()) -> float(). + float_value(Node) -> case unwrap(Node) of {float, _, Value} -> @@ -1637,6 +1727,8 @@ float_value(Node) -> %% %% @see float/1 +-spec float_literal(syntaxTree()) -> string(). + float_literal(Node) -> float_to_list(float_value(Node)). @@ -1667,6 +1759,8 @@ float_literal(Node) -> %% %% Code = integer() +-spec char(char()) -> syntaxTree(). + char(Char) -> tree(char, Char). @@ -1676,7 +1770,7 @@ revert_char(Node) -> %% ===================================================================== -%% @spec is_char(Node::syntaxTree(), Value::char()) -> bool() +%% @spec is_char(Node::syntaxTree(), Value::char()) -> boolean() %% %% @doc Returns true if Node has type %% char and represents Value, otherwise @@ -1684,6 +1778,8 @@ revert_char(Node) -> %% %% @see char/1 +-spec is_char(syntaxTree(), char()) -> boolean(). + is_char(Node, Value) -> case unwrap(Node) of {char, _, Value} -> @@ -1702,6 +1798,8 @@ is_char(Node, Value) -> %% %% @see char/1 +-spec char_value(syntaxTree()) -> char(). + char_value(Node) -> case unwrap(Node) of {char, _, Char} -> @@ -1719,6 +1817,8 @@ char_value(Node) -> %% %% @see char/1 +-spec char_literal(syntaxTree()) -> string(). + char_literal(Node) -> io_lib:write_char(char_value(Node)). @@ -1749,6 +1849,8 @@ char_literal(Node) -> %% %% Chars = string() +-spec string(string()) -> syntaxTree(). + string(String) -> tree(string, String). @@ -1758,7 +1860,7 @@ revert_string(Node) -> %% ===================================================================== -%% @spec is_string(Node::syntaxTree(), Value::string()) -> bool() +%% @spec is_string(Node::syntaxTree(), Value::string()) -> boolean() %% %% @doc Returns true if Node has type %% string and represents Value, otherwise @@ -1766,6 +1868,8 @@ revert_string(Node) -> %% %% @see string/1 +-spec is_string(syntaxTree(), string()) -> boolean(). + is_string(Node, Value) -> case unwrap(Node) of {string, _, Value} -> @@ -1784,6 +1888,8 @@ is_string(Node, Value) -> %% %% @see string/1 +-spec string_value(syntaxTree()) -> string(). + string_value(Node) -> case unwrap(Node) of {string, _, List} -> @@ -1801,6 +1907,8 @@ string_value(Node) -> %% %% @see string/1 +-spec string_literal(syntaxTree()) -> string(). + string_literal(Node) -> io_lib:write_string(string_value(Node)). @@ -1826,6 +1934,8 @@ string_literal(Node) -> %% %% Value = atom() +-spec atom(atom() | string()) -> syntaxTree(). + atom(Name) when is_atom(Name) -> tree(atom, Name); atom(Name) -> @@ -1837,7 +1947,7 @@ revert_atom(Node) -> %% ===================================================================== -%% @spec is_atom(Node::syntaxTree(), Value::atom()) -> bool() +%% @spec is_atom(Node::syntaxTree(), Value::atom()) -> boolean() %% %% @doc Returns true if Node has type %% atom and represents Value, otherwise @@ -1845,6 +1955,8 @@ revert_atom(Node) -> %% %% @see atom/1 +-spec is_atom(syntaxTree(), atom()) -> boolean(). + is_atom(Node, Value) -> case unwrap(Node) of {atom, _, Value} -> @@ -1863,6 +1975,8 @@ is_atom(Node, Value) -> %% %% @see atom/1 +-spec atom_value(syntaxTree()) -> atom(). + atom_value(Node) -> case unwrap(Node) of {atom, _, Name} -> @@ -1879,6 +1993,8 @@ atom_value(Node) -> %% %% @see atom/1 +-spec atom_name(syntaxTree()) -> string(). + atom_name(Node) -> atom_to_list(atom_value(Node)). @@ -1897,6 +2013,8 @@ atom_name(Node) -> %% @see atom/1 %% @see string/1 +-spec atom_literal(syntaxTree()) -> string(). + atom_literal(Node) -> io_lib:write_atom(atom_value(Node)). @@ -1925,6 +2043,8 @@ atom_literal(Node) -> %% %% Elements = [erl_parse()] +-spec tuple([syntaxTree()]) -> syntaxTree(). + tuple(List) -> tree(tuple, List). @@ -1941,6 +2061,8 @@ revert_tuple(Node) -> %% %% @see tuple/1 +-spec tuple_elements(syntaxTree()) -> [syntaxTree()]. + tuple_elements(Node) -> case unwrap(Node) of {tuple, _, List} -> @@ -1962,6 +2084,8 @@ tuple_elements(Node) -> %% @see tuple/1 %% @see tuple_elements/1 +-spec tuple_size(syntaxTree()) -> non_neg_integer(). + tuple_size(Node) -> length(tuple_elements(Node)). @@ -1970,6 +2094,8 @@ tuple_size(Node) -> %% @spec list(List) -> syntaxTree() %% @equiv list(List, none) +-spec list([syntaxTree()]) -> syntaxTree(). + list(List) -> list(List, none). @@ -2020,7 +2146,7 @@ list(List) -> %% @see compact_list/1 %% @see get_attrs/1 --record(list, {prefix, suffix}). +-record(list, {prefix :: [syntaxTree()], suffix :: 'none' | syntaxTree()}). %% type(Node) = list %% data(Node) = #list{prefix :: Elements, suffix :: Tail} @@ -2038,9 +2164,11 @@ list(List) -> %% ]' where the form of can depend on the %% structure of ; there is no fixed printed form. +-spec list([syntaxTree()], 'none' | syntaxTree()) -> syntaxTree(). + list([], none) -> nil(); -list(Elements, Tail) when Elements /= [] -> +list(Elements, Tail) when Elements =/= [] -> tree(list, #list{prefix = Elements, suffix = Tail}). revert_list(Node) -> @@ -2073,6 +2201,8 @@ revert_list(Node) -> %% %% {nil, Pos} +-spec nil() -> syntaxTree(). + nil() -> tree(nil). @@ -2092,6 +2222,8 @@ revert_nil(Node) -> %% %% @see list/2 +-spec list_prefix(syntaxTree()) -> [syntaxTree()]. + list_prefix(Node) -> case unwrap(Node) of {cons, _, Head, _} -> @@ -2102,7 +2234,7 @@ list_prefix(Node) -> %% ===================================================================== -%% @spec list_suffix(Node::syntaxTree()) -> none | syntaxTree() +%% @spec list_suffix(Node::syntaxTree()) -> none | syntaxTree() %% %% @doc Returns the suffix subtree of a list node, if one %% exists. If Node represents "[E1, ..., @@ -2121,6 +2253,8 @@ list_prefix(Node) -> %% @see nil/0 %% @see compact_list/1 +-spec list_suffix(syntaxTree()) -> 'none' | syntaxTree(). + list_suffix(Node) -> case unwrap(Node) of {cons, _, _, Tail} -> @@ -2158,6 +2292,8 @@ list_suffix(Node) -> %% @see list_head/1 %% @see list_tail/1 +-spec cons(syntaxTree(), syntaxTree()) -> syntaxTree(). + cons(Head, Tail) -> case type(Tail) of list -> @@ -2181,6 +2317,8 @@ cons(Head, Tail) -> %% @see list_tail/1 %% @see cons/2 +-spec list_head(syntaxTree()) -> syntaxTree(). + list_head(Node) -> hd(list_prefix(Node)). @@ -2202,6 +2340,8 @@ list_head(Node) -> %% @see list_head/1 %% @see cons/2 +-spec list_tail(syntaxTree()) -> syntaxTree(). + list_tail(Node) -> Tail = list_suffix(Node), case tl(list_prefix(Node)) of @@ -2217,7 +2357,7 @@ list_tail(Node) -> %% ===================================================================== -%% @spec is_list_skeleton(syntaxTree()) -> bool() +%% @spec is_list_skeleton(syntaxTree()) -> boolean() %% %% @doc Returns true if Node has type %% list or nil, otherwise false. @@ -2225,6 +2365,8 @@ list_tail(Node) -> %% @see list/2 %% @see nil/0 +-spec is_list_skeleton(syntaxTree()) -> boolean(). + is_list_skeleton(Node) -> case type(Node) of list -> true; @@ -2234,7 +2376,7 @@ is_list_skeleton(Node) -> %% ===================================================================== -%% @spec is_proper_list(Node::syntaxTree()) -> bool() +%% @spec is_proper_list(Node::syntaxTree()) -> boolean() %% %% @doc Returns true if Node represents a %% proper list, and false otherwise. A proper list is a @@ -2255,6 +2397,8 @@ is_list_skeleton(Node) -> %% %% @see list/2 +-spec is_proper_list(syntaxTree()) -> boolean(). + is_proper_list(Node) -> case type(Node) of list -> @@ -2284,6 +2428,8 @@ is_proper_list(Node) -> %% @see list/2 %% @see is_proper_list/1 +-spec list_elements(syntaxTree()) -> [syntaxTree()]. + list_elements(Node) -> lists:reverse(list_elements(Node, [])). @@ -2319,6 +2465,8 @@ list_elements(Node, As) -> %% @see is_proper_list/1 %% @see list_elements/1 +-spec list_length(syntaxTree()) -> non_neg_integer(). + list_length(Node) -> list_length(Node, 0). @@ -2354,6 +2502,8 @@ list_length(Node, A) -> %% @see list/2 %% @see compact_list/1 +-spec normalize_list(syntaxTree()) -> syntaxTree(). + normalize_list(Node) -> case type(Node) of list -> @@ -2391,6 +2541,8 @@ normalize_list_1(Es, Tail) -> %% @see list/2 %% @see normalize_list/1 +-spec compact_list(syntaxTree()) -> syntaxTree(). + compact_list(Node) -> case type(Node) of list -> @@ -2447,6 +2599,8 @@ compact_list(Node) -> %% See `binary_field' for documentation on `erl_parse' binary %% fields (or "elements"). +-spec binary([syntaxTree()]) -> syntaxTree(). + binary(List) -> tree(binary, List). @@ -2464,6 +2618,8 @@ revert_binary(Node) -> %% @see binary/1 %% @see binary_field/2 +-spec binary_fields(syntaxTree()) -> [syntaxTree()]. + binary_fields(Node) -> case unwrap(Node) of {bin, _, List} -> @@ -2477,6 +2633,8 @@ binary_fields(Node) -> %% @spec binary_field(Body) -> syntaxTree() %% @equiv binary_field(Body, []) +-spec binary_field(syntaxTree()) -> syntaxTree(). + binary_field(Body) -> binary_field(Body, []). @@ -2498,6 +2656,9 @@ binary_field(Body) -> %% @see binary_field/2 %% @see size_qualifier/2 +-spec binary_field(syntaxTree(), 'none' | syntaxTree(), [syntaxTree()]) -> + syntaxTree(). + binary_field(Body, none, Types) -> binary_field(Body, Types); binary_field(Body, Size, Types) -> @@ -2521,13 +2682,13 @@ binary_field(Body, Size, Types) -> %% @see binary_field_types/1 %% @see binary_field_size/1 --record(binary_field, {body, types}). +-record(binary_field, {body :: syntaxTree(), types :: [syntaxTree()]}). %% type(Node) = binary_field %% data(Node) = #binary_field{body :: Body, types :: Types} %% %% Body = syntaxTree() -%% Types = [Type] +%% Types = [syntaxTree()] %% %% `erl_parse' representation: %% @@ -2538,6 +2699,8 @@ binary_field(Body, Size, Types) -> %% TypeList = default | [Type] \ [] %% Type = atom() | {atom(), integer()} +-spec binary_field(syntaxTree(), [syntaxTree()]) -> syntaxTree(). + binary_field(Body, Types) -> tree(binary_field, #binary_field{body = Body, types = Types}). @@ -2569,6 +2732,8 @@ revert_binary_field(Node) -> %% %% @see binary_field/2 +-spec binary_field_body(syntaxTree()) -> syntaxTree(). + binary_field_body(Node) -> case unwrap(Node) of {bin_element, _, Body, Size, _} -> @@ -2592,6 +2757,8 @@ binary_field_body(Node) -> %% %% @see binary_field/2 +-spec binary_field_types(syntaxTree()) -> [syntaxTree()]. + binary_field_types(Node) -> case unwrap(Node) of {bin_element, Pos, _, _, Types} -> @@ -2620,6 +2787,8 @@ binary_field_types(Node) -> %% @see binary_field/2 %% @see binary_field/3 +-spec binary_field_size(syntaxTree()) -> 'none' | syntaxTree(). + binary_field_size(Node) -> case unwrap(Node) of {bin_element, _, _, Size, _} -> @@ -2649,13 +2818,15 @@ binary_field_size(Node) -> %% @see size_qualifier_body/1 %% @see size_qualifier_argument/1 --record(size_qualifier, {body, size}). +-record(size_qualifier, {body :: syntaxTree(), size :: syntaxTree()}). %% type(Node) = size_qualifier %% data(Node) = #size_qualifier{body :: Body, size :: Size} %% %% Body = Size = syntaxTree() +-spec size_qualifier(syntaxTree(), syntaxTree()) -> syntaxTree(). + size_qualifier(Body, Size) -> tree(size_qualifier, #size_qualifier{body = Body, size = Size}). @@ -2669,6 +2840,8 @@ size_qualifier(Body, Size) -> %% %% @see size_qualifier/2 +-spec size_qualifier_body(syntaxTree()) -> syntaxTree(). + size_qualifier_body(Node) -> (data(Node))#size_qualifier.body. @@ -2681,6 +2854,8 @@ size_qualifier_body(Node) -> %% %% @see size_qualifier/2 +-spec size_qualifier_argument(syntaxTree()) -> syntaxTree(). + size_qualifier_argument(Node) -> (data(Node))#size_qualifier.size. @@ -2714,6 +2889,8 @@ size_qualifier_argument(Node) -> %% Note that there is no position information for the node %% itself: `get_pos' and `set_pos' handle this as a special case. +-spec error_marker(term()) -> syntaxTree(). + error_marker(Error) -> tree(error_marker, Error). @@ -2731,6 +2908,8 @@ revert_error_marker(Node) -> %% %% @see error_marker/1 +-spec error_marker_info(syntaxTree()) -> term(). + error_marker_info(Node) -> case unwrap(Node) of {error, Error} -> @@ -2769,6 +2948,8 @@ error_marker_info(Node) -> %% Note that there is no position information for the node %% itself: `get_pos' and `set_pos' handle this as a special case. +-spec warning_marker(term()) -> syntaxTree(). + warning_marker(Warning) -> tree(warning_marker, Warning). @@ -2786,6 +2967,8 @@ revert_warning_marker(Node) -> %% %% @see warning_marker/1 +-spec warning_marker_info(syntaxTree()) -> term(). + warning_marker_info(Node) -> case unwrap(Node) of {warning, Error} -> @@ -2818,6 +3001,8 @@ warning_marker_info(Node) -> %% %% {eof, Pos} +-spec eof_marker() -> syntaxTree(). + eof_marker() -> tree(eof_marker). @@ -2830,6 +3015,8 @@ revert_eof_marker(Node) -> %% @spec attribute(Name) -> syntaxTree() %% @equiv attribute(Name, none) +-spec attribute(syntaxTree()) -> syntaxTree(). + attribute(Name) -> attribute(Name, none). @@ -2859,7 +3046,7 @@ attribute(Name) -> %% @see text/1 %% @see is_form/1 --record(attribute, {name, args}). +-record(attribute, {name :: syntaxTree(), args :: 'none' | [syntaxTree()]}). %% type(Node) = attribute %% data(Node) = #attribute{name :: Name, args :: Arguments} @@ -2922,6 +3109,8 @@ attribute(Name) -> %% %% Representing `-Name(Term).'. +-spec attribute(syntaxTree(), 'none' | [syntaxTree()]) -> syntaxTree(). + attribute(Name, Args) -> tree(attribute, #attribute{name = Name, args = Args}). @@ -3049,6 +3238,8 @@ revert_module_name(A) -> %% %% @see attribute/1 +-spec attribute_name(syntaxTree()) -> syntaxTree(). + attribute_name(Node) -> case unwrap(Node) of {attribute, Pos, Name, _} -> @@ -3071,6 +3262,8 @@ attribute_name(Node) -> %% %% @see attribute/1 +-spec attribute_arguments(syntaxTree()) -> none | [syntaxTree()]. + attribute_arguments(Node) -> case unwrap(Node) of {attribute, Pos, Name, Data} -> @@ -3141,13 +3334,15 @@ attribute_arguments(Node) -> %% @see arity_qualifier_body/1 %% @see arity_qualifier_argument/1 --record(arity_qualifier, {body, arity}). +-record(arity_qualifier, {body :: syntaxTree(), arity :: syntaxTree()}). %% type(Node) = arity_qualifier %% data(Node) = #arity_qualifier{body :: Body, arity :: Arity} %% %% Body = Arity = syntaxTree() +-spec arity_qualifier(syntaxTree(), syntaxTree()) -> syntaxTree(). + arity_qualifier(Body, Arity) -> tree(arity_qualifier, #arity_qualifier{body = Body, arity = Arity}). @@ -3161,6 +3356,8 @@ arity_qualifier(Body, Arity) -> %% %% @see arity_qualifier/2 +-spec arity_qualifier_body(syntaxTree()) -> syntaxTree(). + arity_qualifier_body(Node) -> (data(Node))#arity_qualifier.body. @@ -3173,6 +3370,8 @@ arity_qualifier_body(Node) -> %% %% @see arity_qualifier/2 +-spec arity_qualifier_argument(syntaxTree()) -> syntaxTree(). + arity_qualifier_argument(Node) -> (data(Node))#arity_qualifier.arity. @@ -3187,7 +3386,7 @@ arity_qualifier_argument(Node) -> %% @see module_qualifier_argument/1 %% @see module_qualifier_body/1 --record(module_qualifier, {module, body}). +-record(module_qualifier, {module :: syntaxTree(), body :: syntaxTree()}). %% type(Node) = module_qualifier %% data(Node) = #module_qualifier{module :: Module, body :: Body} @@ -3200,6 +3399,8 @@ arity_qualifier_argument(Node) -> %% %% Module = Arg = erl_parse() +-spec module_qualifier(syntaxTree(), syntaxTree()) -> syntaxTree(). + module_qualifier(Module, Body) -> tree(module_qualifier, #module_qualifier{module = Module, body = Body}). @@ -3219,6 +3420,8 @@ revert_module_qualifier(Node) -> %% %% @see module_qualifier/2 +-spec module_qualifier_argument(syntaxTree()) -> syntaxTree(). + module_qualifier_argument(Node) -> case unwrap(Node) of {remote, _, Module, _} -> @@ -3236,6 +3439,8 @@ module_qualifier_argument(Node) -> %% %% @see module_qualifier/2 +-spec module_qualifier_body(syntaxTree()) -> syntaxTree(). + module_qualifier_body(Node) -> case unwrap(Node) of {remote, _, _, Body} -> @@ -3267,6 +3472,8 @@ module_qualifier_body(Node) -> %% represents a Mnemosyne query record field access ('record_access'); %% see type/1 for details. +-spec qualified_name([syntaxTree()]) -> syntaxTree(). + qualified_name(Segments) -> tree(qualified_name, Segments). @@ -3283,6 +3490,8 @@ revert_qualified_name(Node) -> %% %% @see qualified_name/1 +-spec qualified_name_segments(syntaxTree()) -> [syntaxTree()]. + qualified_name_segments(Node) -> case unwrap(Node) of {record_field, _, _, _} = Node1 -> @@ -3314,6 +3523,9 @@ qualified_name_segments(Node) -> %% @see rule/2 -record(function, {name, clauses}). +%% XXX: This one is problematic because there is a tuple with the same +%% tag and size that comes from 'erl_parse' +%% -record(function, {name :: syntaxTree(), clauses :: [syntaxTree()]}). %% type(Node) = function %% data(Node) = #function{name :: Name, clauses :: Clauses} @@ -3340,6 +3552,8 @@ qualified_name_segments(Node) -> %% the integer `Arity'; see `clause' for documentation on %% `erl_parse' clauses. +-spec function(syntaxTree(), [syntaxTree()]) -> syntaxTree(). + function(Name, Clauses) -> tree(function, #function{name = Name, clauses = Clauses}). @@ -3363,6 +3577,8 @@ revert_function(Node) -> %% %% @see function/2 +-spec function_name(syntaxTree()) -> syntaxTree(). + function_name(Node) -> case unwrap(Node) of {function, Pos, Name, _, _} -> @@ -3380,6 +3596,8 @@ function_name(Node) -> %% %% @see function/2 +-spec function_clauses(syntaxTree()) -> [syntaxTree()]. + function_clauses(Node) -> case unwrap(Node) of {function, _, _, _, Clauses} -> @@ -3406,6 +3624,8 @@ function_clauses(Node) -> %% @see clause/3 %% @see clause_patterns/1 +-spec function_arity(syntaxTree()) -> arity(). + function_arity(Node) -> %% Note that this never accesses the arity field of `erl_parse' %% function nodes. @@ -3416,6 +3636,10 @@ function_arity(Node) -> %% @spec clause(Guard, Body) -> syntaxTree() %% @equiv clause([], Guard, Body) +-type guard() :: 'none' | syntaxTree() | [syntaxTree()] | [[syntaxTree()]]. + +-spec clause(guard(), [syntaxTree()]) -> syntaxTree(). + clause(Guard, Body) -> clause([], Guard, Body). @@ -3455,7 +3679,9 @@ clause(Guard, Body) -> %% @see clause_guard/1 %% @see clause_body/1 --record(clause, {patterns, guard, body}). +-record(clause, {patterns :: [syntaxTree()], + guard :: guard(), + body :: [syntaxTree()]}). %% type(Node) = clause %% data(Node) = #clause{patterns :: Patterns, guard :: Guard, @@ -3482,6 +3708,8 @@ clause(Guard, Body) -> %% versions, `Guard' was simply a list `[E1, ..., En]' of parse %% trees, which is equivalent to the new form `[[E1, ..., En]]'. +-spec clause([syntaxTree()], guard(), [syntaxTree()]) -> syntaxTree(). + clause(Patterns, Guard, Body) -> Guard1 = case Guard of [] -> @@ -3551,7 +3779,7 @@ fold_try_clause({clause, Pos, [P], Guard, Body}) -> unfold_try_clauses(Cs) -> [unfold_try_clause(C) || C <- Cs]. -unfold_try_clause({clause, Pos, [{tuple, _, [{atom,_,throw}, V, _]}], +unfold_try_clause({clause, Pos, [{tuple, _, [{atom, _, throw}, V, _]}], Guard, Body}) -> {clause, Pos, [V], Guard, Body}; unfold_try_clause({clause, Pos, [{tuple, _, [C, V, _]}], @@ -3567,6 +3795,8 @@ unfold_try_clause({clause, Pos, [{tuple, _, [C, V, _]}], %% %% @see clause/3 +-spec clause_patterns(syntaxTree()) -> [syntaxTree()]. + clause_patterns(Node) -> case unwrap(Node) of {clause, _, Patterns, _, _} -> @@ -3587,6 +3817,8 @@ clause_patterns(Node) -> %% %% @see clause/3 +-spec clause_guard(syntaxTree()) -> 'none' | syntaxTree(). + clause_guard(Node) -> case unwrap(Node) of {clause, _, _, Guard, _} -> @@ -3610,6 +3842,8 @@ clause_guard(Node) -> %% %% @see clause/3 +-spec clause_body(syntaxTree()) -> [syntaxTree()]. + clause_body(Node) -> case unwrap(Node) of {clause, _, _, _, Body} -> @@ -3632,6 +3866,8 @@ clause_body(Node) -> %% type(Node) = disjunction %% data(Node) = [syntaxTree()] +-spec disjunction([syntaxTree()]) -> syntaxTree(). + disjunction(Tests) -> tree(disjunction, Tests). @@ -3644,6 +3880,8 @@ disjunction(Tests) -> %% %% @see disjunction/1 +-spec disjunction_body(syntaxTree()) -> [syntaxTree()]. + disjunction_body(Node) -> data(Node). @@ -3661,6 +3899,8 @@ disjunction_body(Node) -> %% type(Node) = conjunction %% data(Node) = [syntaxTree()] +-spec conjunction([syntaxTree()]) -> syntaxTree(). + conjunction(Tests) -> tree(conjunction, Tests). @@ -3673,6 +3913,8 @@ conjunction(Tests) -> %% %% @see conjunction/1 +-spec conjunction_body(syntaxTree()) -> [syntaxTree()]. + conjunction_body(Node) -> data(Node). @@ -3694,6 +3936,8 @@ conjunction_body(Node) -> %% %% Expr = erl_parse() +-spec catch_expr(syntaxTree()) -> syntaxTree(). + catch_expr(Expr) -> tree(catch_expr, Expr). @@ -3710,6 +3954,8 @@ revert_catch_expr(Node) -> %% %% @see catch_expr/1 +-spec catch_expr_body(syntaxTree()) -> syntaxTree(). + catch_expr_body(Node) -> case unwrap(Node) of {'catch', _, Expr} -> @@ -3729,7 +3975,7 @@ catch_expr_body(Node) -> %% @see match_expr_pattern/1 %% @see match_expr_body/1 --record(match_expr, {pattern, body}). +-record(match_expr, {pattern :: syntaxTree(), body :: syntaxTree()}). %% type(Node) = match_expr %% data(Node) = #match_expr{pattern :: Pattern, body :: Body} @@ -3742,6 +3988,8 @@ catch_expr_body(Node) -> %% %% Pattern = Body = erl_parse() +-spec match_expr(syntaxTree(), syntaxTree()) -> syntaxTree(). + match_expr(Pattern, Body) -> tree(match_expr, #match_expr{pattern = Pattern, body = Body}). @@ -3759,6 +4007,8 @@ revert_match_expr(Node) -> %% %% @see match_expr/2 +-spec match_expr_pattern(syntaxTree()) -> syntaxTree(). + match_expr_pattern(Node) -> case unwrap(Node) of {match, _, Pattern, _} -> @@ -3775,6 +4025,8 @@ match_expr_pattern(Node) -> %% %% @see match_expr/2 +-spec match_expr_body(syntaxTree()) -> syntaxTree(). + match_expr_body(Node) -> case unwrap(Node) of {match, _, _, Body} -> @@ -3802,6 +4054,8 @@ match_expr_body(Node) -> %% type(Node) = operator %% data(Node) = atom() +-spec operator(atom() | string()) -> syntaxTree(). + operator(Name) when is_atom(Name) -> tree(operator, Name); operator(Name) -> @@ -3816,6 +4070,8 @@ operator(Name) -> %% %% @see operator/1 +-spec operator_name(syntaxTree()) -> atom(). + operator_name(Node) -> data(Node). @@ -3829,6 +4085,8 @@ operator_name(Node) -> %% %% @see operator/1 +-spec operator_literal(syntaxTree()) -> string(). + operator_literal(Node) -> atom_to_list(operator_name(Node)). @@ -3846,7 +4104,9 @@ operator_literal(Node) -> %% @see infix_expr_operator/1 %% @see prefix_expr/2 --record(infix_expr, {operator, left, right}). +-record(infix_expr, {operator :: syntaxTree(), + left :: syntaxTree(), + right :: syntaxTree()}). %% type(Node) = infix_expr %% data(Node) = #infix_expr{left :: Left, operator :: Operator, @@ -3861,6 +4121,8 @@ operator_literal(Node) -> %% Operator = atom() %% Left = Right = erl_parse() +-spec infix_expr(syntaxTree(), syntaxTree(), syntaxTree()) -> syntaxTree(). + infix_expr(Left, Operator, Right) -> tree(infix_expr, #infix_expr{operator = Operator, left = Left, right = Right}). @@ -3888,6 +4150,8 @@ revert_infix_expr(Node) -> %% %% @see infix_expr/3 +-spec infix_expr_left(syntaxTree()) -> syntaxTree(). + infix_expr_left(Node) -> case unwrap(Node) of {op, _, _, Left, _} -> @@ -3905,6 +4169,8 @@ infix_expr_left(Node) -> %% %% @see infix_expr/3 +-spec infix_expr_operator(syntaxTree()) -> syntaxTree(). + infix_expr_operator(Node) -> case unwrap(Node) of {op, Pos, Operator, _, _} -> @@ -3922,6 +4188,8 @@ infix_expr_operator(Node) -> %% %% @see infix_expr/3 +-spec infix_expr_right(syntaxTree()) -> syntaxTree(). + infix_expr_right(Node) -> case unwrap(Node) of {op, _, _, _, Right} -> @@ -3942,7 +4210,7 @@ infix_expr_right(Node) -> %% @see prefix_expr_operator/1 %% @see infix_expr/3 --record(prefix_expr, {operator, argument}). +-record(prefix_expr, {operator :: syntaxTree(), argument :: syntaxTree()}). %% type(Node) = prefix_expr %% data(Node) = #prefix_expr{operator :: Operator, @@ -3957,6 +4225,8 @@ infix_expr_right(Node) -> %% Operator = atom() %% Argument = erl_parse() +-spec prefix_expr(syntaxTree(), syntaxTree()) -> syntaxTree(). + prefix_expr(Operator, Argument) -> tree(prefix_expr, #prefix_expr{operator = Operator, argument = Argument}). @@ -3983,6 +4253,8 @@ revert_prefix_expr(Node) -> %% %% @see prefix_expr/2 +-spec prefix_expr_operator(syntaxTree()) -> syntaxTree(). + prefix_expr_operator(Node) -> case unwrap(Node) of {op, Pos, Operator, _} -> @@ -4000,6 +4272,8 @@ prefix_expr_operator(Node) -> %% %% @see prefix_expr/2 +-spec prefix_expr_argument(syntaxTree()) -> syntaxTree(). + prefix_expr_argument(Node) -> case unwrap(Node) of {op, _, _, Argument} -> @@ -4013,6 +4287,8 @@ prefix_expr_argument(Node) -> %% @spec record_field(Name) -> syntaxTree() %% @equiv record_field(Name, none) +-spec record_field(syntaxTree()) -> syntaxTree(). + record_field(Name) -> record_field(Name, none). @@ -4030,13 +4306,15 @@ record_field(Name) -> %% @see record_field_value/1 %% @see record_expr/3 --record(record_field, {name, value}). +-record(record_field, {name :: syntaxTree(), value :: 'none' | syntaxTree()}). %% type(Node) = record_field %% data(Node) = #record_field{name :: Name, value :: Value} %% %% Name = Value = syntaxTree() +-spec record_field(syntaxTree(), 'none' | syntaxTree()) -> syntaxTree(). + record_field(Name, Value) -> tree(record_field, #record_field{name = Name, value = Value}). @@ -4048,6 +4326,8 @@ record_field(Name, Value) -> %% %% @see record_field/2 +-spec record_field_name(syntaxTree()) -> syntaxTree(). + record_field_name(Node) -> (data(Node))#record_field.name. @@ -4064,6 +4344,8 @@ record_field_name(Node) -> %% %% @see record_field/2 +-spec record_field_value(syntaxTree()) -> 'none' | syntaxTree(). + record_field_value(Node) -> (data(Node))#record_field.value. @@ -4083,7 +4365,7 @@ record_field_value(Node) -> %% @see record_index_expr_field/1 %% @see record_expr/3 --record(record_index_expr, {type, field}). +-record(record_index_expr, {type :: syntaxTree(), field :: syntaxTree()}). %% type(Node) = record_index_expr %% data(Node) = #record_index_expr{type :: Type, field :: Field} @@ -4097,6 +4379,8 @@ record_field_value(Node) -> %% Type = atom() %% Field = erl_parse() +-spec record_index_expr(syntaxTree(), syntaxTree()) -> syntaxTree(). + record_index_expr(Type, Field) -> tree(record_index_expr, #record_index_expr{type = Type, field = Field}). @@ -4121,6 +4405,8 @@ revert_record_index_expr(Node) -> %% %% @see record_index_expr/2 +-spec record_index_expr_type(syntaxTree()) -> syntaxTree(). + record_index_expr_type(Node) -> case unwrap(Node) of {record_index, Pos, Type, _} -> @@ -4138,6 +4424,8 @@ record_index_expr_type(Node) -> %% %% @see record_index_expr/2 +-spec record_index_expr_field(syntaxTree()) -> syntaxTree(). + record_index_expr_field(Node) -> case unwrap(Node) of {record_index, _, _, Field} -> @@ -4151,6 +4439,8 @@ record_index_expr_field(Node) -> %% @spec record_access(Argument, Field) -> syntaxTree() %% @equiv record_access(Argument, none, Field) +-spec record_access(syntaxTree(), syntaxTree()) -> syntaxTree(). + record_access(Argument, Field) -> record_access(Argument, none, Field). @@ -4175,7 +4465,9 @@ record_access(Argument, Field) -> %% @see record_expr/3 %% @see query_expr/1 --record(record_access, {argument, type, field}). +-record(record_access, {argument :: syntaxTree(), + type :: 'none' | syntaxTree(), + field :: syntaxTree()}). %% type(Node) = record_access %% data(Node) = #record_access{argument :: Argument, type :: Type, @@ -4192,6 +4484,9 @@ record_access(Argument, Field) -> %% Argument = Field = erl_parse() %% Type = atom() +-spec record_access(syntaxTree(), 'none' | syntaxTree(), syntaxTree()) -> + syntaxTree(). + record_access(Argument, Type, Field) -> tree(record_access,#record_access{argument = Argument, type = Type, @@ -4223,6 +4518,8 @@ revert_record_access(Node) -> %% %% @see record_access/3 +-spec record_access_argument(syntaxTree()) -> syntaxTree(). + record_access_argument(Node) -> case unwrap(Node) of {record_field, _, Argument, _} -> @@ -4246,6 +4543,8 @@ record_access_argument(Node) -> %% %% @see record_access/3 +-spec record_access_type(syntaxTree()) -> 'none' | syntaxTree(). + record_access_type(Node) -> case unwrap(Node) of {record_field, _, _, _} -> @@ -4265,6 +4564,8 @@ record_access_type(Node) -> %% %% @see record_access/3 +-spec record_access_field(syntaxTree()) -> syntaxTree(). + record_access_field(Node) -> case unwrap(Node) of {record_field, _, _, Field} -> @@ -4280,6 +4581,8 @@ record_access_field(Node) -> %% @spec record_expr(Type, Fields) -> syntaxTree() %% @equiv record_expr(none, Type, Fields) +-spec record_expr(syntaxTree(), [syntaxTree()]) -> syntaxTree(). + record_expr(Type, Fields) -> record_expr(none, Type, Fields). @@ -4305,7 +4608,9 @@ record_expr(Type, Fields) -> %% @see record_index_expr/2 %% @see record_access/3 --record(record_expr, {argument, type, fields}). +-record(record_expr, {argument :: 'none' | syntaxTree(), + type :: syntaxTree(), + fields :: [syntaxTree()]}). %% type(Node) = record_expr %% data(Node) = #record_expr{argument :: Argument, type :: Type, @@ -4327,6 +4632,9 @@ record_expr(Type, Fields) -> %% | {record_field, Pos, Field} %% Field = Value = erl_parse() +-spec record_expr('none' | syntaxTree(), syntaxTree(), [syntaxTree()]) -> + syntaxTree(). + record_expr(Argument, Type, Fields) -> tree(record_expr, #record_expr{argument = Argument, type = Type, fields = Fields}). @@ -4363,6 +4671,8 @@ revert_record_expr(Node) -> %% %% @see record_expr/3 +-spec record_expr_argument(syntaxTree()) -> 'none' | syntaxTree(). + record_expr_argument(Node) -> case unwrap(Node) of {record, _, _, _} -> @@ -4381,6 +4691,8 @@ record_expr_argument(Node) -> %% %% @see record_expr/3 +-spec record_expr_type(syntaxTree()) -> syntaxTree(). + record_expr_type(Node) -> case unwrap(Node) of {record, Pos, Type, _} -> @@ -4400,6 +4712,8 @@ record_expr_type(Node) -> %% %% @see record_expr/3 +-spec record_expr_fields(syntaxTree()) -> [syntaxTree()]. + record_expr_fields(Node) -> case unwrap(Node) of {record, _, _, Fields} -> @@ -4427,6 +4741,9 @@ record_expr_fields(Node) -> %% @see application/2 %% @see module_qualifier/2 +-spec application('none' | syntaxTree(), syntaxTree(), [syntaxTree()]) -> + syntaxTree(). + application(none, Name, Arguments) -> application(Name, Arguments); application(Module, Name, Arguments) -> @@ -4446,7 +4763,7 @@ application(Module, Name, Arguments) -> %% @see application_arguments/1 %% @see application/3 --record(application, {operator, arguments}). +-record(application, {operator :: syntaxTree(), arguments :: [syntaxTree()]}). %% type(Node) = application %% data(Node) = #application{operator :: Operator, @@ -4462,6 +4779,8 @@ application(Module, Name, Arguments) -> %% Operator = erl_parse() %% Arguments = [erl_parse()] +-spec application(syntaxTree(), [syntaxTree()]) -> syntaxTree(). + application(Operator, Arguments) -> tree(application, #application{operator = Operator, arguments = Arguments}). @@ -4486,6 +4805,8 @@ revert_application(Node) -> %% @see application/2 %% @see module_qualifier/2 +-spec application_operator(syntaxTree()) -> syntaxTree(). + application_operator(Node) -> case unwrap(Node) of {call, _, Operator, _} -> @@ -4503,6 +4824,8 @@ application_operator(Node) -> %% %% @see application/2 +-spec application_arguments(syntaxTree()) -> [syntaxTree()]. + application_arguments(Node) -> case unwrap(Node) of {call, _, _, Arguments} -> @@ -4524,7 +4847,7 @@ application_arguments(Node) -> %% @see list_comp_body/1 %% @see generator/2 --record(list_comp, {template, body}). +-record(list_comp, {template :: syntaxTree(), body :: [syntaxTree()]}). %% type(Node) = list_comp %% data(Node) = #list_comp{template :: Template, body :: Body} @@ -4539,6 +4862,8 @@ application_arguments(Node) -> %% Template = erl_parse() %% Body = [erl_parse()] \ [] +-spec list_comp(syntaxTree(), [syntaxTree()]) -> syntaxTree(). + list_comp(Template, Body) -> tree(list_comp, #list_comp{template = Template, body = Body}). @@ -4556,6 +4881,8 @@ revert_list_comp(Node) -> %% %% @see list_comp/2 +-spec list_comp_template(syntaxTree()) -> syntaxTree(). + list_comp_template(Node) -> case unwrap(Node) of {lc, _, Template, _} -> @@ -4573,6 +4900,8 @@ list_comp_template(Node) -> %% %% @see list_comp/2 +-spec list_comp_body(syntaxTree()) -> [syntaxTree()]. + list_comp_body(Node) -> case unwrap(Node) of {lc, _, _, Body} -> @@ -4593,7 +4922,7 @@ list_comp_body(Node) -> %% @see binary_comp_body/1 %% @see generator/2 --record(binary_comp, {template, body}). +-record(binary_comp, {template :: syntaxTree(), body :: [syntaxTree()]}). %% type(Node) = binary_comp %% data(Node) = #binary_comp{template :: Template, body :: Body} @@ -4608,6 +4937,8 @@ list_comp_body(Node) -> %% Template = erl_parse() %% Body = [erl_parse()] \ [] +-spec binary_comp(syntaxTree(), [syntaxTree()]) -> syntaxTree(). + binary_comp(Template, Body) -> tree(binary_comp, #binary_comp{template = Template, body = Body}). @@ -4625,6 +4956,8 @@ revert_binary_comp(Node) -> %% %% @see binary_comp/2 +-spec binary_comp_template(syntaxTree()) -> syntaxTree(). + binary_comp_template(Node) -> case unwrap(Node) of {bc, _, Template, _} -> @@ -4642,6 +4975,8 @@ binary_comp_template(Node) -> %% %% @see binary_comp/2 +-spec binary_comp_body(syntaxTree()) -> [syntaxTree()]. + binary_comp_body(Node) -> case unwrap(Node) of {bc, _, _, Body} -> @@ -4670,6 +5005,8 @@ binary_comp_body(Node) -> %% %% Body = erl_parse() +-spec query_expr(syntaxTree()) -> syntaxTree(). + query_expr(Body) -> tree(query_expr, Body). @@ -4686,6 +5023,8 @@ revert_query_expr(Node) -> %% %% @see query_expr/1 +-spec query_expr_body(syntaxTree()) -> syntaxTree(). + query_expr_body(Node) -> case unwrap(Node) of {'query', _, Body} -> @@ -4715,7 +5054,7 @@ query_expr_body(Node) -> %% @see is_form/1 %% @see function/2 --record(rule, {name, clauses}). +-record(rule, {name :: syntaxTree(), clauses :: [syntaxTree()]}). %% type(Node) = rule %% data(Node) = #rule{name :: Name, clauses :: Clauses} @@ -4738,6 +5077,8 @@ query_expr_body(Node) -> %% the integer `Arity'; see `clause' for documentation on %% `erl_parse' clauses. +-spec rule(syntaxTree(), [syntaxTree()]) -> syntaxTree(). + rule(Name, Clauses) -> tree(rule, #rule{name = Name, clauses = Clauses}). @@ -4761,6 +5102,8 @@ revert_rule(Node) -> %% %% @see rule/2 +-spec rule_name(syntaxTree()) -> syntaxTree(). + rule_name(Node) -> case unwrap(Node) of {rule, Pos, Name, _, _} -> @@ -4776,6 +5119,8 @@ rule_name(Node) -> %% %% @see rule/2 +-spec rule_clauses(syntaxTree()) -> [syntaxTree()]. + rule_clauses(Node) -> case unwrap(Node) of {rule, _, _, _, Clauses} -> @@ -4801,6 +5146,8 @@ rule_clauses(Node) -> %% @see clause/3 %% @see clause_patterns/1 +-spec rule_arity(syntaxTree()) -> arity(). + rule_arity(Node) -> %% Note that this never accesses the arity field of %% `erl_parse' rule nodes. @@ -4819,7 +5166,7 @@ rule_arity(Node) -> %% @see list_comp/2 %% @see binary_comp/2 --record(generator, {pattern, body}). +-record(generator, {pattern :: syntaxTree(), body :: syntaxTree()}). %% type(Node) = generator %% data(Node) = #generator{pattern :: Pattern, body :: Body} @@ -4832,6 +5179,8 @@ rule_arity(Node) -> %% %% Pattern = Body = erl_parse() +-spec generator(syntaxTree(), syntaxTree()) -> syntaxTree(). + generator(Pattern, Body) -> tree(generator, #generator{pattern = Pattern, body = Body}). @@ -4849,6 +5198,8 @@ revert_generator(Node) -> %% %% @see generator/2 +-spec generator_pattern(syntaxTree()) -> syntaxTree(). + generator_pattern(Node) -> case unwrap(Node) of {generate, _, Pattern, _} -> @@ -4865,6 +5216,8 @@ generator_pattern(Node) -> %% %% @see generator/2 +-spec generator_body(syntaxTree()) -> syntaxTree(). + generator_body(Node) -> case unwrap(Node) of {generate, _, _, Body} -> @@ -4886,7 +5239,7 @@ generator_body(Node) -> %% @see list_comp/2 %% @see binary_comp/2 --record(binary_generator, {pattern, body}). +-record(binary_generator, {pattern :: syntaxTree(), body :: syntaxTree()}). %% type(Node) = binary_generator %% data(Node) = #binary_generator{pattern :: Pattern, body :: Body} @@ -4899,6 +5252,8 @@ generator_body(Node) -> %% %% Pattern = Body = erl_parse() +-spec binary_generator(syntaxTree(), syntaxTree()) -> syntaxTree(). + binary_generator(Pattern, Body) -> tree(binary_generator, #binary_generator{pattern = Pattern, body = Body}). @@ -4916,6 +5271,8 @@ revert_binary_generator(Node) -> %% %% @see binary_generator/2 +-spec binary_generator_pattern(syntaxTree()) -> syntaxTree(). + binary_generator_pattern(Node) -> case unwrap(Node) of {b_generate, _, Pattern, _} -> @@ -4932,6 +5289,8 @@ binary_generator_pattern(Node) -> %% %% @see binary_generator/2 +-spec binary_generator_body(syntaxTree()) -> syntaxTree(). + binary_generator_body(Node) -> case unwrap(Node) of {b_generate, _, _, Body} -> @@ -4940,6 +5299,7 @@ binary_generator_body(Node) -> (data(Node1))#binary_generator.body end. + %% ===================================================================== %% @spec block_expr(Body::[syntaxTree()]) -> syntaxTree() %% @@ -4960,6 +5320,8 @@ binary_generator_body(Node) -> %% %% Body = [erl_parse()] \ [] +-spec block_expr(Body::[syntaxTree()]) -> syntaxTree(). + block_expr(Body) -> tree(block_expr, Body). @@ -4977,6 +5339,8 @@ revert_block_expr(Node) -> %% %% @see block_expr/1 +-spec block_expr_body(syntaxTree()) -> [syntaxTree()]. + block_expr_body(Node) -> case unwrap(Node) of {block, _, Body} -> @@ -5015,6 +5379,8 @@ block_expr_body(Node) -> %% %% See `clause' for documentation on `erl_parse' clauses. +-spec if_expr([syntaxTree()]) -> syntaxTree(). + if_expr(Clauses) -> tree(if_expr, Clauses). @@ -5032,6 +5398,8 @@ revert_if_expr(Node) -> %% %% @see if_expr/1 +-spec if_expr_clauses(syntaxTree()) -> [syntaxTree()]. + if_expr_clauses(Node) -> case unwrap(Node) of {'if', _, Clauses} -> @@ -5059,7 +5427,7 @@ if_expr_clauses(Node) -> %% @see if_expr/1 %% @see cond_expr/1 --record(case_expr, {argument, clauses}). +-record(case_expr, {argument :: syntaxTree(), clauses :: [syntaxTree()]}). %% type(Node) = case_expr %% data(Node) = #case_expr{argument :: Argument, @@ -5078,6 +5446,8 @@ if_expr_clauses(Node) -> %% %% See `clause' for documentation on `erl_parse' clauses. +-spec case_expr(syntaxTree(), [syntaxTree()]) -> syntaxTree(). + case_expr(Argument, Clauses) -> tree(case_expr, #case_expr{argument = Argument, clauses = Clauses}). @@ -5096,6 +5466,8 @@ revert_case_expr(Node) -> %% %% @see case_expr/2 +-spec case_expr_argument(syntaxTree()) -> syntaxTree(). + case_expr_argument(Node) -> case unwrap(Node) of {'case', _, Argument, _} -> @@ -5113,6 +5485,8 @@ case_expr_argument(Node) -> %% %% @see case_expr/2 +-spec case_expr_clauses(syntaxTree()) -> [syntaxTree()]. + case_expr_clauses(Node) -> case unwrap(Node) of {'case', _, _, Clauses} -> @@ -5151,6 +5525,8 @@ case_expr_clauses(Node) -> %% %% See `clause' for documentation on `erl_parse' clauses. +-spec cond_expr([syntaxTree()]) -> syntaxTree(). + cond_expr(Clauses) -> tree(cond_expr, Clauses). @@ -5168,6 +5544,8 @@ revert_cond_expr(Node) -> %% %% @see cond_expr/1 +-spec cond_expr_clauses(syntaxTree()) -> [syntaxTree()]. + cond_expr_clauses(Node) -> case unwrap(Node) of {'cond', _, Clauses} -> @@ -5181,6 +5559,8 @@ cond_expr_clauses(Node) -> %% @spec receive_expr(Clauses) -> syntaxTree() %% @equiv receive_expr(Clauses, none, []) +-spec receive_expr([syntaxTree()]) -> syntaxTree(). + receive_expr(Clauses) -> receive_expr(Clauses, none, []). @@ -5213,7 +5593,9 @@ receive_expr(Clauses) -> %% @see clause/3 %% @see case_expr/2 --record(receive_expr, {clauses, timeout, action}). +-record(receive_expr, {clauses :: [syntaxTree()], + timeout :: 'none' | syntaxTree(), + action :: [syntaxTree()]}). %% type(Node) = receive_expr %% data(Node) = #receive_expr{clauses :: Clauses, @@ -5236,6 +5618,9 @@ receive_expr(Clauses) -> %% %% See `clause' for documentation on `erl_parse' clauses. +-spec receive_expr([syntaxTree()], 'none' | syntaxTree(), [syntaxTree()]) -> + syntaxTree(). + receive_expr(Clauses, Timeout, Action) -> %% If `Timeout' is `none', we always replace the actual %% `Action' argument with an empty list, since @@ -5271,6 +5656,8 @@ revert_receive_expr(Node) -> %% %% @see receive_expr/3 +-spec receive_expr_clauses(syntaxTree()) -> [syntaxTree()]. + receive_expr_clauses(Node) -> case unwrap(Node) of {'receive', _, Clauses} -> @@ -5295,6 +5682,8 @@ receive_expr_clauses(Node) -> %% %% @see receive_expr/3 +-spec receive_expr_timeout(syntaxTree()) -> 'none' | syntaxTree(). + receive_expr_timeout(Node) -> case unwrap(Node) of {'receive', _, _} -> @@ -5316,6 +5705,8 @@ receive_expr_timeout(Node) -> %% %% @see receive_expr/3 +-spec receive_expr_action(syntaxTree()) -> [syntaxTree()]. + receive_expr_action(Node) -> case unwrap(Node) of {'receive', _, _} -> @@ -5332,6 +5723,8 @@ receive_expr_action(Node) -> %% syntaxTree() %% @equiv try_expr(Body, [], Handlers) +-spec try_expr([syntaxTree()], [syntaxTree()]) -> syntaxTree(). + try_expr(Body, Handlers) -> try_expr(Body, [], Handlers). @@ -5341,6 +5734,8 @@ try_expr(Body, Handlers) -> %% Handlers::[syntaxTree()]) -> syntaxTree() %% @equiv try_expr(Body, Clauses, Handlers, []) +-spec try_expr([syntaxTree()], [syntaxTree()], [syntaxTree()]) -> syntaxTree(). + try_expr(Body, Clauses, Handlers) -> try_expr(Body, Clauses, Handlers, []). @@ -5350,6 +5745,8 @@ try_expr(Body, Clauses, Handlers) -> %% syntaxTree() %% @equiv try_expr(Body, [], [], After) +-spec try_after_expr([syntaxTree()], [syntaxTree()]) -> syntaxTree(). + try_after_expr(Body, After) -> try_expr(Body, [], [], After). @@ -5391,7 +5788,10 @@ try_after_expr(Body, After) -> %% @see class_qualifier/2 %% @see case_expr/2 --record(try_expr, {body, clauses, handlers, 'after'}). +-record(try_expr, {body :: [syntaxTree()], + clauses :: [syntaxTree()], + handlers :: [syntaxTree()], + 'after' :: [syntaxTree()]}). %% type(Node) = try_expr %% data(Node) = #try_expr{body :: Body, @@ -5414,6 +5814,9 @@ try_after_expr(Body, After) -> %% %% See `clause' for documentation on `erl_parse' clauses. +-spec try_expr([syntaxTree()], [syntaxTree()], + [syntaxTree()], [syntaxTree()]) -> syntaxTree(). + try_expr(Body, Clauses, Handlers, After) -> tree(try_expr, #try_expr{body = Body, clauses = Clauses, @@ -5437,6 +5840,8 @@ revert_try_expr(Node) -> %% %% @see try_expr/4 +-spec try_expr_body(syntaxTree()) -> [syntaxTree()]. + try_expr_body(Node) -> case unwrap(Node) of {'try', _, Body, _, _, _} -> @@ -5456,6 +5861,8 @@ try_expr_body(Node) -> %% %% @see try_expr/4 +-spec try_expr_clauses(syntaxTree()) -> [syntaxTree()]. + try_expr_clauses(Node) -> case unwrap(Node) of {'try', _, _, Clauses, _, _} -> @@ -5473,6 +5880,8 @@ try_expr_clauses(Node) -> %% %% @see try_expr/4 +-spec try_expr_handlers(syntaxTree()) -> [syntaxTree()]. + try_expr_handlers(Node) -> case unwrap(Node) of {'try', _, _, _, Handlers, _} -> @@ -5490,6 +5899,8 @@ try_expr_handlers(Node) -> %% %% @see try_expr/4 +-spec try_expr_after(syntaxTree()) -> [syntaxTree()]. + try_expr_after(Node) -> case unwrap(Node) of {'try', _, _, _, _, After} -> @@ -5510,13 +5921,15 @@ try_expr_after(Node) -> %% @see class_qualifier_body/1 %% @see try_expr/4 --record(class_qualifier, {class, body}). +-record(class_qualifier, {class :: syntaxTree(), body :: syntaxTree()}). %% type(Node) = class_qualifier %% data(Node) = #class_qualifier{class :: Class, body :: Body} %% %% Class = Body = syntaxTree() +-spec class_qualifier(syntaxTree(), syntaxTree()) -> syntaxTree(). + class_qualifier(Class, Body) -> tree(class_qualifier, #class_qualifier{class = Class, body = Body}). @@ -5530,6 +5943,8 @@ class_qualifier(Class, Body) -> %% %% @see class_qualifier/2 +-spec class_qualifier_argument(syntaxTree()) -> syntaxTree(). + class_qualifier_argument(Node) -> (data(Node))#class_qualifier.class. @@ -5541,6 +5956,8 @@ class_qualifier_argument(Node) -> %% %% @see class_qualifier/2 +-spec class_qualifier_body(syntaxTree()) -> syntaxTree(). + class_qualifier_body(Node) -> (data(Node))#class_qualifier.body. @@ -5559,6 +5976,8 @@ class_qualifier_body(Node) -> %% @see implicit_fun/1 %% @see implicit_fun/3 +-spec implicit_fun(syntaxTree(), 'none' | syntaxTree()) -> syntaxTree(). + implicit_fun(Name, none) -> implicit_fun(Name); implicit_fun(Name, Arity) -> @@ -5580,6 +5999,9 @@ implicit_fun(Name, Arity) -> %% @see implicit_fun/1 %% @see implicit_fun/2 +-spec implicit_fun('none' | syntaxTree(), syntaxTree(), syntaxTree()) -> + syntaxTree(). + implicit_fun(none, Name, Arity) -> implicit_fun(Name, Arity); implicit_fun(Module, Name, Arity) -> @@ -5610,7 +6032,9 @@ implicit_fun(Module, Name, Arity) -> %% %% Module = atom() %% Name = atom() -%% Arity = integer() +%% Arity = arity() + +-spec implicit_fun(syntaxTree()) -> syntaxTree(). implicit_fun(Name) -> tree(implicit_fun, Name). @@ -5661,6 +6085,8 @@ revert_implicit_fun(Node) -> %% @see arity_qualifier/2 %% @see module_qualifier/2 +-spec implicit_fun_name(syntaxTree()) -> syntaxTree(). + implicit_fun_name(Node) -> case unwrap(Node) of {'fun', Pos, {function, Atom, Arity}} -> @@ -5707,6 +6133,8 @@ implicit_fun_name(Node) -> %% %% See `clause' for documentation on `erl_parse' clauses. +-spec fun_expr([syntaxTree()]) -> syntaxTree(). + fun_expr(Clauses) -> tree(fun_expr, Clauses). @@ -5724,6 +6152,8 @@ revert_fun_expr(Node) -> %% %% @see fun_expr/1 +-spec fun_expr_clauses(syntaxTree()) -> [syntaxTree()]. + fun_expr_clauses(Node) -> case unwrap(Node) of {'fun', _, {clauses, Clauses}} -> @@ -5750,6 +6180,8 @@ fun_expr_clauses(Node) -> %% @see clause/3 %% @see clause_patterns/1 +-spec fun_expr_arity(syntaxTree()) -> arity(). + fun_expr_arity(Node) -> length(clause_patterns(hd(fun_expr_clauses(Node)))). @@ -5766,6 +6198,8 @@ fun_expr_arity(Node) -> %% type(Node) = parentheses %% data(Node) = syntaxTree() +-spec parentheses(syntaxTree()) -> syntaxTree(). + parentheses(Expr) -> tree(parentheses, Expr). @@ -5780,6 +6214,8 @@ revert_parentheses(Node) -> %% %% @see parentheses/1 +-spec parentheses_body(syntaxTree()) -> syntaxTree(). + parentheses_body(Node) -> data(Node). @@ -5788,6 +6224,8 @@ parentheses_body(Node) -> %% @spec macro(Name) -> syntaxTree() %% @equiv macro(Name, none) +-spec macro(syntaxTree()) -> syntaxTree(). + macro(Name) -> macro(Name, none). @@ -5818,7 +6256,7 @@ macro(Name) -> %% @see macro/1 %% @see text/1 --record(macro, {name, arguments}). +-record(macro, {name :: syntaxTree(), arguments :: 'none' | [syntaxTree()]}). %% type(Node) = macro %% data(Node) = #macro{name :: Name, arguments :: Arguments} @@ -5826,6 +6264,8 @@ macro(Name) -> %% Name = syntaxTree() %% Arguments = none | [syntaxTree()] +-spec macro(syntaxTree(), 'none' | [syntaxTree()]) -> syntaxTree(). + macro(Name, Arguments) -> tree(macro, #macro{name = Name, arguments = Arguments}). @@ -5837,6 +6277,8 @@ macro(Name, Arguments) -> %% %% @see macro/2 +-spec macro_name(syntaxTree()) -> syntaxTree(). + macro_name(Node) -> (data(Node))#macro.name. @@ -5853,6 +6295,8 @@ macro_name(Node) -> %% %% @see macro/2 +-spec macro_arguments(syntaxTree()) -> 'none' | [syntaxTree()]. + macro_arguments(Node) -> (data(Node))#macro.arguments. @@ -5871,6 +6315,8 @@ macro_arguments(Node) -> %% @see concrete/1 %% @see is_literal/1 +-spec abstract(term()) -> syntaxTree(). + abstract([H | T] = L) when is_integer(H) -> case is_printable(L) of true -> @@ -5932,6 +6378,8 @@ abstract_tail(H, T) -> %% @see is_literal/1 %% @see char/1 +-spec concrete(syntaxTree()) -> term(). + concrete(Node) -> case type(Node) of atom -> @@ -5978,7 +6426,7 @@ concrete_list([]) -> %% ===================================================================== -%% @spec is_literal(Node::syntaxTree()) -> bool() +%% @spec is_literal(Node::syntaxTree()) -> boolean() %% %% @doc Returns true if Node represents a %% literal term, otherwise false. This function returns @@ -5988,6 +6436,8 @@ concrete_list([]) -> %% @see abstract/1 %% @see concrete/1 +-spec is_literal(syntaxTree()) -> boolean(). + is_literal(T) -> case type(T) of atom -> @@ -6031,6 +6481,8 @@ is_literal(T) -> %% @see revert_forms/1 %% @see //stdlib/erl_parse +-spec revert(syntaxTree()) -> syntaxTree(). + revert(Node) -> case is_tree(Node) of false -> @@ -6172,6 +6624,10 @@ revert_root(Node) -> %% @see form_list/1 %% @see is_form/1 +-type forms() :: syntaxTree() | [syntaxTree()]. + +%% -spec revert_forms(forms()) -> [erl_parse()]. + revert_forms(L) when is_list(L) -> revert_forms(form_list(L)); revert_forms(T) -> @@ -6270,6 +6726,8 @@ revert_forms_1([]) -> %% @see is_leaf/1 %% @see copy_attrs/2 +-spec subtrees(syntaxTree()) -> [[syntaxTree()]]. + subtrees(T) -> case is_leaf(T) of true -> @@ -6442,6 +6900,8 @@ subtrees(T) -> %% @see copy_attrs/2 %% @see type/1 +-spec update_tree(syntaxTree(), [[syntaxTree()]]) -> syntaxTree(). + update_tree(Node, Groups) -> copy_attrs(Node, make_tree(type(Node), Groups)). @@ -6471,6 +6931,8 @@ update_tree(Node, Groups) -> %% @see is_leaf/1 %% @see copy_attrs/2 +-spec make_tree(atom(), [[syntaxTree()]]) -> syntaxTree(). + make_tree(application, [[F], A]) -> application(F, A); make_tree(arity_qualifier, [[N], [A]]) -> arity_qualifier(N, A); make_tree(attribute, [[N]]) -> attribute(N); @@ -6567,6 +7029,8 @@ make_tree(tuple, [E]) -> tuple(E). %% @see type/1 %% @see get_ann/1 +-spec meta(syntaxTree()) -> syntaxTree(). + meta(T) -> %% First of all we check for metavariables: case type(T) of @@ -6693,6 +7157,8 @@ meta_call(F, As) -> %% @spec tree(Type) -> syntaxTree() %% @equiv tree(Type, []) +-spec tree(atom()) -> syntaxTree(). + tree(Type) -> tree(Type, []). @@ -6727,12 +7193,14 @@ tree(Type) -> %% @see data/1 %% @see type/1 +-spec tree(atom(), term()) -> syntaxTree(). + tree(Type, Data) -> #tree{type = Type, data = Data}. %% ===================================================================== -%% @spec is_tree(Tree::syntaxTree()) -> bool() +%% @spec is_tree(Tree::syntaxTree()) -> boolean() %% %% @doc For special purposes only. Returns true if %% Tree is an abstract syntax tree and false @@ -6743,6 +7211,8 @@ tree(Type, Data) -> %% %% @see tree/2 +-spec is_tree(syntaxTree()) -> boolean(). + is_tree(#tree{}) -> true; is_tree(_) -> @@ -6759,6 +7229,8 @@ is_tree(_) -> %% %% @see tree/2 +-spec data(syntaxTree()) -> term(). + data(#tree{data = D}) -> D; data(T) -> erlang:error({badarg, T}). @@ -6788,6 +7260,8 @@ data(T) -> erlang:error({badarg, T}). %% trees. Attaching a wrapper onto another wrapper structure is an %% error.

+%%-spec wrap(erl_parse:parse_tree()) -> syntaxTree(). + wrap(Node) -> %% We assume that Node is an old-school `erl_parse' tree. #wrapper{type = type(Node), attr = #attr{pos = get_pos(Node)}, @@ -6802,17 +7276,21 @@ wrap(Node) -> %% erl_parse tree; otherwise it returns Node %% itself. +-spec unwrap(syntaxTree()) -> syntaxTree(). + unwrap(#wrapper{tree = Node}) -> Node; unwrap(Node) -> Node. % This could also be a new-form node. %% ===================================================================== -%% @spec is_wrapper(Term::term()) -> bool() +%% @spec is_wrapper(Term::term()) -> boolean() %% %% @doc Returns true if the argument is a wrapper %% structure, otherwise false. -ifndef(NO_UNUSED). +-spec is_wrapper(term()) -> boolean(). + is_wrapper(#wrapper{}) -> true; is_wrapper(_) -> diff --git a/lib/syntax_tools/src/erl_syntax_lib.erl b/lib/syntax_tools/src/erl_syntax_lib.erl index ccbf864c2a62..5c4e07448856 100644 --- a/lib/syntax_tools/src/erl_syntax_lib.erl +++ b/lib/syntax_tools/src/erl_syntax_lib.erl @@ -46,6 +46,9 @@ new_variable_names/2, new_variable_names/3, strip_comments/1, to_comment/1, to_comment/2, to_comment/3, variables/1]). +%% ===================================================================== + +-type ordset(X) :: [X]. % XXX: TAKE ME OUT %% ===================================================================== %% @spec map(Function, Tree::syntaxTree()) -> syntaxTree() @@ -58,6 +61,9 @@ %% %% @see map_subtrees/2 +-spec map(fun((erl_syntax:syntaxTree()) -> erl_syntax:syntaxTree()), + erl_syntax:syntaxTree()) -> erl_syntax:syntaxTree(). + map(F, Tree) -> case erl_syntax:subtrees(Tree) of [] -> @@ -81,6 +87,9 @@ map(F, Tree) -> %% %% @see map/2 +-spec map_subtrees(fun((erl_syntax:syntaxTree()) -> erl_syntax:syntaxTree()), + erl_syntax:syntaxTree()) -> erl_syntax:syntaxTree(). + map_subtrees(F, Tree) -> case erl_syntax:subtrees(Tree) of [] -> @@ -105,6 +114,9 @@ map_subtrees(F, Tree) -> %% @see fold_subtrees/3 %% @see foldl_listlist/3 +-spec fold(fun((erl_syntax:syntaxTree(), term()) -> term()), + term(), erl_syntax:syntaxTree()) -> term(). + fold(F, S, Tree) -> case erl_syntax:subtrees(Tree) of [] -> @@ -137,6 +149,9 @@ fold_2(_, S, []) -> %% %% @see fold/3 +-spec fold_subtrees(fun((erl_syntax:syntaxTree(), term()) -> term()), + term(), erl_syntax:syntaxTree()) -> term(). + fold_subtrees(F, S, Tree) -> foldl_listlist(F, S, erl_syntax:subtrees(Tree)). @@ -151,6 +166,9 @@ fold_subtrees(F, S, Tree) -> %% @see fold/3 %% @see //stdlib/lists:foldl/3 +-spec foldl_listlist(fun((term(), term()) -> term()), + term(), [[term()]]) -> term(). + foldl_listlist(F, S, [L | Ls]) -> foldl_listlist(F, foldl(F, S, L), Ls); foldl_listlist(_, S, []) -> @@ -178,6 +196,9 @@ foldl(_, S, []) -> %% @see map/2 %% @see fold/3 +-spec mapfold(fun((erl_syntax:syntaxTree(), term()) -> {erl_syntax:syntaxTree(), term()}), + term(), erl_syntax:syntaxTree()) -> {erl_syntax:syntaxTree(), term()}. + mapfold(F, S, Tree) -> case erl_syntax:subtrees(Tree) of [] -> @@ -216,6 +237,11 @@ mapfold_2(_, S, []) -> %% %% @see mapfold/3 +-spec mapfold_subtrees(fun((erl_syntax:syntaxTree(), term()) -> + {erl_syntax:syntaxTree(), term()}), + term(), erl_syntax:syntaxTree()) -> + {erl_syntax:syntaxTree(), term()}. + mapfold_subtrees(F, S, Tree) -> case erl_syntax:subtrees(Tree) of [] -> @@ -237,6 +263,9 @@ mapfold_subtrees(F, S, Tree) -> %% The list of lists in the result has the same structure as the given %% list of lists. +-spec mapfoldl_listlist(fun((term(), term()) -> {term(), term()}), + term(), [[term()]]) -> {[[term()]], term()}. + mapfoldl_listlist(F, S, [L | Ls]) -> {L1, S1} = mapfoldl(F, S, L), {Ls1, S2} = mapfoldl_listlist(F, S1, Ls), @@ -263,6 +292,8 @@ mapfoldl(_, S, []) -> %% %% @see //stdlib/sets +-spec variables(erl_syntax:syntaxTree()) -> set(). + variables(Tree) -> variables(Tree, sets:new()). @@ -316,6 +347,8 @@ default_variable_name(N) -> %% %% @see new_variable_name/2 +-spec new_variable_name(set()) -> atom(). + new_variable_name(S) -> new_variable_name(fun default_variable_name/1, S). @@ -340,6 +373,8 @@ new_variable_name(S) -> %% @see //stdlib/sets %% @see //stdlib/random +-spec new_variable_name(fun((integer()) -> atom()), set()) -> atom(). + new_variable_name(F, S) -> R = start_range(S), new_variable_name(R, F, S). @@ -388,6 +423,8 @@ generate(_Key, Range) -> %% %% @see new_variable_name/1 +-spec new_variable_names(integer(), set()) -> [atom()]. + new_variable_names(N, S) -> new_variable_names(N, fun default_variable_name/1, S). @@ -402,6 +439,9 @@ new_variable_names(N, S) -> %% %% @see new_variable_name/2 +-spec new_variable_names(integer(), fun((integer()) -> atom()), set()) -> + [atom()]. + new_variable_names(N, F, S) when is_integer(N) -> R = start_range(S), new_variable_names(N, [], R, F, S). @@ -441,6 +481,9 @@ new_variable_names(0, Names, _, _, _) -> %% @see annotate_bindings/1 %% @see //stdlib/ordsets +-spec annotate_bindings(erl_syntax:syntaxTree(), ordset(atom())) -> + erl_syntax:syntaxTree(). + annotate_bindings(Tree, Env) -> {Tree1, _, _} = vann(Tree, Env), Tree1. @@ -457,6 +500,8 @@ annotate_bindings(Tree, Env) -> %% %% @see annotate_bindings/2 +-spec annotate_bindings(erl_syntax:syntaxTree()) -> erl_syntax:syntaxTree(). + annotate_bindings(Tree) -> As = erl_syntax:get_ann(Tree), case lists:keyfind(env, 1, As) of @@ -856,7 +901,7 @@ delete_binding_anns([]) -> %% ===================================================================== -%% @spec is_fail_expr(Tree::syntaxTree()) -> bool() +%% @spec is_fail_expr(Tree::syntaxTree()) -> boolean() %% %% @doc Returns `true' if `Tree' represents an %% expression which never terminates normally. Note that the reverse @@ -869,6 +914,8 @@ delete_binding_anns([]) -> %% @see //erts/erlang:error/1 %% @see //erts/erlang:error/2 +-spec is_fail_expr(erl_syntax:syntaxTree()) -> boolean(). + is_fail_expr(E) -> case erl_syntax:type(E) of application -> @@ -1038,6 +1085,12 @@ is_fail_expr(E) -> %% @see erl_syntax:error_marker_info/1 %% @see erl_syntax:warning_marker_info/1 +-type key() :: 'attributes' | 'errors' | 'exports' | 'functions' | 'imports' + | 'module' | 'records' | 'rules' | 'warnings'. +-type info_pair() :: {key(), term()}. + +-spec analyze_forms(erl_syntax:forms()) -> [info_pair()]. + analyze_forms(Forms) when is_list(Forms) -> finfo_to_list(lists:foldl(fun collect_form/2, new_finfo(), Forms)); analyze_forms(Forms) -> @@ -1204,6 +1257,8 @@ list_value(List) -> %% @see erl_syntax:error_marker_info/1 %% @see erl_syntax:warning_marker_info/1 +-spec analyze_form(erl_syntax:syntaxTree()) -> {atom(), term()} | atom(). + analyze_form(Node) -> case erl_syntax:type(Node) of attribute -> @@ -1276,6 +1331,9 @@ analyze_form(Node) -> %% @see analyze_record_attribute/1 %% @see analyze_wild_attribute/1 +-spec analyze_attribute(erl_syntax:syntaxTree()) -> + 'preprocessor' | {atom(), term()}. % XXX: underspecified + analyze_attribute(Node) -> Name = erl_syntax:attribute_name(Node), case erl_syntax:type(Name) of @@ -1326,12 +1384,14 @@ analyze_attribute(_, Node) -> %% containing the module name and a list of the parameter variable %% names. %% -%% The evaluation throws `syntax_error' if -%% `Node' does not represent a well-formed module -%% attribute. +%% The evaluation throws `syntax_error' if `Node' does not represent a +%% well-formed module attribute. %% %% @see analyze_attribute/1 +-spec analyze_module_attribute(erl_syntax:syntaxTree()) -> + atom() | {atom(), [atom()]}. + analyze_module_attribute(Node) -> case erl_syntax:type(Node) of attribute -> @@ -1370,12 +1430,16 @@ analyze_variable_list(Node) -> %% attribute. We do not guarantee that each name occurs at most once in %% the list. The order of listing is not defined. %% -%% The evaluation throws `syntax_error' if -%% `Node' does not represent a well-formed export -%% attribute. +%% The evaluation throws `syntax_error' if `Node' does not represent a +%% well-formed export attribute. %% %% @see analyze_attribute/1 +-type functionN() :: atom() | {atom(), arity()}. +-type functionName() :: functionN() | {atom(), functionN()}. + +-spec analyze_export_attribute(erl_syntax:syntaxTree()) -> [functionName()]. + analyze_export_attribute(Node) -> case erl_syntax:type(Node) of attribute -> @@ -1416,6 +1480,8 @@ analyze_function_name_list(Node) -> %% The evaluation throws `syntax_error' if %% `Node' does not represent a well-formed function name. +-spec analyze_function_name(erl_syntax:syntaxTree()) -> functionName(). + analyze_function_name(Node) -> case erl_syntax:type(Node) of atom -> @@ -1470,12 +1536,14 @@ append_arity(_A, Name) -> %% that each name occurs at most once in `Names'. The order %% of listing is not defined. %% -%% The evaluation throws `syntax_error' if -%% `Node' does not represent a well-formed import -%% attribute. +%% The evaluation throws `syntax_error' if `Node' does not represent a +%% well-formed import attribute. %% %% @see analyze_attribute/1 +-spec analyze_import_attribute(erl_syntax:syntaxTree()) -> + {atom(), [functionName()]} | atom(). + analyze_import_attribute(Node) -> case erl_syntax:type(Node) of attribute -> @@ -1498,19 +1566,19 @@ analyze_import_attribute(Node) -> %% @spec analyze_wild_attribute(Node::syntaxTree()) -> {atom(), term()} %% %% @doc Returns the name and value of a "wild" attribute. The result is -%% the pair `{Name, Value}', if `Node' represents -%% "`-Name(Value)'". +%% the pair `{Name, Value}', if `Node' represents "`-Name(Value)'". %% %% Note that no checking is done whether `Name' is a %% reserved attribute name such as `module' or %% `export': it is assumed that the attribute is "wild". %% -%% The evaluation throws `syntax_error' if -%% `Node' does not represent a well-formed wild -%% attribute. +%% The evaluation throws `syntax_error' if `Node' does not represent a +%% well-formed wild attribute. %% %% @see analyze_attribute/1 +-spec analyze_wild_attribute(erl_syntax:syntaxTree()) -> {atom(), term()}. + analyze_wild_attribute(Node) -> case erl_syntax:type(Node) of attribute -> @@ -1559,6 +1627,10 @@ analyze_wild_attribute(Node) -> %% @see analyze_attribute/1 %% @see analyze_record_field/1 +-type fields() :: [{atom(), 'none' | erl_syntax:syntaxTree()}]. + +-spec analyze_record_attribute(erl_syntax:syntaxTree()) -> {atom(), fields()}. + analyze_record_attribute(Node) -> case erl_syntax:type(Node) of attribute -> @@ -1629,6 +1701,11 @@ analyze_record_attribute_tuple(Node) -> %% @see analyze_record_attribute/1 %% @see analyze_record_field/1 +-type info() :: {atom(), [{atom(), 'none' | erl_syntax:syntaxTree()}]} + | {atom(), atom()} | atom(). + +-spec analyze_record_expr(erl_syntax:syntaxTree()) -> {atom(), info()} | atom(). + analyze_record_expr(Node) -> case erl_syntax:type(Node) of record_expr -> @@ -1700,6 +1777,9 @@ analyze_record_expr(Node) -> %% @see analyze_record_attribute/1 %% @see analyze_record_expr/1 +-spec analyze_record_field(erl_syntax:syntaxTree()) -> + {atom(), 'none' | erl_syntax:syntaxTree()}. + analyze_record_field(Node) -> case erl_syntax:type(Node) of record_field -> @@ -1730,6 +1810,8 @@ analyze_record_field(Node) -> %% %% @see analyze_attribute/1 +-spec analyze_file_attribute(erl_syntax:syntaxTree()) -> {string(), integer()}. + analyze_file_attribute(Node) -> case erl_syntax:type(Node) of attribute -> @@ -1765,6 +1847,8 @@ analyze_file_attribute(Node) -> %% %% @see analyze_rule/1 +-spec analyze_function(erl_syntax:syntaxTree()) -> {atom(), arity()}. + analyze_function(Node) -> case erl_syntax:type(Node) of function -> @@ -1794,6 +1878,8 @@ analyze_function(Node) -> %% %% @see analyze_function/1 +-spec analyze_rule(erl_syntax:syntaxTree()) -> {atom(), arity()}. + analyze_rule(Node) -> case erl_syntax:type(Node) of rule -> @@ -1826,11 +1912,12 @@ analyze_rule(Node) -> %% %% @see analyze_function_name/1 +-spec analyze_implicit_fun(erl_syntax:syntaxTree()) -> functionName(). + analyze_implicit_fun(Node) -> case erl_syntax:type(Node) of implicit_fun -> - analyze_function_name( - erl_syntax:implicit_fun_name(Node)); + analyze_function_name(erl_syntax:implicit_fun_name(Node)); _ -> throw(syntax_error) end. @@ -1851,12 +1938,15 @@ analyze_implicit_fun(Node) -> %% function is not explicitly named (i.e., `F' is given by %% some expression), only the arity `A' is returned. %% -%% The evaluation throws `syntax_error' if -%% `Node' does not represent a well-formed application -%% expression. +%% The evaluation throws `syntax_error' if `Node' does not represent a +%% well-formed application expression. %% %% @see analyze_function_name/1 +-type appFunName() :: {atom(), arity()} | {atom(), {atom(), arity()}}. + +-spec analyze_application(erl_syntax:syntaxTree()) -> appFunName() | arity(). + analyze_application(Node) -> case erl_syntax:type(Node) of application -> @@ -1897,6 +1987,11 @@ analyze_application(Node) -> %% %% @see analyze_function_name/1 +-type shortname() :: atom() | {atom(), arity()}. +-type name() :: shortname() | {atom(), shortname()}. + +-spec function_name_expansions([name()]) -> [{shortname(), name()}]. + function_name_expansions(Fs) -> function_name_expansions(Fs, []). @@ -1922,6 +2017,8 @@ function_name_expansions(A, Name, Ack) -> %% Standalone comments in form lists are removed; any other standalone %% comments are changed into null-comments (no text, no indentation). +-spec strip_comments(erl_syntax:syntaxTree()) -> erl_syntax:syntaxTree(). + strip_comments(Tree) -> map(fun strip_comments_1/1, Tree). @@ -1942,6 +2039,8 @@ strip_comments_1(T) -> %% @spec to_comment(Tree) -> syntaxTree() %% @equiv to_comment(Tree, "% ") +-spec to_comment(erl_syntax:syntaxTree()) -> erl_syntax:syntaxTree(). + to_comment(Tree) -> to_comment(Tree, "% "). @@ -1956,6 +2055,8 @@ to_comment(Tree) -> %% @see to_comment/3 %% @see erl_prettypr:format/1 +-spec to_comment(erl_syntax:syntaxTree(), string()) -> erl_syntax:syntaxTree(). + to_comment(Tree, Prefix) -> F = fun (T) -> erl_prettypr:format(T) end, to_comment(Tree, Prefix, F). @@ -1985,6 +2086,10 @@ to_comment(Tree, Prefix) -> %% @see to_comment/1 %% @see to_comment/2 +-spec to_comment(erl_syntax:syntaxTree(), string(), + fun((erl_syntax:syntaxTree()) -> string())) -> + erl_syntax:syntaxTree(). + to_comment(Tree, Prefix, F) -> erl_syntax:comment(split_lines(F(Tree), Prefix)). @@ -1998,6 +2103,8 @@ to_comment(Tree, Prefix, F) -> %% @see limit/3 %% @see erl_syntax:text/1 +-spec limit(erl_syntax:syntaxTree(), integer()) -> erl_syntax:syntaxTree(). + limit(Tree, Depth) -> limit(Tree, Depth, erl_syntax:text("...")). @@ -2026,6 +2133,9 @@ limit(Tree, Depth) -> %% %% @see limit/2 +-spec limit(erl_syntax:syntaxTree(), integer(), erl_syntax:syntaxTree()) -> + erl_syntax:syntaxTree(). + limit(_Tree, Depth, Node) when Depth < 0 -> Node; limit(Tree, Depth, Node) -> diff --git a/lib/syntax_tools/src/erl_tidy.erl b/lib/syntax_tools/src/erl_tidy.erl index e3b479008f48..021ab1873628 100644 --- a/lib/syntax_tools/src/erl_tidy.erl +++ b/lib/syntax_tools/src/erl_tidy.erl @@ -66,6 +66,7 @@ dir__defaults() -> %% @equiv dir("") -spec dir() -> 'ok'. + dir() -> dir(""). @@ -74,6 +75,7 @@ dir() -> %% @equiv dir(Dir, []) -spec dir(file:filename()) -> 'ok'. + dir(Dir) -> dir(Dir, []). @@ -130,6 +132,7 @@ dir(Dir) -> options :: options()}). -spec dir(file:filename(), options()) -> 'ok'. + dir(Dir, Opts) -> Opts1 = Opts ++ dir__defaults(), Env = #dir{follow_links = proplists:get_bool(follow_links, Opts1), @@ -212,6 +215,7 @@ default_printer() -> %% @equiv file(Name, []) -spec file(file:filename()) -> 'ok'. + file(Name) -> file(Name, []). @@ -275,6 +279,7 @@ file(Name) -> %% @see module/2 -spec file(file:filename(), options()) -> 'ok'. + file(Name, Opts) -> Parent = self(), Child = spawn_link(fun () -> file_1(Parent, Name, Opts) end), @@ -478,11 +483,12 @@ backup_file_1(Name, Opts) -> throw(R) end. - %% ===================================================================== %% @spec module(Forms) -> syntaxTree() %% @equiv module(Forms, []) +-spec module(erl_syntax:forms()) -> erl_syntax:syntaxTree(). + module(Forms) -> module(Forms, []). @@ -609,6 +615,8 @@ module(Forms) -> %% %%
+-spec module(erl_syntax:forms(), [term()]) -> erl_syntax:syntaxTree(). + module(Forms, Opts) when is_list(Forms) -> module(erl_syntax:form_list(Forms), Opts); module(Forms, Opts) -> @@ -668,11 +676,8 @@ analyze_forms(Forms, File) -> throw(R) end. -%% XXX: The following should be imported from erl_syntax_lib --type key() :: atom(). --type info_pair() :: {key(), any()}. +-spec get_module_name([erl_syntax_lib:info_pair()], string()) -> atom(). --spec get_module_name([info_pair()], string()) -> atom(). get_module_name(List, File) -> case lists:keyfind(module, 1, List) of {module, M} -> @@ -691,7 +696,8 @@ get_module_attributes(List) -> [] end. --spec get_module_exports([info_pair()]) -> [{atom(), byte()}]. +-spec get_module_exports([erl_syntax_lib:info_pair()]) -> [{atom(), arity()}]. + get_module_exports(List) -> case lists:keyfind(exports, 1, List) of {exports, Es} -> @@ -700,7 +706,8 @@ get_module_exports(List) -> [] end. --spec get_module_imports([info_pair()]) -> [{atom(), atom()}]. +-spec get_module_imports([erl_syntax_lib:info_pair()]) -> [{atom(), atom()}]. + get_module_imports(List) -> case lists:keyfind(imports, 1, List) of {imports, Is} -> @@ -714,6 +721,7 @@ compile_attrs(As) -> || {compile, T} <- As]). -spec flatten_imports([{atom(), [atom()]}]) -> [{atom(), atom()}]. + flatten_imports(Is) -> [{F, M} || {M, Fs} <- Is, F <- Fs]. @@ -736,7 +744,8 @@ check_imports(Is, Opts, File) -> end. -spec check_imports_1([{atom(), atom()}]) -> boolean(). -check_imports_1([{F1, M1}, {F2, M2} | _Is]) when F1 =:= F2, M1 =/= M2 -> + +check_imports_1([{F, M1}, {F, M2} | _Is]) when M1 =/= M2 -> false; check_imports_1([_ | Is]) -> check_imports_1(Is); @@ -1629,6 +1638,7 @@ rename_remote_call(F, St) -> end. -spec rename_remote_call_1(mfa()) -> {atom(), atom()} | 'false'. + rename_remote_call_1({dict, dict_to_list, 1}) -> {dict, to_list}; rename_remote_call_1({dict, list_to_dict, 1}) -> {dict, from_list}; rename_remote_call_1({erl_eval, arg_list, 2}) -> {erl_eval, expr_list}; @@ -1662,7 +1672,8 @@ rename_remote_call_1({string, index, 2}) -> {string, str}; rename_remote_call_1({unix, cmd, 1}) -> {os, cmd}; rename_remote_call_1(_) -> false. --spec rewrite_guard_test(atom(), byte()) -> atom(). +-spec rewrite_guard_test(atom(), arity()) -> atom(). + rewrite_guard_test(atom, 1) -> is_atom; rewrite_guard_test(binary, 1) -> is_binary; rewrite_guard_test(constant, 1) -> is_constant; @@ -1680,7 +1691,8 @@ rewrite_guard_test(record, 2) -> is_record; rewrite_guard_test(record, 3) -> is_record; rewrite_guard_test(N, _A) -> N. --spec reverse_guard_test(atom(), byte()) -> atom(). +-spec reverse_guard_test(atom(), arity()) -> atom(). + reverse_guard_test(is_atom, 1) -> atom; reverse_guard_test(is_binary, 1) -> binary; reverse_guard_test(is_constant, 1) -> constant; diff --git a/lib/syntax_tools/src/igor.erl b/lib/syntax_tools/src/igor.erl index 9e7b7841702b..e92e9593b612 100644 --- a/lib/syntax_tools/src/igor.erl +++ b/lib/syntax_tools/src/igor.erl @@ -117,20 +117,23 @@ -define(record_name(R), {record, R}). +%% ===================================================================== + +-type ordset(X) :: [X]. % XXX: TAKE ME OUT + +%% ===================================================================== %% Data structure for module information --record(module, {name, % = atom() - vars = none, % = [atom()] | none - functions, % = ordset({atom(), int()}) - exports, % = ordset({atom(), int()}) - % | ordset({{atom(), int()}, - % term()}) - aliases, % = ordset({{atom(), int()}, - % {atom(), - % {atom(), int()}}}) - attributes, % = ordset({atom(), term()}) - records % = [{atom(), [{atom(), term()}]}] +-record(module, {name :: atom(), + vars = none :: [atom()] | 'none', + functions :: ordset({atom(), arity()}), + exports :: ordset({atom(), arity()}) + | ordset({{atom(), arity()}, term()}), + aliases :: ordset({{atom(), arity()}, + {atom(), {atom(), arity()}}}), + attributes :: ordset({atom(), term()}), + records :: [{atom(), [{atom(), term()}]}] }). %% The default pretty-printing function. @@ -138,6 +141,17 @@ default_printer(Tree, Options) -> erl_prettypr:format(Tree, Options). +%% ===================================================================== + +-type option() :: atom() | {atom(), term()}. + +-type attribute() :: {atom(), term()}. +-type moduleName() :: atom(). +-type functionName() :: {atom(), arity()}. +-type functionPair() :: {functionName(), {moduleName(), functionName()}}. +-type stubDescriptor() :: [{moduleName(), [functionPair()], [attribute()]}]. + +-type notes() :: 'always' | 'yes' | 'no'. %% ===================================================================== %% @spec parse_transform(Forms::[syntaxTree()], Options::[term()]) -> @@ -169,6 +183,9 @@ default_printer(Tree, Options) -> %% @see merge_files/4 %% @see //compiler/compile:file/2 +-spec parse_transform(erl_syntax:forms(), [option()]) -> + [erl_syntax:syntaxTree()]. + parse_transform(Forms, Options) -> M = get_module_info(Forms), Name = M#module.name, @@ -192,6 +209,8 @@ parse_transform(Forms, Options) -> %% @spec merge(Name::atom(), Files::[filename()]) -> [filename()] %% @equiv merge(Name, Files, []) +-spec merge(atom(), [file:filename()]) -> [file:filename()]. + merge(Name, Files) -> merge(Name, Files, []). @@ -251,7 +270,7 @@ merge(Name, Files) -> %%
Specifies the file name suffix to be used when a backup file %% is created; the default value is `".bak"'.
%% -%%
`{backups, bool()}'
+%%
`{backups, boolean()}'
%% %%
If the value is `true', existing files will be %% renamed before new files are opened for writing. The new names @@ -271,7 +290,7 @@ merge(Name, Files) -> %% resulting source code is to be written. By default, this is the %% same as the `Name' argument.
%% -%%
`{preprocess, bool()}'
+%%
`{preprocess, boolean()}'
%% %%
If the value is `true', preprocessing will be done %% when reading the source code. See `merge_files/4' for @@ -294,7 +313,7 @@ merge(Name, Files) -> %% stub module files are written. The default value is %% `"stubs"'.
%% -%%
`{stubs, bool()}'
+%%
`{stubs, boolean()}'
%% %%
If the value is `true', stub module files will be %% automatically generated for all exported modules that do not have @@ -324,6 +343,8 @@ merge(Name, Files) -> {suffix, ?DEFAULT_SUFFIX}, {verbose, false}]). +-spec merge(atom(), [file:filename()], [option()]) -> [file:filename()]. + merge(Name, Files, Opts) -> Opts1 = Opts ++ ?DEFAULT_MERGE_OPTS, {Tree, Stubs} = merge_files(Name, Files, Opts1), @@ -339,6 +360,9 @@ merge(Name, Files, Opts) -> %% {syntaxTree(), [stubDescriptor()]} %% @equiv merge_files(Name, [], Files, Options) +-spec merge_files(atom(), [file:filename()], [option()]) -> + {erl_syntax:syntaxTree(), [stubDescriptor()]}. + merge_files(Name, Files, Options) -> merge_files(Name, [], Files, Options). @@ -380,7 +404,7 @@ merge_files(Name, Files, Options) -> %% %% Options: %%
-%%
`{comments, bool()}'
+%%
`{comments, boolean()}'
%% %%
If the value is `true', source code comments in %% the original files will be preserved in the output. The default @@ -409,7 +433,7 @@ merge_files(Name, Files, Options) -> %% Erlang preprocessor, if used (cf. the `preprocess' %% option). The default value is the empty list.
%% -%%
`{preprocess, bool()}'
+%%
`{preprocess, boolean()}'
%% %%
If the value is `false', Igor will read source %% files without passing them through the Erlang preprocessor @@ -438,6 +462,9 @@ merge_files(Name, Files, Options) -> %% @see //stdlib/filename:find_src/2 %% @see epp_dodger +-spec merge_files(atom(), erl_syntax:forms(), [file:filename()], [option()]) -> + {erl_syntax:syntaxTree(), [stubDescriptor()]}. + merge_files(_, _Trees, [], _) -> report_error("no files to merge."), exit(badarg); @@ -512,7 +539,7 @@ merge_files(Name, Trees, Files, Opts) -> %% `Sources' will be exported. The default value is the %% empty list.
%% -%%
`{export_all, bool()}'
+%%
`{export_all, boolean()}'
%% %%
If the value is `true', this is equivalent to %% listing all of the input modules in the `export' @@ -532,7 +559,7 @@ merge_files(Name, Trees, Files, Opts) -> %% they will be handled as in the `comment' case. The %% default value is `no'.
%% -%%
`{no_banner, bool()}'
+%%
`{no_banner, boolean()}'
%% %%
If the value is `true', no banner comment will be %% added at the top of the resulting module, even if the target @@ -541,7 +568,7 @@ merge_files(Name, Trees, Files, Opts) -> %% code is at the top of the output. The default value is %% `false'.
%% -%%
`{no_headers, bool()}'
+%%
`{no_headers, boolean()}'
%% %%
If the value is `true', no header comments will be %% added to the resulting module at the beginning of each section of @@ -550,7 +577,7 @@ merge_files(Name, Trees, Files, Opts) -> %% normally added whenever more than two or more modules are %% merged.
%% -%%
`{no_imports, bool()}'
+%%
`{no_imports, boolean()}'
%% %%
If the value is `true', all %% `-import(...)' declarations in the original code will @@ -599,7 +626,7 @@ merge_files(Name, Trees, Files, Opts) -> %% regarded as "static", regardless of the value of this option. By %% default, all involved modules are assumed to be static.
%% -%%
`{tidy, bool()}'
+%%
`{tidy, boolean()}'
%% %%
If the value is `true', the resulting code will be %% processed using the `erl_tidy' module, which removes @@ -607,7 +634,7 @@ merge_files(Name, Trees, Files, Opts) -> %% `erl_tidy:module/2' for additional options.) The %% default value is `true'.
%% -%%
`{verbose, bool()}'
+%%
`{verbose, boolean()}'
%% %%
If the value is `true', progress messages will be %% output while the program is running; the default value is @@ -659,19 +686,22 @@ merge_files(Name, Trees, Files, Opts) -> %% Data structure for merging environment. --record(merge, {target, % = atom() - sources, % = ordset(atom()) - export, % = ordset(atom()) - static, % = ordset(atom()) - safe, % = ordset(atom()) - preserved, % = bool() - no_headers, % = bool() - notes, % = bool() - redirect, % = dict(atom(), atom()) - no_imports, % = ordset(atom()) - options % = [term()] +-record(merge, {target :: atom(), + sources :: ordset(atom()), + export :: ordset(atom()), + static :: ordset(atom()), + safe :: ordset(atom()), + preserved :: boolean(), + no_headers :: boolean(), + notes :: notes(), + redirect :: dict(), % = dict(atom(), atom()) + no_imports :: ordset(atom()), + options :: [option()] }). +-spec merge_sources(atom(), erl_syntax:forms(), [option()]) -> + {erl_syntax:syntaxTree(), [stubDescriptor()]}. + merge_sources(Name, Sources, Opts) -> %% Prepare the options and the inputs. Opts1 = Opts ++ [{export_all, false}, @@ -696,7 +726,7 @@ merge_sources(Name, Sources, Opts) -> %% Data structure for keeping state during transformation. --record(state, {export}). +-record(state, {export :: set()}). state__add_export(Name, Arity, S) -> S#state{export = sets:add_element({Name, Arity}, @@ -981,7 +1011,7 @@ make_stubs(Modules, Renaming, Env) -> make_stubs_1([M | Ms], Renaming, Env) -> Name = M#module.name, - if Name /= Env#merge.target -> + if Name =/= Env#merge.target -> case ordsets:is_element(Name, Env#merge.export) of true -> [make_stub(M, Renaming(Name), Env) @@ -1005,7 +1035,7 @@ make_stub(M, Map, Env) -> %% Removing and/or out-commenting program forms. The returned form %% sequence tree is not necessarily flat. --record(filter, {records, file_attributes, attributes}). +-record(filter, {records :: set(), file_attributes, attributes}). filter_forms(Tree, Env) -> Forms = erl_syntax:form_list_elements( @@ -1098,8 +1128,7 @@ filter_form(F, S) -> kill_form(F) -> F1 = erl_syntax:set_precomments(F, []), F2 = erl_syntax_lib:to_comment(F1, ?KILL_PREFIX), - erl_syntax:set_precomments(F2, - erl_syntax:get_precomments(F)). + erl_syntax:set_precomments(F2, erl_syntax:get_precomments(F)). %% --------------------------------------------------------------------- @@ -1138,8 +1167,7 @@ merge_namespaces(Modules, Env) -> [] -> ok; Fs -> - report_warning("interface functions renamed:\n\t~p.", - [Fs]) + report_warning("interface functions renamed:\n\t~p.", [Fs]) end, {M4, Acc2} = merge_namespaces_1(M2, Acc1), Ms = M3 ++ M4, @@ -1550,20 +1578,20 @@ alias_expansions_2(Modules, Table) -> %% Data structure for code transformation environment. --record(code, {module, % = atom() - target, % = atom() - sources, % = ordset(atom()) - static, % = ordset(atom()) - safe, % = ordset(atom()) - preserved, % = bool() - no_headers, % = bool() - notes, % = bool() +-record(code, {module :: atom(), + target :: atom(), + sources :: set(), % set(atom()), + static :: set(), % set(atom()), + safe :: set(), % set(atom()), + preserved :: boolean(), + no_headers :: boolean(), + notes :: notes(), map, % = ({atom(), int()}) -> {atom(), int()} renaming, % = (atom()) -> ({atom(), int()}) -> % {atom(), int()} - expand, % = dict({atom(), int()}, - % {atom(), {atom(), int()}}) - redirect % = dict(atom(), atom()) + expand :: dict(), % = dict({atom(), int()}, + % {atom(), {atom(), int()}}) + redirect :: dict() % = dict(atom(), atom()) }). %% `Trees' must be a list of syntax trees of type `form_list'. The @@ -1657,8 +1685,8 @@ take_header_1([F | Fs], As) -> section_header(Name, Tree, Env) -> N = sets:size(Env#code.sources), - if N > 1, Name /= Env#code.target, Env#code.notes /= no, - Env#code.no_headers /= true -> + if N > 1, Name =/= Env#code.target, Env#code.notes =/= no, + Env#code.no_headers =/= true -> Text = io_lib:fwrite("The following code stems " "from module `~w'.", [Name]), Header = comment([?COMMENT_BAR, "", @@ -2292,11 +2320,11 @@ maybe_modified_1({value, Node1}, Node, Depth, Message, Notes) -> %% Options: %%
%%
`{backup_suffix, string()}'
-%%
`{backups, bool()}'
+%%
`{backups, boolean()}'
%%
`{printer, Function}'
%%
`{stub_dir, filename()}'
%%
`{suffix, string()}'
-%%
`{verbose, bool()}'
+%%
`{verbose, boolean()}'
%%
%% %% See `merge/3' for details on these options. @@ -2304,6 +2332,8 @@ maybe_modified_1({value, Node1}, Node, Depth, Message, Notes) -> %% @see merge/3 %% @see merge_sources/3 +-spec create_stubs([stubDescriptor()], [option()]) -> [string()]. + create_stubs(Stubs, Opts) -> Opts1 = Opts ++ ?DEFAULT_MERGE_OPTS, lists:foldl(fun (S, Fs) -> @@ -2364,10 +2394,16 @@ stub_header(Name, Exports, Attrs) -> ++ make_attributes(Attrs). +%% ===================================================================== + +-type renamings() :: [{atom(), atom()}]. + %% ===================================================================== %% @spec rename(Files::[filename()], Renamings) -> [string()] %% @equiv rename(Files, Renamings, []) +-spec rename([file:filename()], renamings()) -> [string()]. + rename(Files, Renamings) -> rename(Files, Renamings, []). @@ -2408,35 +2444,35 @@ rename(Files, Renamings) -> %% Options: %%
%%
`{backup_suffix, string()}'
-%%
`{backups, bool()}'
+%%
`{backups, boolean()}'
%%
`{printer, Function}'
-%%
`{stubs, bool()}'
+%%
`{stubs, boolean()}'
%%
`{suffix, string()}'
%%
%% See `merge/3' for details on these options. %% %%
-%%
`{comments, bool()}'
-%%
`{preprocess, bool()}'
+%%
`{comments, boolean()}'
+%%
`{preprocess, boolean()}'
%%
%% See `merge_files/4' for details on these options. %% %%
-%%
`{no_banner, bool()}'
+%%
`{no_banner, boolean()}'
%%
%% For the `rename' function, this option is %% `true' by default. See `merge_sources/3' for %% details. %% %%
-%%
`{tidy, bool()}'
+%%
`{tidy, boolean()}'
%%
%% For the `rename' function, this option is %% `false' by default. See `merge_sources/3' for %% details. %% %%
-%%
`{no_headers, bool()}'
+%%
`{no_headers, boolean()}'
%%
`{stub_dir, filename()}'
%%
%% These options are preset by the `rename' function and @@ -2448,6 +2484,8 @@ rename(Files, Renamings) -> %% @see merge_sources/3 %% @see merge_files/4 +-spec rename([file:filename()], renamings(), [term()]) -> [string()]. + rename(Files, Renamings, Opts) -> Dict = case is_atom_map(Renamings) of true -> diff --git a/lib/syntax_tools/src/prettypr.erl b/lib/syntax_tools/src/prettypr.erl index 4dd95a2b0832..1868f63e5404 100644 --- a/lib/syntax_tools/src/prettypr.erl +++ b/lib/syntax_tools/src/prettypr.erl @@ -50,29 +50,22 @@ %% --------------------------------------------------------------------- -%% XXX: just an approximation --type deep_string() :: [char() | [_]]. - -%% XXX: poor man's document() until recursive data types are supported --type doc() :: 'null' - | {'text' | 'fit', _} - | {'nest' | 'beside' | 'above' | 'union', _, _} - | {'sep' | 'float', _, _, _}. +-type deep_string() :: [char() | deep_string()]. %% Document structures fully implemented and available to the user: -record(text, {s :: deep_string()}). --record(nest, {n :: integer(), d :: doc()}). --record(beside, {d1 :: doc(), d2 :: doc()}). --record(above, {d1 :: doc(), d2 :: doc()}). --record(sep, {ds :: [doc()], i = 0 :: integer(), p = false :: boolean()}). +-record(nest, {n :: integer(), d :: document()}). +-record(beside, {d1 :: document(), d2 :: document()}). +-record(above, {d1 :: document(), d2 :: document()}). +-record(sep, {ds :: [document()], i = 0 :: integer(), + p = false :: boolean()}). %% Document structure which is not clear whether it is fully implemented: --record(float, {d :: doc(), h :: integer(), v :: integer()}). +-record(float, {d :: document(), h :: integer(), v :: integer()}). %% Document structures not available to the user: --record(union, {d1 :: doc(), d2 :: doc()}). --record(fit, {d :: doc()}). - +-record(union, {d1 :: document(), d2 :: document()}). +-record(fit, {d :: document()}). %% --------------------------------------------------------------------- %% A small warning for hackers: it's fairly easy to break this @@ -637,43 +630,43 @@ flatrev([], As, []) -> %% Contexts: %% %% #c_best_nest{w = integer(), r = integer(), i = integer()} -%% #c_above_nest{d = doc(), i = integer(), c = ctxt()} -%% #c_beside{d = doc(), c = ctxt()} +%% #c_above_nest{d = document(), i = integer(), c = ctxt()} +%% #c_beside{d = document(), c = ctxt()} %% #c_text_beside{s = string(), c = ctxt()} -%% #c_sep_nest{ds = [doc()], i = integer(), p = boolean(), +%% #c_sep_nest{ds = [document()], i = integer(), p = boolean(), %% c = ctxt()} %% #c_best_nest_or{w = integer(), r = integer(), i = integer(), -%% d = doc()} +%% d = document()} %% #c_fit{c = ctxt()} --record(c_best_nest, {w, r, i}). %% best(w, r, nest(i, *)) +%% best(w, r, nest(i, *)) +-record(c_best_nest, {w :: integer(), r :: integer(), i :: integer()}). --record(c_above_nest, {d, i = 0, c}). %% above(*, nest(i, d)) +%% above(*, nest(i, d)) +-record(c_above_nest, {d :: document(), i = 0 :: integer(), c :: ctxt()}). --record(c_beside, {d, c}). %% beside(*, d) +-record(c_beside, {d :: document(), c :: ctxt()}). %% beside(*, d) --record(c_text_beside, {s, c}). %% beside(text(s), *) +-record(c_text_beside, {s :: string(), c :: ctxt()}). %% beside(text(s), *) %% p = false => sep([* | map(nest i, ds)]) %% p = true => par([* | map(nest i, ds)]) --record(c_sep_nest, {ds, i, p, c}). +-record(c_sep_nest, {ds :: [document()], i :: integer(), + p :: boolean(), c :: ctxt()}). --record(c_best_nest_or, {w, r, i, d}). %% nicest( - %% best(w, r, - %% nest(i, *)), - %% best(w, r, d)) +%% nicest(best(w, r, nest(i, *)), best(w, r, d)) +-record(c_best_nest_or, {w :: integer(), r :: integer(), + i :: integer(), d :: document()}). --record(c_fit, {c}). %% fit(*) +-record(c_fit, {c :: ctxt()}). %% fit(*) --record(c_float_beside, {d, h, v, c}). %% beside( - %% float(d, h, - %% v), - %% *) --record(c_float_above_nest, {d, h, v, i, c}). %% above( - %% float(d, h, - %% v), - %% nest(i, *)) +%% beside(float(d, h, v), *) +-record(c_float_beside, {d :: document(), h :: integer(), + v :: integer(), c :: ctxt()}). +%% above(float(d, h, v), nest(i, *)) +-record(c_float_above_nest, {d :: document(), h :: integer(), + v :: integer(), i :: integer(), c :: ctxt()}). %% Contexts introduced: In case: %% @@ -687,6 +680,11 @@ flatrev([], As, []) -> %% c_float_beside float (c_beside) %% c_float_above_nest float (c_above_nest) +-type ctxt() :: #c_best_nest{} | #c_above_nest{} + | #c_beside{} | #c_text_beside{} + | #c_sep_nest{} | #c_best_nest_or{} + | #c_fit{} | #c_float_beside{} | #c_float_above_nest{}. + %% Entry point for the layout algorithm: -spec best(document(), integer(), integer()) -> 'empty' | document(). From abe48c24c115fd629063653eef7bdabd0f82fbbc Mon Sep 17 00:00:00 2001 From: Kostis Sagonas Date: Tue, 16 Feb 2010 16:33:11 +0200 Subject: [PATCH 8/8] dialyzer: Fix system_limit exception in race analysis dialyzer_callgraph: Avoid creation of too many ets tables by the race analysis. dialyzer_dataflow: The digraph returned by the race analysis should not be the translated one. --- lib/dialyzer/src/dialyzer_callgraph.erl | 29 +++++++++++++++---------- lib/dialyzer/src/dialyzer_dataflow.erl | 10 +++++++-- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/lib/dialyzer/src/dialyzer_callgraph.erl b/lib/dialyzer/src/dialyzer_callgraph.erl index 1f79e164492d..696d5049788d 100644 --- a/lib/dialyzer/src/dialyzer_callgraph.erl +++ b/lib/dialyzer/src/dialyzer_callgraph.erl @@ -55,8 +55,8 @@ -export([cleanup/1, get_digraph/1, get_named_tables/1, get_public_tables/1, get_race_code/1, get_race_detection/1, race_code_new/1, - put_race_code/2, put_race_detection/2, put_named_tables/2, - put_public_tables/2, put_behaviour_api_calls/2, + put_digraph/2, put_race_code/2, put_race_detection/2, + put_named_tables/2, put_public_tables/2, put_behaviour_api_calls/2, get_behaviour_api_calls/1]). -include("dialyzer.hrl"). @@ -611,15 +611,17 @@ digraph_reaching_subgraph(Funs, DG) -> -spec cleanup(callgraph()) -> callgraph(). -cleanup(#callgraph{name_map = NameMap, - rev_name_map = RevNameMap, - public_tables = PublicTables, - named_tables = NamedTables, - race_code = RaceCode}) -> - #callgraph{name_map = NameMap, - rev_name_map = RevNameMap, - public_tables = PublicTables, - named_tables = NamedTables, +cleanup(#callgraph{digraph = Digraph, + name_map = NameMap, + rev_name_map = RevNameMap, + public_tables = PublicTables, + named_tables = NamedTables, + race_code = RaceCode}) -> + #callgraph{digraph = Digraph, + name_map = NameMap, + rev_name_map = RevNameMap, + public_tables = PublicTables, + named_tables = NamedTables, race_code = RaceCode}. -spec get_digraph(callgraph()) -> digraph(). @@ -652,6 +654,11 @@ get_race_detection(#callgraph{race_detection = RD}) -> race_code_new(Callgraph) -> Callgraph#callgraph{race_code = dict:new()}. +-spec put_digraph(digraph(), callgraph()) -> callgraph(). + +put_digraph(Digraph, Callgraph) -> + Callgraph#callgraph{digraph = Digraph}. + -spec put_race_code(dict(), callgraph()) -> callgraph(). put_race_code(RaceCode, Callgraph) -> diff --git a/lib/dialyzer/src/dialyzer_dataflow.erl b/lib/dialyzer/src/dialyzer_dataflow.erl index 7fb309497ad2..dada88c8bf00 100644 --- a/lib/dialyzer/src/dialyzer_dataflow.erl +++ b/lib/dialyzer/src/dialyzer_dataflow.erl @@ -291,10 +291,16 @@ analyze_module(Tree, Plt, Callgraph, Records, GetWarnings) -> case BehaviourTranslations of [] -> dialyzer_races:race(State5); Behaviours -> + Callgraph2 = State5#state.callgraph, + Digraph = dialyzer_callgraph:get_digraph(Callgraph2), TranslatedCallgraph = dialyzer_behaviours:translate_callgraph(Behaviours, Module, - State5#state.callgraph), - dialyzer_races:race(State5#state{callgraph = TranslatedCallgraph}) + Callgraph2), + St = + dialyzer_races:race(State5#state{callgraph = TranslatedCallgraph}), + Callgraph3 = dialyzer_callgraph:put_digraph(Digraph, + St#state.callgraph), + St#state{callgraph = Callgraph3} end; false -> state__restore_race_code(