summaryrefslogtreecommitdiff
path: root/lib/dialyzer/test/cplt_SUITE.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dialyzer/test/cplt_SUITE.erl')
-rw-r--r--lib/dialyzer/test/cplt_SUITE.erl1012
1 files changed, 1012 insertions, 0 deletions
diff --git a/lib/dialyzer/test/cplt_SUITE.erl b/lib/dialyzer/test/cplt_SUITE.erl
new file mode 100644
index 0000000000..27e50b54fc
--- /dev/null
+++ b/lib/dialyzer/test/cplt_SUITE.erl
@@ -0,0 +1,1012 @@
+-module(cplt_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("stdlib/include/assert.hrl").
+-include_lib("dialyzer/src/dialyzer.hrl").
+-include("dialyzer_test_constants.hrl").
+
+-export([suite/0, all/0,
+ build_plt/1, beam_tests/1, update_plt/1,
+ local_fun_same_as_callback/1,
+ remove_plt/1, run_plt_check/1, run_succ_typings/1,
+ bad_dialyzer_attr/1, merge_plts/1, bad_record_type/1,
+ letrec_rvals/1,
+ missing_plt_file/1,
+ build_xdg_plt/1,
+ mod_dep_from_behaviour/1,
+ mod_dep_from_record_definition_field_value_default_used/1,
+ mod_dep_from_record_definition_field_value_default_unused/1,
+ mod_dep_from_record_definition_field_type/1,
+ mod_dep_from_overloaded_callback/1,
+ mod_dep_from_exported_overloaded_fun_spec/1,
+ mod_dep_from_unexported_overloaded_fun_spec/1,
+ mod_dep_from_callback_constraint/1,
+ mod_dep_from_unexported_fun_spec_constraint/1,
+ mod_dep_from_exported_fun_spec_constraint/1,
+ mod_dep_from_exported_type/1,
+ mod_dep_from_callback_return/1,
+ mod_dep_from_callback_args/1,
+ mod_dep_from_unexported_opaque_type_args/1,
+ mod_dep_from_exported_opaque_type_args/1,
+ mod_dep_from_unexported_opaque_type/1,
+ mod_dep_from_exported_opaque_type/1,
+ mod_dep_from_unexported_type_args/1,
+ mod_dep_from_exported_type_args/1,
+ mod_dep_from_unexported_fun_spec_args/1,
+ mod_dep_from_exported_fun_spec_args/1,
+ mod_dep_from_unexported_fun_spec_return/1,
+ mod_dep_from_exported_fun_spec_return/1,
+ mod_dep_from_unexported_type/1,
+ adding_warning_apps_after_a_run_without_them_causes_any_new_warnings_to_be_reported/1,
+ removing_warning_apps_after_a_run_with_them_causes_any_warnings_for_the_removed_apps_not_to_be_reported/1,
+ removing_legal_warnings_with_existing_stored_warnings_in_plt_does_not_result_in_old_warnings_being_printed/1,
+ adding_legal_warnings_with_existing_stored_warnings_in_plt_results_in_new_warnings_being_printed/1
+ ]).
+
+suite() ->
+ [{timetrap, ?plt_timeout}].
+
+all() -> [build_plt, build_xdg_plt, beam_tests, update_plt, run_plt_check,
+ remove_plt, run_succ_typings, local_fun_same_as_callback,
+ bad_dialyzer_attr, merge_plts, bad_record_type,
+ letrec_rvals,
+ missing_plt_file,
+ mod_dep_from_behaviour,
+ mod_dep_from_record_definition_field_value_default_used,
+ mod_dep_from_record_definition_field_value_default_unused,
+ mod_dep_from_record_definition_field_type,
+ mod_dep_from_overloaded_callback,
+ mod_dep_from_exported_overloaded_fun_spec,
+ mod_dep_from_unexported_overloaded_fun_spec,
+ mod_dep_from_callback_constraint,
+ mod_dep_from_unexported_fun_spec_constraint,
+ mod_dep_from_exported_fun_spec_constraint,
+ mod_dep_from_exported_type,
+ mod_dep_from_callback_return,
+ mod_dep_from_callback_args,
+ mod_dep_from_unexported_opaque_type_args,
+ mod_dep_from_exported_opaque_type_args,
+ mod_dep_from_unexported_opaque_type,
+ mod_dep_from_exported_opaque_type,
+ mod_dep_from_unexported_type_args,
+ mod_dep_from_exported_type_args,
+ mod_dep_from_unexported_fun_spec_args,
+ mod_dep_from_exported_fun_spec_args,
+ mod_dep_from_unexported_fun_spec_return,
+ mod_dep_from_exported_fun_spec_return,
+ mod_dep_from_unexported_type,
+ adding_warning_apps_after_a_run_without_them_causes_any_new_warnings_to_be_reported,
+ removing_warning_apps_after_a_run_with_them_causes_any_warnings_for_the_removed_apps_not_to_be_reported,
+ removing_legal_warnings_with_existing_stored_warnings_in_plt_does_not_result_in_old_warnings_being_printed,
+ adding_legal_warnings_with_existing_stored_warnings_in_plt_results_in_new_warnings_being_printed
+ ].
+
+build_plt(Config) ->
+ OutDir = proplists:get_value(priv_dir, Config),
+ case dialyzer_common:check_plt(OutDir) of
+ ok -> ok;
+ fail -> ct:fail(plt_build_fail)
+ end.
+
+build_xdg_plt(Config) ->
+ TestHome = filename:join(?config(priv_dir, Config), ?FUNCTION_NAME),
+
+ %% We change the $HOME of the emulator to run this test
+ HomeEnv = case os:type() of
+ {win32, _} ->
+ [Drive | Path] = filename:split(TestHome),
+ [{"APPDATA", filename:join(TestHome,"AppData")},
+ {"HOMEDRIVE", Drive}, {"HOMEPATH", filename:join(Path)}];
+ _ ->
+ [{"HOME", TestHome}]
+ end,
+
+ {ok, Peer, Node} = ?CT_PEER(#{ env => HomeEnv }),
+
+ erpc:call(
+ Node,
+ fun() ->
+ ?assertMatch([], dialyzer:run(
+ [{analysis_type, plt_build},
+ {apps, [erts]},
+ {warnings, [no_unknown]}])),
+ ?assertMatch(
+ {ok,_}, file:read_file(
+ filename:join(
+ filename:basedir(user_cache, "erlang"),
+ ".dialyzer_plt")))
+ end),
+
+ peer:stop(Peer).
+
+beam_tests(Config) when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Plt = filename:join(PrivDir, "beam_tests.plt"),
+ Src = <<"
+ -module(no_auto_import).
+
+ %% Copied from erl_lint_SUITE.erl, clash6
+
+ -export([size/1]).
+
+ size([]) ->
+ 0;
+ size({N,_}) ->
+ N;
+ size([_|T]) ->
+ 1+size(T).
+ ">>,
+ Opts = [no_auto_import],
+ {ok, BeamFile} = compile(Config, Src, no_auto_import, Opts),
+ [] = run_dialyzer(plt_build, [BeamFile], [{output_plt, Plt}]),
+ ok.
+
+run_plt_check(Config) when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Plt = filename:join(PrivDir, "run_plt_check.plt"),
+ Mod1 = <<"
+ -module(run_plt_check1).
+ ">>,
+
+ Mod2A = <<"
+ -module(run_plt_check2).
+ ">>,
+
+ {ok, BeamFile1} = compile(Config, Mod1, run_plt_check1, []),
+ {ok, BeamFile2} = compile(Config, Mod2A, run_plt_check2, []),
+ [] = run_dialyzer(plt_build, [BeamFile1, BeamFile2], [{output_plt, Plt}]),
+
+ Mod2B = <<"
+ -module(run_plt_check2).
+
+ -export([call/1]).
+
+ call(X) -> run_plt_check1:call(X).
+ ">>,
+
+ {ok, BeamFile2} = compile(Config, Mod2B, run_plt_check2, []),
+
+ %% callgraph warning as run_plt_check2:call/1 makes a call to unexported
+ %% function run_plt_check1:call/1.
+ [_] = run_dialyzer(plt_check, [], [{init_plt, Plt}]),
+
+ ok.
+
+run_succ_typings(Config) when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Plt = filename:join(PrivDir, "run_succ_typings.plt"),
+ Mod1A = <<"
+ -module(run_succ_typings1).
+
+ -export([call/0]).
+
+ call() -> a.
+ ">>,
+
+ {ok, BeamFile1} = compile(Config, Mod1A, run_succ_typings1, []),
+ [] = run_dialyzer(plt_build, [BeamFile1], [{output_plt, Plt}]),
+
+ Mod1B = <<"
+ -module(run_succ_typings1).
+
+ -export([call/0]).
+
+ call() -> b.
+ ">>,
+
+ Mod2 = <<"
+ -module(run_succ_typings2).
+
+ -export([call/0]).
+
+ -spec call() -> b.
+ call() -> run_succ_typings1:call().
+ ">>,
+
+ {ok, BeamFile1} = compile(Config, Mod1B, run_succ_typings1, []),
+ {ok, BeamFile2} = compile(Config, Mod2, run_succ_typings2, []),
+ %% contract types warning as run_succ_typings2:call/0 makes a call to
+ %% run_succ_typings1:call/0, which returns a (not b) in the PLT.
+ [_] = run_dialyzer(succ_typings, [BeamFile2],
+ [{check_plt, false}, {init_plt, Plt}]),
+ %% warning not returned as run_succ_typings1 is updated in the PLT.
+ [] = run_dialyzer(succ_typings, [BeamFile2],
+ [{check_plt, true}, {init_plt, Plt}]),
+
+ ok.
+
+%%% [James Fish:]
+%%% If a function is removed from a module and the module has previously
+%%% been added to a PLT, the function will not be removed from PLT when
+%%% the PLT is checked. This results in dialyzer failing to produce a
+%%% callgraph warning when doing success typings analysis if the remove
+%%% function is still called in another module
+%%% As the function is not removed from the PLT a prior warning, such as a
+%%% contract types warning, might be emitted when the removed function
+%%% nolonger exists.
+update_plt(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Prog1 = <<"-module(plt_gc).
+ -export([one/0]).
+ one() ->
+ one.">>,
+ {ok, Beam} = compile(Config, Prog1, plt_gc, []),
+
+ ErlangBeam = case code:where_is_file("erlang.beam") of
+ non_existing ->
+ filename:join([code:root_dir(),
+ "erts", "preloaded", "ebin",
+ "erlang.beam"]);
+ EBeam ->
+ EBeam
+ end,
+ Plt = filename:join(PrivDir, "plt_gc.plt"),
+ Opts = [{check_plt, true}, {from, byte_code}],
+ [] = dialyzer:run([{analysis_type, plt_build},
+ {files, [Beam, ErlangBeam]},
+ {output_plt, Plt},
+ {warnings, [no_unknown]}] ++ Opts),
+
+ Prog2 = <<"-module(plt_gc).
+ -export([two/0]).
+ two() ->
+ two.">>,
+ {ok, Beam} = compile(Config, Prog2, plt_gc, []),
+
+ Test = <<"-module(test).
+ -export([test/0]).
+ -spec test() -> test.
+ test() ->
+ plt_gc:one().">>,
+ {ok, TestBeam} = compile(Config, Test, test, []),
+ [{warn_callgraph, {_Filename, {5,19}}, {call_to_missing, [plt_gc,one,0]}}] =
+ dialyzer:run([{analysis_type, succ_typings},
+ {files, [TestBeam]},
+ {init_plt, Plt}] ++ Opts),
+ ok.
+
+
+%%% If a behaviour module contains an non-exported function with the same name
+%%% as one of the behaviour's callbacks, the callback info was inadvertently
+%%% deleted from the PLT as the dialyzer_plt:delete_list/2 function was cleaning
+%%% up the callback table. This bug was reported by Brujo Benavides.
+
+local_fun_same_as_callback(Config) when is_list(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Prog1 =
+ <<"-module(bad_behaviour).
+ -callback bad() -> bad.
+ -export([publicly_bad/0]).
+
+ %% @doc This function is here just to avoid the 'unused' warning for bad/0
+ publicly_bad() -> bad().
+
+ %% @doc This function overlaps with the callback with the same name, and
+ %% that was an issue for dialyzer since it's a private function.
+ bad() -> bad.">>,
+ {ok, Beam} = compile(Config, Prog1, bad_behaviour, []),
+
+ ErlangBeam = case code:where_is_file("erlang.beam") of
+ non_existing ->
+ filename:join([code:root_dir(),
+ "erts", "preloaded", "ebin",
+ "erlang.beam"]);
+ EBeam ->
+ EBeam
+ end,
+ Plt = filename:join(PrivDir, "plt_bad_behaviour.plt"),
+ Opts = [{check_plt, true}, {from, byte_code}],
+ [] = dialyzer:run([{analysis_type, plt_build},
+ {files, [Beam, ErlangBeam]},
+ {output_plt, Plt},
+ {warnings, [no_unknown]}] ++ Opts),
+
+ Prog2 =
+ <<"-module(bad_child).
+ -behaviour(bad_behaviour).
+
+ -export([bad/0]).
+
+ %% @doc This function incorrectly implements bad_behaviour.
+ bad() -> not_bad.">>,
+ {ok, TestBeam} = compile(Config, Prog2, bad_child, []),
+
+ [{warn_behaviour, _,
+ {callback_type_mismatch,
+ [bad_behaviour,bad,0,"'not_bad'","'bad'"]}}] =
+ dialyzer:run([{analysis_type, succ_typings},
+ {files, [TestBeam]},
+ {init_plt, Plt}] ++ Opts),
+ ok.
+
+%%% [James Fish:]
+%%% Dialyzer always asserts that files and directories passed in its
+%%% options exist. Therefore it is not possible to remove a beam/module
+%%% from a PLT when the beam file no longer exists. Dialyzer should not to
+%%% check files exist on disk when removing from the PLT.
+remove_plt(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Prog1 = <<"-module(m1).
+ -export([t/0]).
+ t() ->
+ m2:a(a).">>,
+ {ok, Beam1} = compile(Config, Prog1, m1, []),
+
+ Prog2 = <<"-module(m2).
+ -export([a/1]).
+ a(A) when is_integer(A) -> A.">>,
+ {ok, Beam2} = compile(Config, Prog2, m2, []),
+
+ Plt = filename:join(PrivDir, "remove.plt"),
+ Opts = [{check_plt, true}, {from, byte_code}],
+
+ [{warn_return_no_exit, _, {no_return,[only_normal,t,0]}},
+ {warn_failing_call, _, {call, [m2,a,"('a')",_,_,_,_,_]}}] =
+ dialyzer:run([{analysis_type, plt_build},
+ {files, [Beam1, Beam2]},
+ {get_warnings, true},
+ {output_plt, Plt},
+ {warnings, [no_unknown]}] ++ Opts),
+
+ [] = dialyzer:run([{init_plt, Plt},
+ {files, [Beam2]},
+ {analysis_type, plt_remove},
+ {warnings, [no_unknown]}]),
+
+ [] = dialyzer:run([{analysis_type, succ_typings},
+ {files, [Beam1]},
+ {init_plt, Plt},
+ {warnings, [no_unknown]}] ++ Opts),
+ ok.
+
+%% ERL-283, OTP-13979. As of OTP-14323 this test no longer does what
+%% it is designed to do--the linter stops every attempt to run the
+%% checks of Dialyzer's on bad dialyzer attributes. For the time
+%% being, the linter's error message are checked instead. The test
+%% needs to be updated when/if the Dialyzer can analyze Core Erlang
+%% without compiling abstract code.
+bad_dialyzer_attr(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Source = lists:concat([dial, ".erl"]),
+ Filename = filename:join(PrivDir, Source),
+ ok = dialyzer_common:check_plt(PrivDir),
+ PltFilename = dialyzer_common:plt_file(PrivDir),
+ Opts = [{files, [Filename]},
+ {check_plt, false},
+ {from, src_code},
+ {init_plt, PltFilename}],
+
+ Prog1 = <<"-module(dial).
+ -dialyzer({no_return, [undef/0]}).">>,
+ ok = file:write_file(Filename, Prog1),
+ {dialyzer_error,
+ "Analysis failed with error:\n" ++ Str1} =
+ (catch dialyzer:run(Opts)),
+ S1 = string:find(Str1, "dial.erl:2:17: function undef/0 undefined"),
+ true = is_list(S1),
+
+ Prog2 = <<"-module(dial).
+ -dialyzer({no_return, [{undef,1,2}]}).">>,
+ ok = file:write_file(Filename, Prog2),
+ {dialyzer_error,
+ "Analysis failed with error:\n" ++ Str2} =
+ (catch dialyzer:run(Opts)),
+ S2 = string:find(Str2, "dial.erl:2:17: badly formed dialyzer "
+ "attribute: {no_return,{undef,1,2}}"),
+ true = is_list(S2),
+
+ ok.
+
+merge_plts(Config) ->
+ %% A few checks of merging PLTs.
+ fun() ->
+ {Mod1, Mod2} = types(),
+ {BeamFiles, Plt1, Plt2} = create_plts(Mod1, Mod2, Config),
+
+ {dialyzer_error,
+ "Could not merge PLTs since they are not disjoint"++_} =
+ (catch run_dialyzer(succ_typings, BeamFiles,
+ [{plts, [Plt1, Plt1]}])),
+ [{warn_contract_types,_,_}] =
+ run_dialyzer(succ_typings, BeamFiles,
+ [{warnings, [unknown]},
+ {plts, [Plt1, Plt2]}])
+ end(),
+
+ fun() ->
+ {Mod1, Mod2} = callbacks(),
+ {BeamFiles, Plt1, Plt2} = create_plts(Mod1, Mod2, Config),
+
+ {dialyzer_error,
+ "Could not merge PLTs since they are not disjoint"++_} =
+ (catch run_dialyzer(succ_typings, BeamFiles,
+ [{plts, [Plt1, Plt1]}])),
+ [] =
+ run_dialyzer(succ_typings, BeamFiles,
+ [{warnings, [unknown]},
+ {plts, [Plt1, Plt2]}])
+ end(),
+
+ ok.
+
+types() ->
+ Mod1 = <<"-module(merge_plts_1).
+ -export([f/0]).
+ -export_type([t/0]).
+ -type t() :: merge_plts_2:t().
+ -spec f() -> t().
+ f() -> 1. % Not an atom().
+ ">>,
+ Mod2 = <<"-module(merge_plts_2).
+ -export_type([t/0]).
+ -type t() :: atom().
+ ">>,
+ {Mod1, Mod2}.
+
+callbacks() -> %% A very shallow test.
+ Mod1 = <<"-module(merge_plts_1).
+ -callback t() -> merge_plts_2:t().
+ ">>,
+ Mod2 = <<"-module(merge_plts_2).
+ -export_type([t/0]).
+ -type t() :: atom().
+ ">>,
+ {Mod1, Mod2}.
+
+create_plts(Mod1, Mod2, Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Plt1 = filename:join(PrivDir, "merge_plts_1.plt"),
+ Plt2 = filename:join(PrivDir, "merge_plts_2.plt"),
+ ErlangBeam = erlang_beam(),
+
+ {ok, BeamFile1} = compile(Config, Mod1, merge_plts_1, []),
+ [] = run_dialyzer(plt_build, [ErlangBeam,BeamFile1], [{output_plt,Plt1}]),
+
+ {ok, BeamFile2} = compile(Config, Mod2, merge_plts_2, []),
+ [] = run_dialyzer(plt_build, [BeamFile2], [{output_plt, Plt2}]),
+ {[BeamFile1, BeamFile2], Plt1, Plt2}.
+
+%% End of merge_plts().
+
+bad_record_type(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Source = lists:concat([bad_record_type, ".erl"]),
+ Filename = filename:join(PrivDir, Source),
+ PltFilename = dialyzer_common:plt_file(PrivDir),
+
+ Opts = [{files, [Filename]},
+ {check_plt, false},
+ {from, src_code},
+ {init_plt, PltFilename}],
+
+ Prog = <<"-module(bad_record_type).
+ -export([r/0]).
+ -record(r, {f = 3 :: integer()}).
+ -spec r() -> #r{f :: atom()}.
+ r() ->
+ #r{}.">>,
+ ok = file:write_file(Filename, Prog),
+ {dialyzer_error,
+ "Analysis failed with error:\n" ++ Str} =
+ (catch dialyzer:run(Opts)),
+ P = string:str(Str,
+ "bad_record_type.erl:4:16: Illegal declaration of #r{f}"),
+ true = P > 0,
+ ok.
+
+letrec_rvals(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ Plt = filename:join(PrivDir, "letrec_rvals.plt"),
+ Prog = <<"
+-module(letrec_rvals).
+
+-export([demo_fun/1]).
+
+demo_fun(_Arg) ->
+ case ok of
+ _ ->
+ _Res = _Arg,
+ [ ok || _ <- [] ]
+ end,
+ _Res.
+
+handle_info() ->
+ case chids_to_audit() of
+ {ChIds, St2} ->
+ [ ChId || ChId <- ChIds ],
+ ok
+ end,
+ check_done(St2).
+
+chids_to_audit() ->
+ some_module:get_audit_list().
+
+check_done(_) ->
+ ok.
+ ">>,
+ {ok, BeamFile} = compile(Config, Prog, letrec_rvals, []),
+ [] = run_dialyzer(plt_build, [BeamFile], [{output_plt, Plt}]),
+ ok.
+
+%% GH-4501
+missing_plt_file(Config) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ PltFile = filename:join(PrivDir, "missing_plt_file.plt"),
+ Prog2 = <<"-module(missing_plt_file2).
+ t() -> foo.">>,
+ {ok, BeamFile2} = compile(Config, Prog2, missing_plt_file2, []),
+
+ true = missing_plt_file_1(Config, PltFile, BeamFile2),
+ true = missing_plt_file_2(Config, PltFile, BeamFile2),
+ true = missing_plt_file_3(),
+ ok.
+
+missing_plt_file_1(Config, PltFile, BeamFile2) ->
+ BeamFile = create(Config, PltFile),
+ ok = file:delete(BeamFile),
+ try succ(PltFile, BeamFile2), false
+ catch throw:{dialyzer_error, _} -> true
+ end.
+
+missing_plt_file_2(Config, PltFile, BeamFile2) ->
+ BeamFile = create(Config, PltFile),
+ ok = file:delete(BeamFile),
+
+ Cmd = "dialyzer -q --plt " ++ PltFile ++ " " ++ BeamFile2,
+ io:format("Cmd `~p'\n", [Cmd]),
+ "\ndialyzer: File not found: " ++ _ = os:cmd(Cmd),
+
+ try check(PltFile, BeamFile2), false
+ catch throw:{dialyzer_error, _} -> true
+ end.
+
+missing_plt_file_3() ->
+ try dialyzer_cplt:from_file("no_such_file"), false
+ catch throw:{dialyzer_error, _} -> true
+ end.
+
+create(Config, PltFile) ->
+ Prog = <<"-module(missing_plt_file).
+ t() -> foo.">>,
+ {ok, BeamFile} = compile(Config, Prog, missing_plt_file, []),
+ Files = [BeamFile],
+ _ = file:delete(PltFile),
+ [] = dialyzer:run([{files,Files},
+ {output_plt, PltFile},
+ {analysis_type, plt_build},
+ {warnings, [no_unknown]}]),
+ BeamFile.
+
+succ(PltFile, BeamFile2) ->
+ Files = [BeamFile2],
+ dialyzer:run([{files, Files},
+ {plts,[PltFile]},
+ {analysis_type, succ_typings}]).
+
+check(PltFile, _BeamFile2) ->
+ dialyzer:run([{plts,[PltFile]},
+ {analysis_type, plt_check}]).
+
+mod_dep_from_record_definition_field_value_default_unused(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+
+ -record(my_record,
+ { num_field = type_deps:get_num() :: number(),
+ str_field,
+ bool_field
+ }).
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, []}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_record_definition_field_value_default_used(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+ -export([f/0]).
+
+ -record(my_record,
+ { num_field = type_deps:get_num() :: number(),
+ str_field,
+ bool_field
+ }).
+
+ f() -> #my_record{str_field = \"foo\", bool_field = true}. % type_deps:get_num() used implicitly here
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_behaviour(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+ -behaviour(type_deps).
+ -export([quux/1]).
+
+ quux(N) -> N + 1. % Depends on behaviour module to check the callback implementation
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_record_definition_field_type(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+
+ -record(my_record,
+ { num_field = 1 :: type_deps:number_like(),
+ str_field,
+ bool_field
+ }).
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_overloaded_callback(Config) ->
+ DependerSrc1 = <<"
+ -module(depender).
+
+ -callback f(string()) -> string()
+ ; (type_deps:number_like()) -> type_deps:number_like().
+ ">>,
+ DependerSrc2 = <<"
+ -module(depender).
+
+ -callback f(type_deps:number_like()) -> type_deps:number_like()
+ ; (string()) -> string().
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc1, ExpectedTypeDepsInPlt),
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc2, ExpectedTypeDepsInPlt).
+
+mod_dep_from_exported_overloaded_fun_spec(Config) ->
+ DependerSrc1 = <<"
+ -module(depender).
+ -export([f/1]).
+
+ -spec f({a, atom()}) -> atom()
+ ; ({n, type_deps:number_like()}) -> type_deps:number_like().
+ f({a, X}) when is_atom(X) -> X;
+ f({n, X}) when is_number(X) -> X.
+ ">>,
+ DependerSrc2 = <<"
+ -module(depender).
+ -export([f/1]).
+
+ -spec f({n, type_deps:number_like()}) -> type_deps:number_like()
+ ; ({a, atom()}) -> atom().
+ f({n, X}) when is_number(X) -> X;
+ f({a, X}) when is_atom(X) -> X.
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc1, ExpectedTypeDepsInPlt),
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc2, ExpectedTypeDepsInPlt).
+
+mod_dep_from_unexported_overloaded_fun_spec(Config) ->
+ DependerSrc1 = <<"
+ -module(depender).
+ -compile({nowarn_unused_function, [f/1]}).
+
+ -spec f({a, atom()}) -> atom()
+ ; ({n, type_deps:number_like()}) -> type_deps:number_like().
+ f({a, X}) when is_atom(X) -> X;
+ f({n, X}) when is_number(X) -> X.
+ ">>,
+ DependerSrc2 = <<"
+ -module(depender).
+ -compile({nowarn_unused_function, [f/1]}).
+
+ -spec f({n, type_deps:number_like()}) -> type_deps:number_like()
+ ; ({a, atom()}) -> atom().
+ f({n, X}) when is_number(X) -> X;
+ f({a, X}) when is_atom(X) -> X.
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc1, ExpectedTypeDepsInPlt),
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc2, ExpectedTypeDepsInPlt).
+
+mod_dep_from_callback_constraint(Config) ->
+ DependerSrc1 = <<"
+ -module(depender).
+
+ -callback f(X) -> string() when X :: type_deps:number_like().
+ ">>,
+ DependerSrc2 = <<"
+ -module(depender).
+
+ -callback f(X :: type_deps:number_like()) -> string().
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc1, ExpectedTypeDepsInPlt),
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc2, ExpectedTypeDepsInPlt).
+
+mod_dep_from_unexported_fun_spec_constraint(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+ -compile({nowarn_unused_function, [f/1]}).
+
+ -spec f(N) -> number() when N :: type_deps:number_like().
+ f(N) -> N.
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_exported_fun_spec_constraint(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+ -export([f/1]).
+
+ -spec f(N) -> number() when N :: type_deps:number_like().
+ f(N) -> N.
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_callback_return(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+
+ -callback f(string()) -> type_deps:number_like().
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_callback_args(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+
+ -callback f(type_deps:number_like()) -> string().
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_unexported_opaque_type(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+
+ -opaque my_type() :: {string(), type_deps:number_like()}.
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_exported_opaque_type(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+ -export_type([my_type/0]).
+
+ -opaque my_type() :: {string(), type_deps:number_like()}.
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_unexported_opaque_type_args(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+
+ -type my_type() :: type_deps:my_opaque(number()).
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_exported_opaque_type_args(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+ -export_type([my_type/0]).
+
+ -type my_type() :: type_deps:my_opaque(number()).
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_unexported_type_args(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+
+ -type my_type() :: {string(), type_deps:number_like()}.
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_exported_type_args(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+ -export_type([my_type/0]).
+
+ -type my_type() :: {string(), type_deps:number_like()}.
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_unexported_fun_spec_args(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+ -compile({nowarn_unused_function, [f/1]}).
+
+ -spec f(type_deps:number_like()) -> number().
+ f(N) -> N.
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_exported_fun_spec_args(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+ -export([f/1]).
+
+ -spec f(N :: type_deps:number_like()) -> number().
+ f(N) -> N.
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_unexported_fun_spec_return(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+ -compile({nowarn_unused_function, [f/0]}).
+
+ -spec f() -> type_deps:number_like().
+ f() -> 1.
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_exported_fun_spec_return(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+ -export([f/0]).
+
+ -spec f() -> type_deps:number_like().
+ f() -> 1.
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_unexported_type(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+
+ -type my_type() :: type_deps:list_like(number()).
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+mod_dep_from_exported_type(Config) ->
+ DependerSrc = <<"
+ -module(depender).
+ -export_type([my_type/0]).
+
+ -type my_type() :: type_deps:list_like(number()).
+ ">>,
+ ExpectedTypeDepsInPlt = [{depender, []}, {type_deps, [depender]}],
+ ok = check_plt_deps(Config, ?FUNCTION_NAME, DependerSrc, ExpectedTypeDepsInPlt).
+
+check_plt_deps(Config, TestName, DependerSrc, ExpectedTypeDepsInPltUnsorted) ->
+ PrivDir = proplists:get_value(priv_dir, Config),
+ PltFile = filename:join(PrivDir, atom_to_list(TestName) ++ ".plt"),
+ {ok, DepsBeamFile} = compile(Config, type_deps, []),
+ {ok, DependerBeamFile} = compile(Config, DependerSrc, depender, []),
+ [] = run_dialyzer(plt_build, [DependerBeamFile, DepsBeamFile], [{output_plt, PltFile}]),
+ {_ResPlt, #plt_info{mod_deps = DepsByModule}} = dialyzer_cplt:plt_and_info_from_file(PltFile),
+
+ ActualTypeDepsInPlt =
+ lists:sort(dict:to_list(dict:erase(erlang, DepsByModule))),
+ ExpectedTypeDepsInPlt =
+ lists:usort(ExpectedTypeDepsInPltUnsorted),
+
+ ?assertEqual(
+ ExpectedTypeDepsInPlt,
+ ActualTypeDepsInPlt,
+ {missing, ExpectedTypeDepsInPlt -- ActualTypeDepsInPlt,
+ extra, ActualTypeDepsInPlt -- ExpectedTypeDepsInPlt}).
+
+erlang_beam() ->
+ case code:where_is_file("erlang.beam") of
+ non_existing ->
+ filename:join([code:root_dir(),
+ "erts", "preloaded", "ebin",
+ "erlang.beam"]);
+ EBeam ->
+ EBeam
+ end.
+
+%% Builds the named module using the source in the cplt_SUITE_data dir
+compile(Config, Module, CompileOpts) ->
+ Source = lists:concat([Module, ".erl"]),
+ PrivDir = proplists:get_value(priv_dir,Config),
+ DataDir = proplists:get_value(data_dir,Config),
+ SrcFilename = filename:join([DataDir, Source]),
+ Opts = [{outdir, PrivDir}, debug_info | CompileOpts],
+ {ok, Module} = compile:file(SrcFilename, Opts),
+ {ok, filename:join([PrivDir, lists:concat([Module, ".beam"])])}.
+
+%% Builds the named module using the literal source given
+compile(Config, Prog, Module, CompileOpts) ->
+ Source = lists:concat([Module, ".erl"]),
+ PrivDir = proplists:get_value(priv_dir,Config),
+ Filename = filename:join([PrivDir, Source]),
+ ok = file:write_file(Filename, Prog),
+ Opts = [{outdir, PrivDir}, debug_info | CompileOpts],
+ {ok, Module} = compile:file(Filename, Opts),
+ {ok, filename:join([PrivDir, lists:concat([Module, ".beam"])])}.
+
+run_dialyzer(Analysis, Files, Opts) ->
+ dialyzer:run([{analysis_type, Analysis},
+ {files, Files},
+ {from, byte_code},
+ {warnings, [no_unknown]} |
+ Opts]).
+
+m_src_without_warning() -> <<"
+ -module(m).
+ -export([updt/3]).
+
+ updt(X, K, V) -> X#{ K => V }.
+ ">>.
+
+m_src_with_warning() -> <<"
+ -module(m).
+ -export([updt/3]).
+
+ -spec updt(list(), term(), term()) -> list(). % Warning: Spec is wrong! Function takes a map, not a list
+ updt(X, K, V) -> X#{ K => V }.
+ ">>.
+
+adding_warning_apps_after_a_run_without_them_causes_any_new_warnings_to_be_reported(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ PltFile = filename:join(PrivDir, atom_to_list(?FUNCTION_NAME) ++ ".plt"),
+ Opts = [{init_plt, [PltFile]}],
+ OptsNoContractWarn = [{init_plt, [PltFile]}, {warnings, [no_contracts]}],
+ {ok, Beam} = compile(Config, m_src_with_warning(), m, []),
+ [] = run_dialyzer(incremental, [Beam], OptsNoContractWarn),
+ ?assertMatch(
+ [{warn_contract_types,
+ {_, {5,4}},
+ {invalid_contract,[m,updt,3,{[1],true},"([any()],term(),term()) -> [any()]","(map(),_,_) -> map()"]}}
+ ],
+ run_dialyzer(incremental, [Beam], Opts)).
+
+removing_warning_apps_after_a_run_with_them_causes_any_warnings_for_the_removed_apps_not_to_be_reported(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ PltFile = filename:join(PrivDir, atom_to_list(?FUNCTION_NAME) ++ ".plt"),
+ Opts = [{init_plt, [PltFile]}],
+ OptsNoContractWarn = [{init_plt, [PltFile]}, {warnings, [no_contracts]}],
+ {ok, Beam} = compile(Config, m_src_with_warning(), m, []),
+ ?assertMatch(
+ [{warn_contract_types,
+ {_, {5,4}},
+ {invalid_contract,[m,updt,3,{[1],true},"([any()],term(),term()) -> [any()]","(map(),_,_) -> map()"]}}
+ ],
+ run_dialyzer(incremental, [Beam], Opts)),
+ ?assertEqual(
+ [],
+ run_dialyzer(incremental, [Beam], OptsNoContractWarn)).
+
+removing_legal_warnings_with_existing_stored_warnings_in_plt_does_not_result_in_old_warnings_being_printed(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ PltFile = filename:join(PrivDir, atom_to_list(?FUNCTION_NAME) ++ ".plt"),
+ Opts = [{init_plt, [PltFile]}],
+ {ok, BeamFileBefore} = compile(Config, m_src_with_warning(), m, []),
+ ?assertMatch(
+ [{warn_contract_types,
+ {_, {5,4}},
+ {invalid_contract,[m,updt,3,{[1],true},"([any()],term(),term()) -> [any()]","(map(),_,_) -> map()"]}}
+ ],
+ run_dialyzer(incremental, [BeamFileBefore], Opts)),
+ {ok, BeamFileAfter} = compile(Config, m_src_without_warning(), m, []),
+ ?assertEqual(
+ [],
+ run_dialyzer(incremental, [BeamFileAfter], Opts)).
+
+adding_legal_warnings_with_existing_stored_warnings_in_plt_results_in_new_warnings_being_printed(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ PltFile = filename:join(PrivDir, atom_to_list(?FUNCTION_NAME) ++ ".plt"),
+ Opts = [{init_plt, [PltFile]}],
+ {ok, BeamFileAfter} = compile(Config, m_src_without_warning(), m, []),
+ ?assertEqual(
+ [],
+ run_dialyzer(incremental, [BeamFileAfter], Opts)),
+ {ok, BeamFileBefore} = compile(Config, m_src_with_warning(), m, []),
+ ?assertMatch(
+ [{warn_contract_types,
+ {_, {5,4}},
+ {invalid_contract,[m,updt,3,{[1],true},"([any()],term(),term()) -> [any()]","(map(),_,_) -> map()"]}}
+ ],
+ run_dialyzer(incremental, [BeamFileBefore], Opts)).