diff options
Diffstat (limited to 'lib/dialyzer/src/dialyzer.erl')
-rw-r--r-- | lib/dialyzer/src/dialyzer.erl | 188 |
1 files changed, 150 insertions, 38 deletions
diff --git a/lib/dialyzer/src/dialyzer.erl b/lib/dialyzer/src/dialyzer.erl index 83d3c03e7e..ecfc509e34 100644 --- a/lib/dialyzer/src/dialyzer.erl +++ b/lib/dialyzer/src/dialyzer.erl @@ -29,6 +29,8 @@ %%-------------------------------------------------------------------- -export([plain_cl/0, run/1, + run_report_modules_analyzed/1, + run_report_modules_changed_and_analyzed/1, gui/0, gui/1, plt_info/1, @@ -41,6 +43,14 @@ %% Interfaces: %% - plain_cl/0 : to be used ONLY by the dialyzer C program. %% - run/1: Erlang interface for a command line-like analysis +%% - run_report_modules_analyzed/1: Erlang interface for a command line-like +%% analysis, but also returns the list of modules that +%% had to be analyzed to compute the result +%% - run_report_modules_analyzed/1: Erlang interface for a command line-like +%% analysis, but also returns the list of modules that +%% had to be analyzed to compute the result, plus the +%% set of modules that have changed since the PLT was +%% created (if applicable) %% - gui/0/1: Erlang interface for the gui. %% - format_warning/1: Get the string representation of a warning. %% - format_warning/2: Likewise, but with an option whether @@ -88,6 +98,7 @@ cl_check_init(#options{analysis_type = AnalType} = Opts) -> plt_build -> {ok, ?RET_NOTHING_SUSPICIOUS}; plt_add -> {ok, ?RET_NOTHING_SUSPICIOUS}; plt_remove -> {ok, ?RET_NOTHING_SUSPICIOUS}; + incremental -> {ok, ?RET_NOTHING_SUSPICIOUS}; Other when Other =:= succ_typings; Other =:= plt_check -> F = fun() -> NewOpts = Opts#options{analysis_type = plt_check}, @@ -109,16 +120,37 @@ print_plt_info(#options{init_plts = PLTs, output_file = OutputFile}) -> get_plt_info([PLT|PLTs]) -> String = - case dialyzer_plt:included_files(PLT) of - {ok, Files} -> - io_lib:format("The PLT ~ts includes the following files:\n~tp\n\n", - [PLT, Files]); - {error, read_error} -> - Msg = io_lib:format("Could not read the PLT file ~tp\n\n", [PLT]), - throw({dialyzer_error, Msg}); - {error, no_such_file} -> - Msg = io_lib:format("The PLT file ~tp does not exist\n\n", [PLT]), - throw({dialyzer_error, Msg}) + case dialyzer_plt:plt_kind(PLT) of + cplt -> + case dialyzer_cplt:included_files(PLT) of + {ok, Files} -> + io_lib:format("The classic PLT ~ts includes the following files:\n~tp\n\n", + [PLT, Files]); + {error, read_error} -> + Msg = io_lib:format("Could not read the classic PLT file ~tp\n\n", [PLT]), + throw({dialyzer_error, Msg}); + {error, no_such_file} -> + Msg = io_lib:format("The classic PLT file ~tp does not exist\n\n", [PLT]), + throw({dialyzer_error, Msg}) + end; + iplt -> + case dialyzer_iplt:included_modules(PLT) of + {ok, Modules} -> + io_lib:format("The incremental PLT ~ts includes the following modules:\n~tp\n\n", + [PLT, Modules]); + {error, read_error} -> + Msg = io_lib:format("Could not read the incremental PLT file ~tp\n\n", [PLT]), + throw({dialyzer_error, Msg}); + {error, no_such_file} -> + Msg = io_lib:format("The incremental PLT file ~tp does not exist\n\n", [PLT]), + throw({dialyzer_error, Msg}) + end; + bad_file -> + Msg = io_lib:format("Could not read the PLT file ~tp\n\n", [PLT]), + throw({dialyzer_error, Msg}); + no_file -> + Msg = io_lib:format("The PLT file ~tp does not exist\n\n", [PLT]), + throw({dialyzer_error, Msg}) end, String ++ get_plt_info(PLTs); get_plt_info([]) -> "". @@ -142,10 +174,17 @@ do_print_plt_info(PLTInfo, OutputFile) -> end. cl(Opts) -> - F = fun() -> - {Ret, _Warnings} = dialyzer_cl:start(Opts), - Ret - end, + F = + fun() -> + {Ret, _Warnings} = + case Opts#options.analysis_type of + incremental -> + dialyzer_incremental:start(Opts); + _ -> + dialyzer_cl:start(Opts) + end, + Ret + end, doit(F). -spec run(Options) -> Warnings when @@ -153,15 +192,41 @@ cl(Opts) -> Warnings :: [dial_warning()]. run(Opts) -> + {Warnings, _ModulesAnalyzed} = run_report_modules_analyzed(Opts), + Warnings. + +-spec run_report_modules_analyzed(Options) -> {Warnings, ModulesAnalyzed} when + Options :: [dial_option()], + Warnings :: [dial_warning()], + ModulesAnalyzed :: [module()]. + +-spec run_report_modules_changed_and_analyzed(Options) -> {Warnings, ModulesChanged, ModulesAnalyzed} when + Options :: [dial_option()], + Warnings :: [dial_warning()], + ModulesChanged :: undefined | [module()], + ModulesAnalyzed :: [module()]. + +run_report_modules_analyzed(Opts) -> + {Warnings, _ModulesChanged, ModulesAnalyzed} = run_report_modules_changed_and_analyzed(Opts), + {Warnings, ModulesAnalyzed}. + +run_report_modules_changed_and_analyzed(Opts) -> try dialyzer_options:build([{report_mode, quiet}, {erlang_mode, true}|Opts]) of {error, Msg} -> throw({dialyzer_error, Msg}); OptsRecord -> ok = check_init(OptsRecord), - case dialyzer_cl:start(OptsRecord) of - {?RET_DISCREPANCIES, Warnings} -> Warnings; - {?RET_NOTHING_SUSPICIOUS, _} -> [] + AnalysisResult = + case OptsRecord#options.analysis_type of + incremental -> + dialyzer_incremental:start_report_modules_changed_and_analyzed(OptsRecord); + _ -> + dialyzer_cl:start_report_modules_changed_and_analyzed(OptsRecord) + end, + case AnalysisResult of + {{?RET_DISCREPANCIES, Warnings}, ModulesChanged, ModulesAnalyzed} -> {Warnings, ModulesChanged, ModulesAnalyzed}; + {{?RET_NOTHING_SUSPICIOUS, _}, ModulesChanged, ModulesAnalyzed} -> {[], ModulesChanged, ModulesAnalyzed} end catch throw:{dialyzer_error, ErrorMsg} -> @@ -219,15 +284,26 @@ check_gui_options(#options{analysis_type = Mode}) -> throw({dialyzer_error, Msg}). -spec plt_info(Plt) -> - {'ok', Result} | {'error', Reason} when + {'ok', ClassicResult | IncrementalResult } | {'error', Reason} when Plt :: file:filename(), - Result :: [{'files', [file:filename()]}], + ClassicResult :: [{'files', [file:filename()]}], + IncrementalResult :: {incremental, [{'modules', [module()]}]}, Reason :: 'not_valid' | 'no_such_file' | 'read_error'. plt_info(Plt) -> - case dialyzer_plt:included_files(Plt) of - {ok, Files} -> {ok, [{files, Files}]}; - Error -> Error + case dialyzer_plt:plt_kind(Plt) of + cplt -> + case dialyzer_cplt:included_files(Plt) of + {ok, Files} -> {ok, [{files, Files}]}; + Error -> Error + end; + iplt -> + case dialyzer_iplt:included_modules(Plt) of + {ok, Modules} -> {ok, {incremental, [{modules, Modules}]}}; + Error -> Error + end; + bad_file -> {error, not_valid}; + no_file -> {error, no_such_file} end. @@ -339,8 +415,8 @@ message_to_string({app_call, [M, F, Args, Culprit, ExpectedType, FoundType]}, [M, F, a(Args, I), c(Culprit, I), t(ExpectedType, I), t(FoundType, I)]); message_to_string({bin_construction, [Culprit, Size, Seg, Type]}, I, _E) -> - io_lib:format("Binary construction will fail since the ~s field ~s in" - " segment ~s has type ~s\n", + io_lib:format("Binary construction will fail since the ~ts field ~ts in" + " segment ~ts has type ~ts\n", [Culprit, c(Size, I), c(Seg, I), t(Type, I)]); message_to_string({call, [M, F, Args, ArgNs, FailReason, SigArgs, SigRet, Contract]}, I, _E) -> @@ -440,9 +516,16 @@ message_to_string({contract_range, [Contract, M, F, ArgStrings, " return for ~tw~ts on position ~s is ~ts\n", [con(M, F, Contract, I), F, a(ArgStrings, I), pos(Location, E), t(CRet, I)]); -message_to_string({invalid_contract, [M, F, A, Sig]}, I, _E) -> - io_lib:format("Invalid type specification for function ~w:~tw/~w." - " The success typing is ~ts\n", [M, F, A, sig(Sig, I)]); +message_to_string({invalid_contract, [M, F, A, none, Contract, Sig]}, I, _E) -> + io_lib:format("Invalid type specification for function ~w:~tw/~w.\n" + " The success typing is ~ts\n" + " But the spec is ~ts\n", [M, F, A, con(M, F, Sig, I), con(M, F, Contract, I)]); +message_to_string({invalid_contract, [M, F, A, InvalidContractDetails, Contract, Sig]}, I, _E) -> + io_lib:format("Invalid type specification for function ~w:~tw/~w.\n" + " The success typing is ~ts\n" + " But the spec is ~ts\n" + "~ts", + [M, F, A, con(M, F, Sig, I), con(M, F, Contract, I), format_invalid_contract_details(InvalidContractDetails)]); message_to_string({contract_with_opaque, [M, F, A, OpaqueType, SigType]}, I, _E) -> io_lib:format("The specification for ~w:~tw/~w" @@ -459,7 +542,7 @@ message_to_string({missing_range, [M, F, A, ExtraRanges, ContrRange]}, I, _E) -> [M, F, A, t(ExtraRanges, I), t(ContrRange, I)]); message_to_string({overlapping_contract, [M, F, A]}, _I, _E) -> io_lib:format("Overloaded contract for ~w:~tw/~w has overlapping domains;" - " such contracts are currently unsupported and are simply ignored\n", + " such contracts cannot establish a dependency between the overloaded input and output types\n", [M, F, A]); message_to_string({spec_missing_fun, [M, F, A]}, _I, _E) -> io_lib:format("Contract for function that does not exist: ~w:~tw/~w\n", @@ -510,19 +593,19 @@ message_to_string({callback_type_mismatch, [B, F, A, ST, CT]}, I, _E) -> " the callback of the ~w behaviour\n", [F, A, t("("++ST++")", I), t(CT, I), B]); message_to_string({callback_arg_type_mismatch, [B, F, A, N, ST, CT]}, I, _E) -> - io_lib:format("The inferred type for the ~s argument of ~tw/~w (~ts) is" - " not a supertype of ~ts, which is expected type for this" + io_lib:format("The inferred type for the ~s argument of ~tw/~w (~ts)" + " has nothing in common with ~ts, which is expected type for this" " argument in the callback of the ~w behaviour\n", [ordinal(N), F, A, t(ST, I), t(CT, I), B]); message_to_string({callback_spec_type_mismatch, [B, F, A, ST, CT]}, I, _E) -> - io_lib:format("The return type ~ts in the specification of ~tw/~w is not a" - " subtype of ~ts, which is the expected return type for the" + io_lib:format("The return type ~ts in the specification of ~tw/~w has nothing" + " in common with ~ts, which is the expected return type for the" " callback of the ~w behaviour\n", [t(ST, I), F, A, t(CT, I), B]); message_to_string({callback_spec_arg_type_mismatch, [B, F, A, N, ST, CT]}, I, _E) -> - io_lib:format("The specified type for the ~ts argument of ~tw/~w (~ts) is" - " not a supertype of ~ts, which is expected type for this" + io_lib:format("The specified type for the ~ts argument of ~tw/~w (~ts) has" + " nothing in common with ~ts, which is expected type for this" " argument in the callback of the ~w behaviour\n", [ordinal(N), F, A, t(ST, I), t(CT, I), B]); message_to_string({callback_missing, [B, F, A]}, _I, _E) -> @@ -545,6 +628,27 @@ message_to_string({unknown_behaviour, B}, _I, _E) -> %% Auxiliary functions below %%----------------------------------------------------------------------------- +format_invalid_contract_details({InvalidArgIdxs, IsRangeInvalid}) -> + ArgOrd = form_position_string(InvalidArgIdxs), + ArgDesc = + case InvalidArgIdxs of + [] -> ""; + [_] -> io_lib:format("They do not overlap in the ~ts argument", [ArgOrd]); + [_|_] -> io_lib:format("They do not overlap in the ~ts arguments", [ArgOrd]) + end, + RangeDesc = + case IsRangeInvalid of + true -> "return types do not overlap"; + false -> "" + end, + case {ArgDesc, RangeDesc} of + {"", ""} -> ""; + {"", [_|_]} -> io_lib:format(" The ~ts\n", [RangeDesc]); + {[_|_], ""} -> io_lib:format(" ~ts\n", [ArgDesc]); + {[_|_], [_|_]} -> io_lib:format(" ~ts, and the ~ts\n", [ArgDesc, RangeDesc]) + end. + + call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet, {IsOverloaded, Contract}, I) -> PositionString = form_position_string(ArgNs), @@ -623,10 +727,18 @@ form_position_string(ArgNs) -> 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]). +ordinal(N) when is_integer(N), + ((N rem 100) =:= 11) orelse + ((N rem 100) =:= 12) orelse + ((N rem 100) =:= 13) -> + io_lib:format("~Bth", [N]); +ordinal(N) when is_integer(N) -> + case min(N rem 10, 4) of + 1 -> io_lib:format("~Bst", [N]); + 2 -> io_lib:format("~Bnd", [N]); + 3 -> io_lib:format("~Brd", [N]); + _ -> io_lib:format("~Bth", [N]) + end. %% Functions that parse type strings, literal strings, and contract %% strings. Return strings formatted by erl_pp. |