aboutsummaryrefslogtreecommitdiffstats
path: root/lib/dialyzer/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dialyzer/src')
-rw-r--r--lib/dialyzer/src/Makefile5
-rw-r--r--lib/dialyzer/src/dialyzer.app.src13
-rw-r--r--lib/dialyzer/src/dialyzer.appup.src11
-rw-r--r--lib/dialyzer/src/dialyzer.erl44
-rw-r--r--lib/dialyzer/src/dialyzer.hrl15
-rw-r--r--lib/dialyzer/src/dialyzer_analysis_callgraph.erl27
-rw-r--r--lib/dialyzer/src/dialyzer_behaviours.erl8
-rw-r--r--lib/dialyzer/src/dialyzer_callgraph.erl59
-rw-r--r--lib/dialyzer/src/dialyzer_cl.erl60
-rw-r--r--lib/dialyzer/src/dialyzer_cl_parse.erl19
-rw-r--r--lib/dialyzer/src/dialyzer_codeserver.erl41
-rw-r--r--lib/dialyzer/src/dialyzer_contracts.erl194
-rw-r--r--lib/dialyzer/src/dialyzer_dataflow.erl1208
-rw-r--r--lib/dialyzer/src/dialyzer_dep.erl72
-rw-r--r--lib/dialyzer/src/dialyzer_gui.erl1381
-rw-r--r--lib/dialyzer/src/dialyzer_gui_wx.erl2
-rw-r--r--lib/dialyzer/src/dialyzer_options.erl7
-rw-r--r--lib/dialyzer/src/dialyzer_plt.erl36
-rw-r--r--lib/dialyzer/src/dialyzer_races.erl27
-rw-r--r--lib/dialyzer/src/dialyzer_succ_typings.erl86
-rw-r--r--lib/dialyzer/src/dialyzer_typesig.erl387
-rw-r--r--lib/dialyzer/src/dialyzer_utils.erl23
22 files changed, 1341 insertions, 2384 deletions
diff --git a/lib/dialyzer/src/Makefile b/lib/dialyzer/src/Makefile
index bb2edd419a..d7265ba31a 100644
--- a/lib/dialyzer/src/Makefile
+++ b/lib/dialyzer/src/Makefile
@@ -57,7 +57,6 @@ MODULES = \
dialyzer_dataflow \
dialyzer_dep \
dialyzer_explanation \
- dialyzer_gui \
dialyzer_gui_wx \
dialyzer_options \
dialyzer_plt \
@@ -113,9 +112,6 @@ $(EBIN)/dialyzer_cl_parse.$(EMULATOR): dialyzer_cl_parse.erl ../vsn.mk
$(EBIN)/dialyzer_plt.$(EMULATOR): dialyzer_plt.erl ../vsn.mk
$(erlc_verbose)erlc -W $(ERL_COMPILE_FLAGS) -DVSN="\"v$(VSN)\"" -o$(EBIN) dialyzer_plt.erl
-$(EBIN)/dialyzer_gui.$(EMULATOR): dialyzer_gui.erl ../vsn.mk
- $(erlc_verbose)erlc -W $(ERL_COMPILE_FLAGS) -DVSN="\"v$(VSN)\"" -o$(EBIN) dialyzer_gui.erl
-
$(EBIN)/dialyzer_gui_wx.$(EMULATOR): dialyzer_gui_wx.erl ../vsn.mk
$(erlc_verbose)erlc -W $(ERL_COMPILE_FLAGS) -DVSN="\"v$(VSN)\"" -o$(EBIN) dialyzer_gui_wx.erl
@@ -140,7 +136,6 @@ $(EBIN)/dialyzer_contracts.beam: dialyzer.hrl
$(EBIN)/dialyzer_dataflow.beam: dialyzer.hrl
$(EBIN)/dialyzer_dep.beam: dialyzer.hrl
$(EBIN)/dialyzer_explanation.beam: dialyzer.hrl
-$(EBIN)/dialyzer_gui.beam: dialyzer.hrl
$(EBIN)/dialyzer_gui_wx.beam: dialyzer.hrl dialyzer_gui_wx.hrl
$(EBIN)/dialyzer_options.beam: dialyzer.hrl
$(EBIN)/dialyzer_plt.beam: dialyzer.hrl
diff --git a/lib/dialyzer/src/dialyzer.app.src b/lib/dialyzer/src/dialyzer.app.src
index 9222a28a77..1756800c4f 100644
--- a/lib/dialyzer/src/dialyzer.app.src
+++ b/lib/dialyzer/src/dialyzer.app.src
@@ -2,7 +2,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2010. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2014. 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
@@ -29,17 +29,22 @@
dialyzer_cl_parse,
dialyzer_codeserver,
dialyzer_contracts,
+ dialyzer_coordinator,
dialyzer_dataflow,
dialyzer_dep,
dialyzer_explanation,
- dialyzer_gui,
dialyzer_gui_wx,
dialyzer_options,
dialyzer_plt,
dialyzer_races,
dialyzer_succ_typings,
dialyzer_typesig,
- dialyzer_utils]},
+ dialyzer_utils,
+ dialyzer_timing,
+ dialyzer_worker]},
{registered, []},
{applications, [compiler, gs, hipe, kernel, stdlib, wx]},
- {env, []}]}.
+ {env, []},
+ {runtime_dependencies, ["wx-1.2","syntax_tools-1.6.14","stdlib-2.0",
+ "kernel-3.0","hipe-3.10.3","erts-6.0",
+ "compiler-5.0"]}]}.
diff --git a/lib/dialyzer/src/dialyzer.appup.src b/lib/dialyzer/src/dialyzer.appup.src
index 26d14ee8f4..1e293e407a 100644
--- a/lib/dialyzer/src/dialyzer.appup.src
+++ b/lib/dialyzer/src/dialyzer.appup.src
@@ -1,7 +1,7 @@
-%%
+%% -*- erlang -*-
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2009. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2014. 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
@@ -15,6 +15,7 @@
%% under the License.
%%
%% %CopyrightEnd%
-%%
-
-{"%VSN%",[],[]}.
+{"%VSN%",
+ [{<<".*">>,[{restart_application, dialyzer}]}],
+ [{<<".*">>,[{restart_application, dialyzer}]}]
+}.
diff --git a/lib/dialyzer/src/dialyzer.erl b/lib/dialyzer/src/dialyzer.erl
index 822aa0826a..cec94a49fd 100644
--- a/lib/dialyzer/src/dialyzer.erl
+++ b/lib/dialyzer/src/dialyzer.erl
@@ -2,7 +2,7 @@
%%-----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2014. 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
@@ -62,18 +62,18 @@ plain_cl() ->
cl_halt(cl_check_init(Opts), Opts);
{plt_info, Opts} ->
cl_halt(cl_print_plt_info(Opts), Opts);
- {{gui, Type}, Opts} ->
+ {gui, Opts} ->
try check_gui_options(Opts)
catch throw:{dialyzer_error, Msg} -> cl_error(Msg)
end,
case Opts#options.check_plt of
true ->
case cl_check_init(Opts#options{get_warnings = false}) of
- {ok, _} -> gui_halt(internal_gui(Type, Opts), Opts);
+ {ok, _} -> gui_halt(internal_gui(Opts), Opts);
{error, _} = Error -> cl_halt(Error, Opts)
end;
false ->
- gui_halt(internal_gui(Type, Opts), Opts)
+ gui_halt(internal_gui(Opts), Opts)
end;
{cl, Opts} ->
case Opts#options.check_plt of
@@ -172,19 +172,16 @@ run(Opts) ->
end,
case dialyzer_cl:start(OptsRecord) of
{?RET_DISCREPANCIES, Warnings} -> Warnings;
- {?RET_NOTHING_SUSPICIOUS, []} -> []
+ {?RET_NOTHING_SUSPICIOUS, _} -> []
end
catch
throw:{dialyzer_error, ErrorMsg} ->
erlang:error({dialyzer_error, lists:flatten(ErrorMsg)})
end.
-internal_gui(Type, OptsRecord) ->
+internal_gui(OptsRecord) ->
F = fun() ->
- case Type of
- gs -> dialyzer_gui:start(OptsRecord);
- wx -> dialyzer_gui_wx:start(OptsRecord)
- end,
+ dialyzer_gui_wx:start(OptsRecord),
?RET_NOTHING_SUSPICIOUS
end,
doit(F).
@@ -205,7 +202,7 @@ gui(Opts) ->
case cl_check_init(OptsRecord) of
{ok, ?RET_NOTHING_SUSPICIOUS} ->
F = fun() ->
- dialyzer_gui:start(OptsRecord)
+ dialyzer_gui_wx:start(OptsRecord)
end,
case doit(F) of
{ok, _} -> ok;
@@ -426,6 +423,9 @@ message_to_string({call_without_opaque, [M, F, Args, ExpectedTriples]}) ->
message_to_string({opaque_eq, [Type, _Op, OpaqueType]}) ->
io_lib:format("Attempt to test for equality between a term of type ~s"
" and a term of opaque type ~s\n", [Type, OpaqueType]);
+message_to_string({opaque_guard, [Arg1, Infix, Arg2, ArgNs]}) ->
+ io_lib:format("Guard test ~s ~s ~s contains ~s\n",
+ [Arg1, Infix, Arg2, form_positions(ArgNs)]);
message_to_string({opaque_guard, [Guard, Args]}) ->
io_lib:format("Guard test ~w~s breaks the opaqueness of its argument\n",
[Guard, Args]);
@@ -438,8 +438,15 @@ message_to_string({opaque_match, [Pat, OpaqueType, OpaqueTerm]}) ->
message_to_string({opaque_neq, [Type, _Op, OpaqueType]}) ->
io_lib:format("Attempt to test for inequality between a term of type ~s"
" and a term of opaque type ~s\n", [Type, OpaqueType]);
-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]);
+message_to_string({opaque_type_test, [Fun, Args, Arg, ArgType]}) ->
+ io_lib:format("The type test ~s~s breaks the opaqueness of the term ~s~s\n",
+ [Fun, Args, Arg, ArgType]);
+message_to_string({opaque_size, [SizeType, Size]}) ->
+ io_lib:format("The size ~s breaks the opaqueness of ~s\n",
+ [SizeType, Size]);
+message_to_string({opaque_call, [M, F, Args, Culprit, OpaqueType]}) ->
+ io_lib:format("The call ~s:~s~s breaks the opaqueness of the term ~s :: ~s\n",
+ [M, F, Args, Culprit, OpaqueType]);
%%----- 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]);
@@ -466,7 +473,14 @@ 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({callback_info_missing, [B]}) ->
- io_lib:format("Callback info about the ~w behaviour is not available\n", [B]).
+ io_lib:format("Callback info about the ~w behaviour is not available\n", [B]);
+%%----- Warnings for unknown functions, types, and behaviours -------------
+message_to_string({unknown_type, {M, F, A}}) ->
+ io_lib:format("Unknown type ~w:~w/~w", [M, F, A]);
+message_to_string({unknown_function, {M, F, A}}) ->
+ io_lib:format("Unknown function ~w:~w/~w", [M, F, A]);
+message_to_string({unknown_behaviour, B}) ->
+ io_lib:format("Unknown behaviour ~w", [B]).
%%-----------------------------------------------------------------------------
%% Auxiliary functions below
@@ -549,4 +563,4 @@ form_position_string(ArgNs) ->
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) -> io_lib:format("~wth", [N]).
diff --git a/lib/dialyzer/src/dialyzer.hrl b/lib/dialyzer/src/dialyzer.hrl
index 105a174e31..9a25f86512 100644
--- a/lib/dialyzer/src/dialyzer.hrl
+++ b/lib/dialyzer/src/dialyzer.hrl
@@ -2,7 +2,7 @@
%%%
%%% %CopyrightBegin%
%%%
-%%% Copyright Ericsson AB 2006-2012. All Rights Reserved.
+%%% Copyright Ericsson AB 2006-2014. 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
@@ -58,6 +58,7 @@
-define(WARN_RACE_CONDITION, warn_race_condition).
-define(WARN_BEHAVIOUR, warn_behaviour).
-define(WARN_UNDEFINED_CALLBACK, warn_undefined_callbacks).
+-define(WARN_UNKNOWN, warn_unknown).
%%
%% The following type has double role:
@@ -73,7 +74,7 @@
| ?WARN_CONTRACT_SUPERTYPE | ?WARN_CALLGRAPH
| ?WARN_UNMATCHED_RETURN | ?WARN_RACE_CONDITION
| ?WARN_BEHAVIOUR | ?WARN_CONTRACT_RANGE
- | ?WARN_UNDEFINED_CALLBACK.
+ | ?WARN_UNDEFINED_CALLBACK | ?WARN_UNKNOWN.
%%
%% This is the representation of each warning as they will be returned
@@ -88,12 +89,6 @@
-type dial_error() :: any(). %% XXX: underspecified
%%--------------------------------------------------------------------
-%% THIS TYPE SHOULD ONE DAY DISAPPEAR -- IT DOES NOT BELONG HERE
-%%--------------------------------------------------------------------
-
--type ordset(T) :: [T] . %% XXX: temporarily
-
-%%--------------------------------------------------------------------
%% Basic types used either in the record definitions below or in other
%% parts of the application
%%--------------------------------------------------------------------
@@ -143,7 +138,7 @@
init_plts = [] :: [file:filename()],
include_dirs = [] :: [file:filename()],
output_plt = none :: 'none' | file:filename(),
- legal_warnings = ordsets:new() :: ordset(dial_warn_tag()),
+ legal_warnings = ordsets:new() :: ordsets:ordset(dial_warn_tag()),
report_mode = normal :: rep_mode(),
erlang_mode = false :: boolean(),
use_contracts = true :: boolean(),
@@ -167,4 +162,4 @@
dialyzer_timing:end_stamp(Server),
Var
end).
--define(timing(Server, Msg, Expr),?timing(Server, Msg, _T, Expr)).
+-define(timing(Server, Msg, Expr), ?timing(Server, Msg, _T, Expr)).
diff --git a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl
index 0fbaf1d47c..6a33a2acb3 100644
--- a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl
+++ b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl
@@ -2,7 +2,7 @@
%%--------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2014. 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
@@ -39,6 +39,8 @@
one_file_result/0,
compile_result/0]).
+-export_type([no_warn_unused/0]).
+
-include("dialyzer.hrl").
-record(analysis_state,
@@ -48,7 +50,7 @@
defines = [] :: [dial_define()],
doc_plt :: dialyzer_plt:plt(),
include_dirs = [] :: [file:filename()],
- no_warn_unused :: set(),
+ no_warn_unused :: no_warn_unused(),
parent :: pid(),
plt :: dialyzer_plt:plt(),
start_from = byte_code :: start_from(),
@@ -59,6 +61,8 @@
-record(server_state, {parent :: pid(), legal_warnings :: [dial_warn_tag()]}).
+-type no_warn_unused() :: sets:set(mfa()).
+
%%--------------------------------------------------------------------
%% Main
%%--------------------------------------------------------------------
@@ -168,7 +172,7 @@ analysis_start(Parent, Analysis) ->
throw:{error, _ErrorMsg} = Error -> exit(Error)
end,
NewPlt0 = dialyzer_plt:insert_types(Plt, dialyzer_codeserver:get_records(NewCServer)),
- ExpTypes = dialyzer_codeserver:get_exported_types(NewCServer),
+ ExpTypes = dialyzer_codeserver:get_exported_types(NewCServer),
NewPlt1 = dialyzer_plt:insert_exported_types(NewPlt0, ExpTypes),
State0 = State#analysis_state{plt = NewPlt1},
dump_callgraph(Callgraph, State0, Analysis),
@@ -245,7 +249,7 @@ compile_and_store(Files, #analysis_state{codeserver = CServer,
timing_server = Timing,
parent = Parent} = State) ->
send_log(Parent, "Reading files and computing callgraph... "),
- {T1, _} = statistics(runtime),
+ {T1, _} = statistics(wall_clock),
Callgraph = dialyzer_callgraph:new(),
CompileInit = make_compile_init(State, Callgraph),
{{Failed, NoWarn, Modules}, NextLabel} =
@@ -268,13 +272,13 @@ compile_and_store(Files, #analysis_state{codeserver = CServer,
[[Reason || {_Filename, Reason} <- Failed]]),
exit({error, Msg})
end,
- {T2, _} = statistics(runtime),
+ {T2, _} = statistics(wall_clock),
Msg1 = io_lib:format("done in ~.2f secs\nRemoving edges... ", [(T2-T1)/1000]),
send_log(Parent, Msg1),
Callgraph =
?timing(Timing, "clean", _C2,
cleanup_callgraph(State, CServer2, Callgraph, Modules)),
- {T3, _} = statistics(runtime),
+ {T3, _} = statistics(wall_clock),
Msg2 = io_lib:format("done in ~.2f secs\n", [(T3-T2)/1000]),
send_log(Parent, Msg2),
{Callgraph, sets:from_list(NoWarn), CServer2}.
@@ -423,11 +427,8 @@ abs_get_nowarn(Abs, M) ->
false ->
[{M, F, A} || {function, _, F, A, _} <- Abs]; % all functions
true ->
- OnLoad =
- lists:flatten([{M, F, A} || {attribute, _, on_load, {F, A}} <- Abs]),
- OnLoad ++ [{M, F, A} ||
- {nowarn_unused_function, FAs} <- Opts,
- {F, A} <- lists:flatten([FAs])]
+ [{M, F, A} || {nowarn_unused_function, FAs} <- Opts,
+ {F, A} <- lists:flatten([FAs])]
end.
get_exported_types_from_core(Core) ->
@@ -619,9 +620,9 @@ dump_callgraph(CallGraph, State, #analysis{callgraph_file = File} = Analysis) ->
Extension = filename:extension(File),
Start_Msg = io_lib:format("Dumping the callgraph... ", []),
send_log(State#analysis_state.parent, Start_Msg),
- {T1, _} = statistics(runtime),
+ {T1, _} = statistics(wall_clock),
dump_callgraph(CallGraph, State, Analysis, Extension),
- {T2, _} = statistics(runtime),
+ {T2, _} = statistics(wall_clock),
Finish_Msg = io_lib:format("done in ~2f secs\n", [(T2-T1)/1000]),
send_log(State#analysis_state.parent, Finish_Msg),
ok.
diff --git a/lib/dialyzer/src/dialyzer_behaviours.erl b/lib/dialyzer/src/dialyzer_behaviours.erl
index 2b9a69ce77..1d458b49fc 100644
--- a/lib/dialyzer/src/dialyzer_behaviours.erl
+++ b/lib/dialyzer/src/dialyzer_behaviours.erl
@@ -2,7 +2,7 @@
%%-----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2014. 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
@@ -40,15 +40,17 @@
-type behaviour() :: atom().
+-type rectab() :: erl_types:type_table().
+
-record(state, {plt :: dialyzer_plt:plt(),
codeserver :: dialyzer_codeserver:codeserver(),
filename :: file:filename(),
behlines :: [{behaviour(), non_neg_integer()}],
- records :: dict()}).
+ records :: rectab()}).
%%--------------------------------------------------------------------
--spec check_callbacks(module(), [{cerl:cerl(), cerl:cerl()}], dict(),
+-spec check_callbacks(module(), [{cerl:cerl(), cerl:cerl()}], rectab(),
dialyzer_plt:plt(),
dialyzer_codeserver:codeserver()) -> [dial_warning()].
diff --git a/lib/dialyzer/src/dialyzer_callgraph.erl b/lib/dialyzer/src/dialyzer_callgraph.erl
index b9ad3f857d..00b01fbd36 100644
--- a/lib/dialyzer/src/dialyzer_callgraph.erl
+++ b/lib/dialyzer/src/dialyzer_callgraph.erl
@@ -2,7 +2,7 @@
%%-----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2014. 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
@@ -35,6 +35,7 @@
is_escaping/2,
is_self_rec/2,
non_local_calls/1,
+ lookup_letrec/2,
lookup_rec_var/2,
lookup_call_site/2,
lookup_label/2,
@@ -63,14 +64,16 @@
put_named_tables/2, put_public_tables/2, put_behaviour_api_calls/2,
get_behaviour_api_calls/1, dispose_race_server/1, duplicate/1]).
--export_type([callgraph/0, mfa_or_funlbl/0, callgraph_edge/0]).
+-export_type([callgraph/0, mfa_or_funlbl/0, callgraph_edge/0, mod_deps/0]).
-include("dialyzer.hrl").
%%----------------------------------------------------------------------
-type scc() :: [mfa_or_funlbl()].
--type mfa_calls() :: [{mfa_or_funlbl(), mfa_or_funlbl()}].
+-type mfa_call() :: {mfa_or_funlbl(), mfa_or_funlbl()}.
+-type mfa_calls() :: [mfa_call()].
+-type mod_deps() :: dict:dict(module(), [module()]).
%%-----------------------------------------------------------------------------
%% A callgraph is a directed graph where the nodes are functions and a
@@ -81,6 +84,8 @@
%% digraph - A digraph representing the callgraph.
%% Nodes are represented as MFAs or labels.
%% esc - A set of all escaping functions as reported by dialyzer_dep.
+%% letrec_map - A dict mapping from letrec bound labels to function labels.
+%% Includes all functions.
%% name_map - A mapping from label to MFA.
%% rev_name_map - A reverse mapping of the name_map.
%% rec_var_map - A dict mapping from letrec bound labels to function names.
@@ -90,9 +95,10 @@
%% whenever applicable.
%%-----------------------------------------------------------------------------
--record(callgraph, {digraph = digraph:new() :: digraph(),
+-record(callgraph, {digraph = digraph:new() :: digraph:graph(),
active_digraph :: active_digraph(),
esc :: ets:tid(),
+ letrec_map :: ets:tid(),
name_map :: ets:tid(),
rev_name_map :: ets:tid(),
rec_var_map :: ets:tid(),
@@ -101,7 +107,7 @@
race_detection = false :: boolean(),
race_data_server = new_race_data_server() :: pid()}).
--record(race_data_state, {race_code = dict:new() :: dict(),
+-record(race_data_state, {race_code = dict:new() :: dict:dict(),
public_tables = [] :: [label()],
named_tables = [] :: [string()],
beh_api_calls = [] :: [{mfa(), mfa()}]}).
@@ -110,18 +116,19 @@
-type callgraph() :: #callgraph{}.
--type active_digraph() :: {'d', digraph()} | {'e', ets:tid(), ets:tid()}.
+-type active_digraph() :: {'d', digraph:graph()} | {'e', ets:tid(), ets:tid()}.
%%----------------------------------------------------------------------
-spec new() -> callgraph().
new() ->
- [ETSEsc, ETSNameMap, ETSRevNameMap, ETSRecVarMap, ETSSelfRec, ETSCalls] =
+ [ETSEsc, ETSNameMap, ETSRevNameMap, ETSRecVarMap, ETSLetrecMap, ETSSelfRec, ETSCalls] =
[ets:new(N,[public, {read_concurrency, true}]) ||
N <- [callgraph_esc, callgraph_name_map, callgraph_rev_name_map,
- callgraph_rec_var_map, callgraph_self_rec, callgraph_calls]],
+ callgraph_rec_var_map, callgraph_letrec_map, callgraph_self_rec, callgraph_calls]],
#callgraph{esc = ETSEsc,
+ letrec_map = ETSLetrecMap,
name_map = ETSNameMap,
rev_name_map = ETSRevNameMap,
rec_var_map = ETSRecVarMap,
@@ -144,6 +151,12 @@ lookup_rec_var(Label, #callgraph{rec_var_map = RecVarMap})
when is_integer(Label) ->
ets_lookup_dict(Label, RecVarMap).
+-spec lookup_letrec(label(), callgraph()) -> 'error' | {'ok', label()}.
+
+lookup_letrec(Label, #callgraph{letrec_map = LetrecMap})
+ when is_integer(Label) ->
+ ets_lookup_dict(Label, LetrecMap).
+
-spec lookup_call_site(label(), callgraph()) -> 'error' | {'ok', [_]}. % XXX: refine
lookup_call_site(Label, #callgraph{calls = Calls})
@@ -211,7 +224,10 @@ non_local_calls(#callgraph{digraph = DG}) ->
Edges = digraph_edges(DG),
find_non_local_calls(Edges, sets:new()).
--spec find_non_local_calls([{mfa_or_funlbl(), mfa_or_funlbl()}], set()) -> mfa_calls().
+-type call_tab() :: sets:set(mfa_call()).
+
+-spec find_non_local_calls([{mfa_or_funlbl(), mfa_or_funlbl()}], call_tab()) ->
+ mfa_calls().
find_non_local_calls([{{M,_,_}, {M,_,_}}|Left], Set) ->
find_non_local_calls(Left, Set);
@@ -256,7 +272,7 @@ get_required_by(SCC, #callgraph{active_digraph = {'d', DG}}) ->
modules(#callgraph{digraph = DG}) ->
ordsets:from_list([M || {M,_F,_A} <- digraph_vertices(DG)]).
--spec module_postorder(callgraph()) -> {[module()], {'d', digraph()}}.
+-spec module_postorder(callgraph()) -> {[module()], {'d', digraph:graph()}}.
module_postorder(#callgraph{digraph = DG}) ->
Edges = lists:foldl(fun edge_fold/2, sets:new(), digraph_edges(DG)),
@@ -276,7 +292,7 @@ edge_fold(_, Set) -> Set.
%% The module deps of a module are modules that depend on the module
--spec module_deps(callgraph()) -> dict().
+-spec module_deps(callgraph()) -> mod_deps().
module_deps(#callgraph{digraph = DG}) ->
Edges = lists:foldl(fun edge_fold/2, sets:new(), digraph_edges(DG)),
@@ -290,7 +306,7 @@ module_deps(#callgraph{digraph = DG}) ->
digraph_delete(MDG),
dict:from_list(Deps).
--spec strip_module_deps(dict(), set()) -> dict().
+-spec strip_module_deps(mod_deps(), sets:set(module())) -> mod_deps().
strip_module_deps(ModDeps, StripSet) ->
FilterFun1 = fun(Val) -> not sets:is_element(Val, StripSet) end,
@@ -348,16 +364,18 @@ ets_lookup_set(Key, Table) ->
scan_core_tree(Tree, #callgraph{calls = ETSCalls,
esc = ETSEsc,
+ letrec_map = ETSLetrecMap,
name_map = ETSNameMap,
rec_var_map = ETSRecVarMap,
rev_name_map = ETSRevNameMap,
self_rec = ETSSelfRec}) ->
%% Build name map and recursion variable maps.
- build_maps(Tree, ETSRecVarMap, ETSNameMap, ETSRevNameMap),
+ build_maps(Tree, ETSRecVarMap, ETSNameMap, ETSRevNameMap, ETSLetrecMap),
%% First find the module-local dependencies.
- {Deps0, EscapingFuns, Calls} = dialyzer_dep:analyze(Tree),
+ {Deps0, EscapingFuns, Calls, Letrecs} = dialyzer_dep:analyze(Tree),
true = ets:insert(ETSCalls, dict:to_list(Calls)),
+ true = ets:insert(ETSLetrecMap, dict:to_list(Letrecs)),
true = ets:insert(ETSEsc, [{E} || E <- EscapingFuns]),
LabelEdges = get_edges_from_deps(Deps0),
@@ -394,7 +412,7 @@ scan_core_tree(Tree, #callgraph{calls = ETSCalls,
NamedEdges3 = NewNamedEdges1 ++ NewNamedEdges2,
{Names3, NamedEdges3}.
-build_maps(Tree, ETSRecVarMap, ETSNameMap, ETSRevNameMap) ->
+build_maps(Tree, ETSRecVarMap, ETSNameMap, ETSRevNameMap, ETSLetrecMap) ->
%% We only care about the named (top level) functions. The anonymous
%% functions will be analysed together with their parents.
Defs = cerl:module_defs(Tree),
@@ -406,6 +424,7 @@ build_maps(Tree, ETSRecVarMap, ETSNameMap, ETSRevNameMap) ->
MFA = {Mod, FunName, Arity},
FunLabel = get_label(Function),
VarLabel = get_label(Var),
+ true = ets:insert(ETSLetrecMap, {VarLabel, FunLabel}),
true = ets:insert(ETSNameMap, {FunLabel, MFA}),
true = ets:insert(ETSRevNameMap, {MFA, FunLabel}),
true = ets:insert(ETSRecVarMap, {VarLabel, MFA})
@@ -561,7 +580,7 @@ digraph_reaching_subgraph(Funs, DG) ->
%% Races
%%----------------------------------------------------------------------
--spec renew_race_info(callgraph(), dict(), [label()], [string()]) ->
+-spec renew_race_info(callgraph(), dict:dict(), [label()], [string()]) ->
callgraph().
renew_race_info(#callgraph{race_data_server = RaceDataServer} = CG,
@@ -627,7 +646,7 @@ duplicate(#callgraph{race_data_server = RaceDataServer} = Callgraph) ->
dispose_race_server(#callgraph{race_data_server = RaceDataServer}) ->
race_data_server_cast(stop, RaceDataServer).
--spec get_digraph(callgraph()) -> digraph().
+-spec get_digraph(callgraph()) -> digraph:graph().
get_digraph(#callgraph{digraph = Digraph}) ->
Digraph.
@@ -642,7 +661,7 @@ get_named_tables(#callgraph{race_data_server = RaceDataServer}) ->
get_public_tables(#callgraph{race_data_server = RaceDataServer}) ->
race_data_server_call(get_public_tables, RaceDataServer).
--spec get_race_code(callgraph()) -> dict().
+-spec get_race_code(callgraph()) -> dict:dict().
get_race_code(#callgraph{race_data_server = RaceDataServer}) ->
race_data_server_call(get_race_code, RaceDataServer).
@@ -663,12 +682,12 @@ race_code_new(#callgraph{race_data_server = RaceDataServer} = CG) ->
ok = race_data_server_cast(race_code_new, RaceDataServer),
CG.
--spec put_digraph(digraph(), callgraph()) -> callgraph().
+-spec put_digraph(digraph:graph(), callgraph()) -> callgraph().
put_digraph(Digraph, Callgraph) ->
Callgraph#callgraph{digraph = Digraph}.
--spec put_race_code(dict(), callgraph()) -> callgraph().
+-spec put_race_code(dict:dict(), callgraph()) -> callgraph().
put_race_code(RaceCode, #callgraph{race_data_server = RaceDataServer} = CG) ->
ok = race_data_server_cast({put_race_code, RaceCode}, RaceDataServer),
diff --git a/lib/dialyzer/src/dialyzer_cl.erl b/lib/dialyzer/src/dialyzer_cl.erl
index 365c0b36d4..3e7d9dfa99 100644
--- a/lib/dialyzer/src/dialyzer_cl.erl
+++ b/lib/dialyzer/src/dialyzer_cl.erl
@@ -2,7 +2,7 @@
%%-------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2014. 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
@@ -40,7 +40,7 @@
external_calls = [] :: [mfa()],
external_types = [] :: [mfa()],
legal_warnings = ordsets:new() :: [dial_warn_tag()],
- mod_deps = dict:new() :: dict(),
+ mod_deps = dict:new() :: dialyzer_callgraph:mod_deps(),
output = standard_io :: io:device(),
output_format = formatted :: format(),
filename_opt = basename :: fopt(),
@@ -504,7 +504,7 @@ hipe_compile(Files, #options{erlang_mode = ErlangMode} = Options) ->
_ ->
Mods = [lists, dict, digraph, digraph_utils, ets,
gb_sets, gb_trees, ordsets, sets, sofs,
- cerl, cerl_trees, erl_types, erl_bif_types,
+ cerl, erl_types, cerl_trees, erl_bif_types,
dialyzer_analysis_callgraph, dialyzer, dialyzer_behaviours,
dialyzer_codeserver, dialyzer_contracts,
dialyzer_coordinator, dialyzer_dataflow, dialyzer_dep,
@@ -533,7 +533,7 @@ hc(Mod) ->
case code:is_module_native(Mod) of
true -> ok;
false ->
- %% io:format(" ~s", [Mod]),
+ %% io:format(" ~w", [Mod]),
{ok, Mod} = hipe:c(Mod),
ok
end.
@@ -603,7 +603,7 @@ cl_loop(State, LogCache) ->
Msg = failed_anal_msg(Reason, LogCache),
cl_error(State, Msg);
{'EXIT', BackendPid, Reason} when Reason =/= 'normal' ->
- Msg = failed_anal_msg(io_lib:format("~P", [Reason, 12]), LogCache),
+ Msg = failed_anal_msg(io_lib:format("~p", [Reason]), LogCache),
cl_error(State, Msg);
_Other ->
%% io:format("Received ~p\n", [_Other]),
@@ -613,7 +613,7 @@ cl_loop(State, LogCache) ->
-spec failed_anal_msg(string(), [_]) -> nonempty_string().
failed_anal_msg(Reason, LogCache) ->
- Msg = "Analysis failed with error:\n" ++ Reason ++ "\n",
+ Msg = "Analysis failed with error:\n" ++ lists:flatten(Reason) ++ "\n",
case LogCache =:= [] of
true -> Msg;
false ->
@@ -640,7 +640,7 @@ store_unknown_behaviours(#cl_state{unknown_behaviours = Behs} = St, Beh) ->
-spec cl_error(string()) -> no_return().
cl_error(Msg) ->
- throw({dialyzer_error, Msg}).
+ throw({dialyzer_error, lists:flatten(Msg)}).
-spec cl_error(#cl_state{}, string()) -> no_return().
@@ -650,13 +650,14 @@ cl_error(State, Msg) ->
Outfile -> io:format(Outfile, "\n~s\n", [Msg])
end,
maybe_close_output_file(State),
- throw({dialyzer_error, Msg}).
+ throw({dialyzer_error, lists:flatten(Msg)}).
return_value(State = #cl_state{erlang_mode = ErlangMode,
mod_deps = ModDeps,
output_plt = OutputPlt,
plt_info = PltInfo,
- stored_warnings = StoredWarnings},
+ stored_warnings = StoredWarnings,
+ legal_warnings = LegalWarnings},
Plt) ->
case OutputPlt =:= none of
true -> ok;
@@ -676,16 +677,33 @@ return_value(State = #cl_state{erlang_mode = ErlangMode,
maybe_close_output_file(State),
{RetValue, []};
true ->
- {RetValue, process_warnings(StoredWarnings)}
+ Unknown =
+ case ordsets:is_element(?WARN_UNKNOWN, LegalWarnings) of
+ true ->
+ unknown_functions(State) ++
+ unknown_types(State) ++
+ unknown_behaviours(State);
+ false -> []
+ end,
+ UnknownWarnings =
+ [{?WARN_UNKNOWN, {_Filename = "", _Line = 0}, W} || W <- Unknown],
+ AllWarnings =
+ UnknownWarnings ++ process_warnings(StoredWarnings),
+ {RetValue, AllWarnings}
end.
+unknown_functions(#cl_state{external_calls = Calls}) ->
+ [{unknown_function, MFA} || MFA <- Calls].
+
print_ext_calls(#cl_state{report_mode = quiet}) ->
ok;
print_ext_calls(#cl_state{output = Output,
external_calls = Calls,
stored_warnings = Warnings,
- output_format = Format}) ->
- case Calls =:= [] of
+ output_format = Format,
+ legal_warnings = LegalWarnings}) ->
+ case not ordsets:is_element(?WARN_UNKNOWN, LegalWarnings)
+ orelse Calls =:= [] of
true -> ok;
false ->
case Warnings =:= [] of
@@ -708,14 +726,19 @@ do_print_ext_calls(Output, [{M,F,A}|T], Before) ->
do_print_ext_calls(_, [], _) ->
ok.
+unknown_types(#cl_state{external_types = Types}) ->
+ [{unknown_type, MFA} || MFA <- Types].
+
print_ext_types(#cl_state{report_mode = quiet}) ->
ok;
print_ext_types(#cl_state{output = Output,
external_calls = Calls,
external_types = Types,
stored_warnings = Warnings,
- output_format = Format}) ->
- case Types =:= [] of
+ output_format = Format,
+ legal_warnings = LegalWarnings}) ->
+ case not ordsets:is_element(?WARN_UNKNOWN, LegalWarnings)
+ orelse Types =:= [] of
true -> ok;
false ->
case Warnings =:= [] andalso Calls =:= [] of
@@ -738,6 +761,15 @@ do_print_ext_types(Output, [{M,F,A}|T], Before) ->
do_print_ext_types(_, [], _) ->
ok.
+unknown_behaviours(#cl_state{unknown_behaviours = DupBehaviours,
+ legal_warnings = LegalWarnings}) ->
+ case ordsets:is_element(?WARN_BEHAVIOUR, LegalWarnings) of
+ false -> [];
+ true ->
+ Behaviours = lists:usort(DupBehaviours),
+ [{unknown_behaviour, B} || B <- Behaviours]
+ end.
+
%%print_unknown_behaviours(#cl_state{report_mode = quiet}) ->
%% ok;
print_unknown_behaviours(#cl_state{output = Output,
diff --git a/lib/dialyzer/src/dialyzer_cl_parse.erl b/lib/dialyzer/src/dialyzer_cl_parse.erl
index 2ea3d3af5a..db27b2037d 100644
--- a/lib/dialyzer/src/dialyzer_cl_parse.erl
+++ b/lib/dialyzer/src/dialyzer_cl_parse.erl
@@ -2,7 +2,7 @@
%%-----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2013. 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
@@ -30,7 +30,7 @@
-type dial_cl_parse_ret() :: {'check_init', #options{}}
| {'plt_info', #options{}}
| {'cl', #options{}}
- | {{'gui', 'gs' | 'wx'}, #options{}}
+ | {'gui', #options{}}
| {'error', string()}.
-type deep_string() :: string() | [deep_string()].
@@ -193,12 +193,9 @@ cl(["--dump_callgraph", File|T]) ->
put(dialyzer_callgraph_file, File),
cl(T);
cl(["--gui"|T]) ->
- put(dialyzer_options_mode, {gui, gs}),
+ put(dialyzer_options_mode, gui),
cl(T);
-cl(["--wx"|T]) ->
- put(dialyzer_options_mode, {gui, wx}),
- cl(T);
-cl(["--solver",Solver|T]) -> % not documented
+cl(["--solver", Solver|T]) -> % not documented
append_var(dialyzer_solvers, [list_to_atom(Solver)]),
cl(T);
cl([H|_] = L) ->
@@ -217,7 +214,7 @@ cl([]) ->
{plt_info, cl_options()};
false ->
case get(dialyzer_options_mode) of
- {gui, _} = GUI -> {GUI, common_options()};
+ gui -> {gui, common_options()};
cl ->
case get(dialyzer_options_analysis_type) =:= plt_check of
true -> {check_init, cl_options()};
@@ -361,7 +358,7 @@ help_message() ->
S = "Usage: dialyzer [--help] [--version] [--shell] [--quiet] [--verbose]
[-pa dir]* [--plt plt] [--plts plt*] [-Ddefine]*
[-I include_dir]* [--output_plt file] [-Wwarn]*
- [--src] [--gui | --wx] [files_or_dirs] [-r dirs]
+ [--src] [--gui] [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]
@@ -473,9 +470,7 @@ Options:
--fullpath
Display the full path names of files for which warnings are emitted.
--gui
- Use the gs-based GUI.
- --wx
- Use the wx-based GUI.
+ Use the GUI.
Note:
* denotes that multiple occurrences of these options are possible.
diff --git a/lib/dialyzer/src/dialyzer_codeserver.erl b/lib/dialyzer/src/dialyzer_codeserver.erl
index 216e06219c..aab3d6add6 100644
--- a/lib/dialyzer/src/dialyzer_codeserver.erl
+++ b/lib/dialyzer/src/dialyzer_codeserver.erl
@@ -2,7 +2,7 @@
%%-----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2014. 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
@@ -64,6 +64,12 @@
-type dict_ets() :: ets:tid().
-type set_ets() :: ets:tid().
+-type types() :: erl_types:type_table().
+-type mod_records() :: dict:dict(module(), types()).
+
+-type contracts() :: dict:dict(mfa(),dialyzer_contracts:file_contract()).
+-type mod_contracts() :: dict:dict(module(), contracts()).
+
-record(codeserver, {next_core_label = 0 :: label(),
code :: dict_ets(),
exported_types :: set_ets(), % set(mfa())
@@ -160,12 +166,12 @@ insert(Mod, ModCode, CS) ->
true = ets:insert(CS#codeserver.code, [ModEntry|Funs]),
CS.
--spec get_temp_exported_types(codeserver()) -> set().
+-spec get_temp_exported_types(codeserver()) -> sets:set(mfa()).
get_temp_exported_types(#codeserver{temp_exported_types = TempExpTypes}) ->
ets_set_to_set(TempExpTypes).
--spec insert_temp_exported_types(set(), codeserver()) -> codeserver().
+-spec insert_temp_exported_types(sets:set(mfa()), codeserver()) -> codeserver().
insert_temp_exported_types(Set, CS) ->
TempExportedTypes = CS#codeserver.temp_exported_types,
@@ -183,17 +189,17 @@ insert_exports(List, #codeserver{exports = Exports} = CS) ->
is_exported(MFA, #codeserver{exports = Exports}) ->
ets_set_is_element(MFA, Exports).
--spec get_exported_types(codeserver()) -> set(). % set(mfa())
+-spec get_exported_types(codeserver()) -> sets:set(mfa()).
get_exported_types(#codeserver{exported_types = ExpTypes}) ->
ets_set_to_set(ExpTypes).
--spec get_exports(codeserver()) -> set(). % set(mfa())
+-spec get_exports(codeserver()) -> sets:set(mfa()).
get_exports(#codeserver{exports = Exports}) ->
ets_set_to_set(Exports).
--spec finalize_exported_types(set(), codeserver()) -> codeserver().
+-spec finalize_exported_types(sets:set(mfa()), codeserver()) -> codeserver().
finalize_exported_types(Set, CS) ->
ExportedTypes = ets_read_concurrent_table(dialyzer_codeserver_exported_types),
@@ -222,7 +228,7 @@ get_next_core_label(#codeserver{next_core_label = NCL}) ->
set_next_core_label(NCL, CS) ->
CS#codeserver{next_core_label = NCL}.
--spec lookup_mod_records(atom(), codeserver()) -> dict().
+-spec lookup_mod_records(atom(), codeserver()) -> types().
lookup_mod_records(Mod, #codeserver{records = RecDict}) when is_atom(Mod) ->
case ets_dict_find(Mod, RecDict) of
@@ -230,12 +236,12 @@ lookup_mod_records(Mod, #codeserver{records = RecDict}) when is_atom(Mod) ->
{ok, Dict} -> Dict
end.
--spec get_records(codeserver()) -> dict().
+-spec get_records(codeserver()) -> mod_records().
get_records(#codeserver{records = RecDict}) ->
ets_dict_to_dict(RecDict).
--spec store_temp_records(atom(), dict(), codeserver()) -> codeserver().
+-spec store_temp_records(module(), types(), codeserver()) -> codeserver().
store_temp_records(Mod, Dict, #codeserver{temp_records = TempRecDict} = CS)
when is_atom(Mod) ->
@@ -244,12 +250,12 @@ store_temp_records(Mod, Dict, #codeserver{temp_records = TempRecDict} = CS)
false -> CS#codeserver{temp_records = ets_dict_store(Mod, Dict, TempRecDict)}
end.
--spec get_temp_records(codeserver()) -> dict().
+-spec get_temp_records(codeserver()) -> mod_records().
get_temp_records(#codeserver{temp_records = TempRecDict}) ->
ets_dict_to_dict(TempRecDict).
--spec set_temp_records(dict(), codeserver()) -> codeserver().
+-spec set_temp_records(mod_records(), codeserver()) -> codeserver().
set_temp_records(Dict, CS) ->
true = ets:delete(CS#codeserver.temp_records),
@@ -257,7 +263,7 @@ set_temp_records(Dict, CS) ->
true = ets_dict_store_dict(Dict, TempRecords),
CS#codeserver{temp_records = TempRecords}.
--spec finalize_records(dict(), codeserver()) -> codeserver().
+-spec finalize_records(mod_records(), codeserver()) -> codeserver().
finalize_records(Dict, CS) ->
true = ets:delete(CS#codeserver.temp_records),
@@ -265,7 +271,7 @@ finalize_records(Dict, CS) ->
true = ets_dict_store_dict(Dict, Records),
CS#codeserver{records = Records, temp_records = clean}.
--spec lookup_mod_contracts(atom(), codeserver()) -> dict().
+-spec lookup_mod_contracts(atom(), codeserver()) -> contracts().
lookup_mod_contracts(Mod, #codeserver{contracts = ContDict})
when is_atom(Mod) ->
@@ -284,7 +290,7 @@ get_contract_pair(Key, ContDict) ->
lookup_mfa_contract(MFA, #codeserver{contracts = ContDict}) ->
ets_dict_find(MFA, ContDict).
--spec get_contracts(codeserver()) -> dict().
+-spec get_contracts(codeserver()) -> mod_contracts().
get_contracts(#codeserver{contracts = ContDict}) ->
ets_dict_to_dict(ContDict).
@@ -294,7 +300,7 @@ get_contracts(#codeserver{contracts = ContDict}) ->
get_callbacks(#codeserver{callbacks = CallbDict}) ->
ets:tab2list(CallbDict).
--spec store_temp_contracts(atom(), dict(), dict(), codeserver()) ->
+-spec store_temp_contracts(module(), contracts(), contracts(), codeserver()) ->
codeserver().
store_temp_contracts(Mod, SpecDict, CallbackDict,
@@ -313,13 +319,14 @@ store_temp_contracts(Mod, SpecDict, CallbackDict,
CS1#codeserver{temp_callbacks = ets_dict_store(Mod, CallbackDict, Cb)}
end.
--spec get_temp_contracts(codeserver()) -> {dict(), dict()}.
+-spec get_temp_contracts(codeserver()) -> {mod_contracts(), mod_contracts()}.
get_temp_contracts(#codeserver{temp_contracts = TempContDict,
temp_callbacks = TempCallDict}) ->
{ets_dict_to_dict(TempContDict), ets_dict_to_dict(TempCallDict)}.
--spec finalize_contracts(dict(), dict(), codeserver()) -> codeserver().
+-spec finalize_contracts(mod_contracts(), mod_contracts(), codeserver()) ->
+ codeserver().
finalize_contracts(SpecDict, CallbackDict, CS) ->
Contracts = ets_read_concurrent_table(dialyzer_codeserver_contracts),
diff --git a/lib/dialyzer/src/dialyzer_contracts.erl b/lib/dialyzer/src/dialyzer_contracts.erl
index 332a326b0d..283031eb9a 100644
--- a/lib/dialyzer/src/dialyzer_contracts.erl
+++ b/lib/dialyzer/src/dialyzer_contracts.erl
@@ -2,7 +2,7 @@
%%-----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2007-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2007-2014. 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
@@ -20,11 +20,13 @@
-module(dialyzer_contracts).
+-compile(export_all).
+
-export([check_contract/2,
- check_contracts/3,
+ check_contracts/4,
contracts_without_fun/3,
contract_to_string/1,
- get_invalid_contract_warnings/3,
+ get_invalid_contract_warnings/4,
get_contract_args/1,
get_contract_return/1,
get_contract_return/2,
@@ -52,7 +54,7 @@
%% to expand records and/or remote types that they might contain.
%%-----------------------------------------------------------------------
--type tmp_contract_fun() :: fun((set(), dict()) -> contract_pair()).
+-type tmp_contract_fun() :: fun((sets:set(mfa()), types()) -> contract_pair()).
-record(tmp_contract, {contract_funs = [] :: [tmp_contract_fun()],
forms = [] :: [{_, _}]}).
@@ -160,17 +162,24 @@ process_contract_remote_types(CodeServer) ->
dialyzer_codeserver:finalize_contracts(NewContractDict, NewCallbackDict,
CodeServer).
+-type opaques() :: [erl_types:erl_type()] | 'universe'.
+-type opaques_fun() :: fun((module()) -> opaques()).
+
+-type fun_types() :: dict:dict(label(), erl_types:type_table()).
+
-spec check_contracts([{mfa(), file_contract()}],
- dialyzer_callgraph:callgraph(), dict()) -> plt_contracts().
+ dialyzer_callgraph:callgraph(), fun_types(),
+ opaques_fun()) -> plt_contracts().
-check_contracts(Contracts, Callgraph, FunTypes) ->
+check_contracts(Contracts, Callgraph, FunTypes, FindOpaques) ->
FoldFun =
fun(Label, Type, NewContracts) ->
case dialyzer_callgraph:lookup_name(Label, Callgraph) of
{ok, {M,F,A} = MFA} ->
case orddict:find(MFA, Contracts) of
{ok, {_FileLine, Contract}} ->
- case check_contract(Contract, Type) of
+ Opaques = FindOpaques(M),
+ case check_contract(Contract, Type, Opaques) of
ok ->
case erl_bif_types:is_known(M, F, A) of
true ->
@@ -192,7 +201,10 @@ check_contracts(Contracts, Callgraph, FunTypes) ->
%% Checks all components of a contract
-spec check_contract(#contract{}, erl_types:erl_type()) -> 'ok' | {'error', term()}.
-check_contract(#contract{contracts = Contracts}, SuccType) ->
+check_contract(Contract, SuccType) ->
+ check_contract(Contract, SuccType, 'universe').
+
+check_contract(#contract{contracts = Contracts}, SuccType, Opaques) ->
try
Contracts1 = [{Contract, insert_constraints(Constraints, dict:new())}
|| {Contract, Constraints} <- Contracts],
@@ -203,9 +215,9 @@ check_contract(#contract{contracts = Contracts}, SuccType) ->
error ->
{error, {overlapping_contract, []}};
ok ->
- InfList = [erl_types:t_inf(Contract, SuccType, opaque)
+ InfList = [erl_types:t_inf(Contract, SuccType, Opaques)
|| Contract <- Contracts2],
- case check_contract_inf_list(InfList, SuccType) of
+ case check_contract_inf_list(InfList, SuccType, Opaques) of
{error, _} = Invalid -> Invalid;
ok -> check_extraneous(Contracts2, SuccType)
end
@@ -217,7 +229,7 @@ check_contract(#contract{contracts = Contracts}, SuccType) ->
check_domains([_]) -> ok;
check_domains([Dom|Doms]) ->
Fun = fun(D) ->
- erl_types:any_none_or_unit(erl_types:t_inf_lists(Dom, D, opaque))
+ erl_types:any_none_or_unit(erl_types:t_inf_lists(Dom, D))
end,
case lists:all(Fun, Doms) of
true -> check_domains(Doms);
@@ -227,23 +239,23 @@ check_domains([Dom|Doms]) ->
%% Allow a contract if one of the overloaded contracts is possible.
%% We used to be more strict, e.g., all overloaded contracts had to be
%% possible.
-check_contract_inf_list([FunType|Left], SuccType) ->
+check_contract_inf_list([FunType|Left], SuccType, Opaques) ->
FunArgs = erl_types:t_fun_args(FunType),
case lists:any(fun erl_types:t_is_none_or_unit/1, FunArgs) of
- true -> check_contract_inf_list(Left, SuccType);
+ true -> check_contract_inf_list(Left, SuccType, Opaques);
false ->
STRange = erl_types:t_fun_range(SuccType),
case erl_types:t_is_none_or_unit(STRange) of
true -> ok;
false ->
Range = erl_types:t_fun_range(FunType),
- case erl_types:t_is_none(erl_types:t_inf(STRange, Range, opaque)) of
- true -> check_contract_inf_list(Left, SuccType);
+ case erl_types:t_is_none(erl_types:t_inf(STRange, Range)) of
+ true -> check_contract_inf_list(Left, SuccType, Opaques);
false -> ok
end
end
end;
-check_contract_inf_list([], _SuccType) ->
+check_contract_inf_list([], _SuccType, _Opaques) ->
{error, invalid_contract}.
check_extraneous([], _SuccType) -> ok;
@@ -259,7 +271,7 @@ check_extraneous_1(Contract, SuccType) ->
STRng = erl_types:t_fun_range(SuccType),
?debug("CR = ~p\nSR = ~p\n", [CRngs, STRng]),
case [CR || CR <- CRngs,
- erl_types:t_is_none(erl_types:t_inf(CR, STRng, opaque))] of
+ erl_types:t_is_none(erl_types:t_inf(CR, STRng))] of
[] ->
CRngList = list_part(CRng),
STRngList = list_part(STRng),
@@ -268,7 +280,7 @@ check_extraneous_1(Contract, SuccType) ->
true ->
CRngElements = erl_types:t_list_elements(CRngList),
STRngElements = erl_types:t_list_elements(STRngList),
- Inf = erl_types:t_inf(CRngElements, STRngElements, opaque),
+ Inf = erl_types:t_inf(CRngElements, STRngElements),
case erl_types:t_is_none(Inf) of
true -> {error, invalid_contract};
false -> ok
@@ -278,13 +290,14 @@ check_extraneous_1(Contract, SuccType) ->
end.
list_part(Type) ->
- erl_types:t_inf(erl_types:t_list(), Type, opaque).
+ erl_types:t_inf(erl_types:t_list(), Type).
is_not_nil_list(Type) ->
erl_types:t_is_list(Type) andalso not erl_types:t_is_nil(Type).
%% This is the heart of the "range function"
--spec process_contracts([contract_pair()], [erl_types:erl_type()]) -> erl_types:erl_type().
+-spec process_contracts([contract_pair()], [erl_types:erl_type()]) ->
+ erl_types:erl_type().
process_contracts(OverContracts, Args) ->
process_contracts(OverContracts, Args, erl_types:t_none()).
@@ -299,7 +312,8 @@ process_contracts([OverContract|Left], Args, AccRange) ->
process_contracts([], _Args, AccRange) ->
AccRange.
--spec process_contract(contract_pair(), [erl_types:erl_type()]) -> 'error' | {'ok', erl_types:erl_type()}.
+-spec process_contract(contract_pair(), [erl_types:erl_type()]) ->
+ 'error' | {'ok', erl_types:erl_type()}.
process_contract({Contract, Constraints}, CallTypes0) ->
CallTypesFun = erl_types:t_fun(CallTypes0, erl_types:t_any()),
@@ -335,8 +349,11 @@ solve_constraints(Contract, Call, Constraints) ->
%% ?debug("Inf: ~s\n", [erl_types:t_to_string(Inf)]),
%% erl_types:t_assign_variables_to_subtype(Contract, Inf).
+-type contracts() :: dict:dict(mfa(),dialyzer_contracts:file_contract()).
+
%% Checks the contracts for functions that are not implemented
--spec contracts_without_fun(dict(), [_], dialyzer_callgraph:callgraph()) -> [dial_warning()].
+-spec contracts_without_fun(contracts(), [_], dialyzer_callgraph:callgraph()) ->
+ [dial_warning()].
contracts_without_fun(Contracts, AllFuns0, Callgraph) ->
AllFuns1 = [{dialyzer_callgraph:lookup_name(Label, Callgraph), Arity}
@@ -369,12 +386,15 @@ insert_constraints([{subtype, Type1, Type2}|Left], Dict) ->
end;
insert_constraints([], Dict) -> Dict.
--spec store_tmp_contract(mfa(), file_line(), [_], dict(), dict()) -> dict().
+-type types() :: erl_types:type_table().
+
+-spec store_tmp_contract(mfa(), file_line(), [_], contracts(), types()) ->
+ contracts().
store_tmp_contract(MFA, FileLine, TypeSpec, SpecDict, RecordsDict) ->
%% io:format("contract from form: ~p\n", [TypeSpec]),
TmpContract = contract_from_form(TypeSpec, RecordsDict, FileLine),
- %% io:format("contract: ~p\n", [Contract]),
+ %% io:format("contract: ~p\n", [TmpContract]),
dict:store(MFA, {FileLine, TmpContract}, SpecDict).
contract_from_form(Forms, RecDict, FileLine) ->
@@ -396,7 +416,8 @@ contract_from_form([{type, _, 'fun', [_, _]} = Form | Left], RecDict,
throw({error, NewMsg})
end,
NewType = erl_types:t_solve_remote(Type, ExpTypes, AllRecords),
- {NewType, []}
+ NewTypeNoVars = erl_types:subst_all_vars_to_any(NewType),
+ {NewTypeNoVars, []}
end,
NewTypeAcc = [TypeFun | TypeAcc],
NewFormAcc = [{Form, []} | FormAcc],
@@ -410,7 +431,8 @@ contract_from_form([{type, _L1, bounded_fun,
process_constraints(Constr, RecDict, ExpTypes, AllRecords),
Type = erl_types:t_from_form(Form, RecDict, VarDict),
NewType = erl_types:t_solve_remote(Type, ExpTypes, AllRecords),
- {NewType, Constr1}
+ NewTypeNoVars = erl_types:subst_all_vars_to_any(NewType),
+ {NewTypeNoVars, Constr1}
end,
NewTypeAcc = [TypeFun | TypeAcc],
NewFormAcc = [{Form, Constr} | FormAcc],
@@ -419,7 +441,8 @@ contract_from_form([], _RecDict, _FileLine, TypeAcc, FormAcc) ->
{lists:reverse(TypeAcc), lists:reverse(FormAcc)}.
process_constraints(Constrs, RecDict, ExpTypes, AllRecords) ->
- Init = initialize_constraints(Constrs, RecDict, ExpTypes, AllRecords),
+ Init0 = initialize_constraints(Constrs, RecDict, ExpTypes, AllRecords),
+ Init = remove_cycles(Init0),
constraints_fixpoint(Init, RecDict, ExpTypes, AllRecords).
initialize_constraints(Constrs, RecDict, ExpTypes, AllRecords) ->
@@ -459,12 +482,9 @@ constraints_fixpoint(OldVarDict, Constrs, RecDict, ExpTypes, AllRecords) ->
constraints_fixpoint(NewVarDict, Constrs, RecDict, ExpTypes, AllRecords)
end.
--define(TYPE_LIMIT, 4).
-
final_form(Form, RecDict, ExpTypes, AllRecords, VarDict) ->
T1 = erl_types:t_from_form(Form, RecDict, VarDict),
- T2 = erl_types:t_solve_remote(T1, ExpTypes, AllRecords),
- erl_types:t_limit(T2, ?TYPE_LIMIT).
+ erl_types:t_solve_remote(T1, ExpTypes, AllRecords).
constraints_to_dict(Constrs, RecDict, ExpTypes, AllRecords, VarDict) ->
Subtypes =
@@ -479,6 +499,74 @@ constraints_to_subs([C|Rest], RecDict, ExpTypes, AllRecords, VarDict, Acc) ->
NewAcc = [{subtype, T1, T2}|Acc],
constraints_to_subs(Rest, RecDict, ExpTypes, AllRecords, VarDict, NewAcc).
+%% Replaces variables with '_' when necessary to break up cycles among
+%% the constraints.
+
+remove_cycles(Constrs0) ->
+ Uses = find_uses(Constrs0),
+ G = digraph:new(),
+ Vs0 = [V || {V, _} <- Uses] ++ [V || {_, V} <- Uses],
+ Vs = lists:usort(Vs0),
+ lists:foreach(fun(V) -> _ = digraph:add_vertex(G, V) end, Vs),
+ lists:foreach(fun({From, To}) ->
+ _ = digraph:add_edge(G, {From, To}, From, To, [])
+ end, Uses),
+ ok = remove_cycles(G, Vs),
+ ToRemove = ordsets:subtract(ordsets:from_list(Uses),
+ ordsets:from_list(digraph:edges(G))),
+ Constrs = remove_uses(ToRemove, Constrs0),
+ digraph:delete(G),
+ Constrs.
+
+find_uses([{Var, Form}|Constrs]) ->
+ UsedVars = form_vars(Form, []),
+ VarName = erl_types:t_var_name(Var),
+ [{VarName, UsedVar} || UsedVar <- UsedVars] ++ find_uses(Constrs);
+find_uses([]) ->
+ [].
+
+form_vars({var, _, '_'}, Vs) -> Vs;
+form_vars({var, _, V}, Vs) -> [V|Vs];
+form_vars(T, Vs) when is_tuple(T) ->
+ form_vars(tuple_to_list(T), Vs);
+form_vars([E|Es], Vs) ->
+ form_vars(Es, form_vars(E, Vs));
+form_vars(_, Vs) -> Vs.
+
+remove_cycles(G, Vs) ->
+ NumberOfEdges = digraph:no_edges(G),
+ lists:foreach(fun(V) ->
+ case digraph:get_cycle(G, V) of
+ false -> true;
+ [V] -> digraph:del_edge(G, {V, V});
+ [V, V1|_] -> digraph:del_edge(G, {V, V1})
+ end
+ end, Vs),
+ case digraph:no_edges(G) =:= NumberOfEdges of
+ true -> ok;
+ false -> remove_cycles(G, Vs)
+ end.
+
+remove_uses([], Constrs) -> Constrs;
+remove_uses([{Var, Use}|ToRemove], Constrs0) ->
+ Constrs = remove_uses(Var, Use, Constrs0),
+ remove_uses(ToRemove, Constrs).
+
+remove_uses(_Var, _Use, []) -> [];
+remove_uses(Var, Use, [Constr|Constrs]) ->
+ {V, Form} = Constr,
+ case erl_types:t_var_name(V) =:= Var of
+ true -> [{V, remove_use(Form, Use)}|Constrs];
+ false -> [Constr|remove_uses(Var, Use, Constrs)]
+ end.
+
+remove_use({var, L, V}, V) -> {var, L, '_'};
+remove_use(T, V) when is_tuple(T) ->
+ list_to_tuple(remove_use(tuple_to_list(T), V));
+remove_use([E|Es], V) ->
+ [remove_use(E, V)|remove_use(Es, V)];
+remove_use(T, _V) -> T.
+
%% Gets the most general domain of a list of domains of all
%% the overloaded contracts
@@ -494,30 +582,35 @@ general_domain([], AccSig) ->
AccSig1 = erl_types:subst_all_vars_to_any(AccSig),
erl_types:t_fun_args(AccSig1).
--spec get_invalid_contract_warnings([module()], dialyzer_codeserver:codeserver(), dialyzer_plt:plt()) -> [dial_warning()].
+-spec get_invalid_contract_warnings([module()],
+ dialyzer_codeserver:codeserver(),
+ dialyzer_plt:plt(),
+ opaques_fun()) -> [dial_warning()].
-get_invalid_contract_warnings(Modules, CodeServer, Plt) ->
- get_invalid_contract_warnings_modules(Modules, CodeServer, Plt, []).
+get_invalid_contract_warnings(Modules, CodeServer, Plt, FindOpaques) ->
+ get_invalid_contract_warnings_modules(Modules, CodeServer, Plt, FindOpaques, []).
-get_invalid_contract_warnings_modules([Mod|Mods], CodeServer, Plt, Acc) ->
+get_invalid_contract_warnings_modules([Mod|Mods], CodeServer, Plt, FindOpaques, Acc) ->
Contracts1 = dialyzer_codeserver:lookup_mod_contracts(Mod, CodeServer),
Contracts2 = dict:to_list(Contracts1),
Records = dialyzer_codeserver:lookup_mod_records(Mod, CodeServer),
- NewAcc = get_invalid_contract_warnings_funs(Contracts2, Plt, Records, Acc),
- get_invalid_contract_warnings_modules(Mods, CodeServer, Plt, NewAcc);
-get_invalid_contract_warnings_modules([], _CodeServer, _Plt, Acc) ->
+ NewAcc = get_invalid_contract_warnings_funs(Contracts2, Plt, Records, FindOpaques, Acc),
+ get_invalid_contract_warnings_modules(Mods, CodeServer, Plt, FindOpaques, NewAcc);
+get_invalid_contract_warnings_modules([], _CodeServer, _Plt, _FindOpaques, Acc) ->
Acc.
get_invalid_contract_warnings_funs([{MFA, {FileLine, Contract}}|Left],
- Plt, RecDict, Acc) ->
+ Plt, RecDict, FindOpaques, Acc) ->
case dialyzer_plt:lookup(Plt, MFA) of
none ->
%% This must be a contract for a non-available function. Just accept it.
- get_invalid_contract_warnings_funs(Left, Plt, RecDict, Acc);
+ get_invalid_contract_warnings_funs(Left, Plt, RecDict, FindOpaques, Acc);
{value, {Ret, Args}} ->
Sig = erl_types:t_fun(Args, Ret),
+ {M, _F, _A} = MFA,
+ Opaques = FindOpaques(M),
NewAcc =
- case check_contract(Contract, Sig) of
+ case check_contract(Contract, Sig, Opaques) of
{error, invalid_contract} ->
[invalid_contract_warning(MFA, FileLine, Sig, RecDict)|Acc];
{error, {overlapping_contract, []}} ->
@@ -551,7 +644,7 @@ get_invalid_contract_warnings_funs([{MFA, {FileLine, Contract}}|Left],
BifArgs = erl_bif_types:arg_types(M, F, A),
BifRet = erl_bif_types:type(M, F, A),
BifSig = erl_types:t_fun(BifArgs, BifRet),
- case check_contract(Contract, BifSig) of
+ case check_contract(Contract, BifSig, Opaques) of
{error, _} ->
[invalid_contract_warning(MFA, FileLine, BifSig, RecDict)
|Acc];
@@ -564,9 +657,9 @@ get_invalid_contract_warnings_funs([{MFA, {FileLine, Contract}}|Left],
RecDict, Acc)
end
end,
- get_invalid_contract_warnings_funs(Left, Plt, RecDict, NewAcc)
+ get_invalid_contract_warnings_funs(Left, Plt, RecDict, FindOpaques, NewAcc)
end;
-get_invalid_contract_warnings_funs([], _Plt, _RecDict, Acc) ->
+get_invalid_contract_warnings_funs([], _Plt, _RecDict, _FindOpaques, Acc) ->
Acc.
invalid_contract_warning({M, F, A}, FileLine, SuccType, RecDict) ->
@@ -601,16 +694,23 @@ picky_contract_check(CSig0, Sig0, MFA, FileLine, Contract, RecDict, Acc) ->
end.
extra_contract_warning({M, F, A}, FileLine, Contract, CSig, Sig, RecDict) ->
- SigString = lists:flatten(dialyzer_utils:format_sig(Sig, RecDict)),
- ContractString0 = lists:flatten(dialyzer_utils:format_sig(CSig, RecDict)),
+ %% We do not want to depend upon erl_types:t_to_string() possibly
+ %% hiding the contents of opaque types.
+ SigUnopaque = erl_types:t_unopaque(Sig),
+ CSigUnopaque = erl_types:t_unopaque(CSig),
+ SigString0 =
+ lists:flatten(dialyzer_utils:format_sig(SigUnopaque, RecDict)),
+ ContractString0 =
+ lists:flatten(dialyzer_utils:format_sig(CSigUnopaque, RecDict)),
%% The only difference is in record fields containing 'undefined' or not.
- IsUndefRecordFieldsRelated = SigString =:= ContractString0,
+ IsUndefRecordFieldsRelated = SigString0 =:= ContractString0,
{IsRemoteTypesRelated, SubtypeRelation} =
is_remote_types_related(Contract, CSig, Sig, RecDict),
case IsUndefRecordFieldsRelated orelse IsRemoteTypesRelated of
true ->
no_warning;
false ->
+ SigString = lists:flatten(dialyzer_utils:format_sig(Sig, RecDict)),
ContractString = contract_to_string(Contract),
{Tag, Msg} =
case SubtypeRelation of
diff --git a/lib/dialyzer/src/dialyzer_dataflow.erl b/lib/dialyzer/src/dialyzer_dataflow.erl
index 6956850f1a..e0873b17f8 100644
--- a/lib/dialyzer/src/dialyzer_dataflow.erl
+++ b/lib/dialyzer/src/dialyzer_dataflow.erl
@@ -2,7 +2,7 @@
%%--------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2014. 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
@@ -41,27 +41,37 @@
-include("dialyzer.hrl").
+%%-import(helper, %% 'helper' could be any module doing sanity checks...
-import(erl_types,
- [any_none/1, t_any/0, t_atom/0, t_atom/1, t_atom_vals/1,
+ [t_inf/2, t_inf/3, t_inf_lists/2, t_inf_lists/3,
+ t_inf_lists/3, t_is_equal/2, t_is_subtype/2, t_subtract/2,
+ t_sup/1, t_sup/2]).
+
+-import(erl_types,
+ [any_none/1, t_any/0, t_atom/0, t_atom/1, t_atom_vals/1, t_atom_vals/2,
t_binary/0, t_boolean/0,
t_bitstr/0, t_bitstr/2, t_bitstr_concat/1, t_bitstr_match/2,
- t_cons/0, t_cons/2, t_cons_hd/1, t_cons_tl/1, t_contains_opaque/1,
+ t_cons/0, t_cons/2, t_cons_hd/2, t_cons_tl/2,
+ t_contains_opaque/2,
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_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,
- t_is_number/1, t_is_reference/1, t_is_pid/1, t_is_port/1,
- t_is_subtype/2, t_is_unit/1,
- t_limit/2, t_list/0, t_maybe_improper_list/0, t_module/0,
- t_none/0, t_non_neg_integer/0, t_number/0, t_number_vals/1,
- t_opaque_match_atom/2, t_opaque_match_record/2,
- t_opaque_matching_structure/2,
+ t_fun/0, t_fun/2, t_fun_args/1, t_fun_args/2, t_fun_range/1,
+ t_fun_range/2, t_integer/0, t_integers/1,
+ t_is_any/1, t_is_atom/1, t_is_atom/2, t_is_any_atom/3,
+ t_is_boolean/2,
+ t_is_integer/2, t_is_list/1,
+ t_is_nil/2, t_is_none/1, t_is_none_or_unit/1,
+ t_is_number/2, t_is_reference/2, t_is_pid/2, t_is_port/2,
+ t_is_unit/1,
+ t_limit/2, t_list/0, t_list_elements/2,
+ t_maybe_improper_list/0, t_module/0,
+ t_none/0, t_non_neg_integer/0, t_number/0, t_number_vals/2,
t_pid/0, t_port/0, t_product/1, t_reference/0,
- t_sup/1, t_sup/2, t_subtract/2, t_to_string/2, t_to_tlist/1,
- t_tuple/0, t_tuple/1, t_tuple_args/1, t_tuple_subtypes/1,
- t_unit/0, t_unopaque/1]).
+ t_to_string/2, t_to_tlist/1,
+ t_tuple/0, t_tuple/1, t_tuple_args/1, t_tuple_args/2,
+ t_tuple_subtypes/2,
+ t_unit/0, t_unopaque/2,
+ t_map/1
+ ]).
%%-define(DEBUG, true).
%%-define(DEBUG_PP, true).
@@ -76,39 +86,53 @@
%%--------------------------------------------------------------------
+-type type() :: erl_types:erl_type().
+-type types() :: erl_types:type_table().
+
-define(no_arg, no_arg).
-define(TYPE_LIMIT, 3).
-record(state, {callgraph :: dialyzer_callgraph:callgraph(),
- envs :: dict(),
- fun_tab :: dict(),
+ envs :: env_tab(),
+ fun_tab :: fun_tab(),
plt :: dialyzer_plt:plt(),
- opaques :: [erl_types:erl_type()],
+ opaques :: [type()],
races = dialyzer_races:new() :: dialyzer_races:races(),
- records = dict:new() :: dict(),
- tree_map :: dict(),
+ records = dict:new() :: types(),
+ tree_map :: dict:dict(label(), cerl:cerl()),
warning_mode = false :: boolean(),
warnings = [] :: [dial_warning()],
- work :: {[_], [_], set()},
+ work :: {[_], [_], sets:set()},
module :: module()
}).
--record(map, {dict = dict:new() :: dict(),
- subst = dict:new() :: dict(),
+-record(map, {dict = dict:new() :: type_tab(),
+ subst = dict:new() :: subst_tab(),
modified = [] :: [Key :: term()],
modified_stack = [] :: [{[Key :: term()],reference()}],
ref = undefined :: reference() | undefined}).
+-type nowarn() :: dialyzer_analysis_callgraph:no_warn_unused().
+-type env_tab() :: dict:dict(label(), #map{}).
+-type fun_entry() :: {Args :: [type()], RetType :: type()}.
+-type fun_tab() :: dict:dict('top' | label(),
+ {'not_handled', fun_entry()} | fun_entry()).
+-type key() :: label() | cerl:cerl().
+-type type_tab() :: dict:dict(key(), type()).
+-type subst_tab() :: dict:dict(key(), cerl:cerl()).
+
%% Exported Types
-opaque state() :: #state{}.
%%--------------------------------------------------------------------
+-type fun_types() :: dict:dict(label(), type()).
+
-spec get_warnings(cerl:c_module(), dialyzer_plt:plt(),
- dialyzer_callgraph:callgraph(), dict(), set()) ->
- {[dial_warning()], dict()}.
+ dialyzer_callgraph:callgraph(), types(), nowarn()) ->
+ {[dial_warning()], fun_types()}.
get_warnings(Tree, Plt, Callgraph, Records, NoWarnUnused) ->
State1 = analyze_module(Tree, Plt, Callgraph, Records, true),
@@ -119,7 +143,8 @@ get_warnings(Tree, Plt, Callgraph, Records, NoWarnUnused) ->
{State4#state.warnings, state__all_fun_types(State4)}.
-spec get_fun_types(cerl:c_module(), dialyzer_plt:plt(),
- dialyzer_callgraph:callgraph(), dict()) -> dict().
+ dialyzer_callgraph:callgraph(),
+ types()) -> fun_types().
get_fun_types(Tree, Plt, Callgraph, Records) ->
State = analyze_module(Tree, Plt, Callgraph, Records, false),
@@ -204,7 +229,7 @@ analyze_loop(State) ->
traverse(Tree, Map, State) ->
?debug("Handling ~p\n", [cerl:type(Tree)]),
- %%debug_pp_map(Map),
+ %% debug_pp_map(Map),
case cerl:type(Tree) of
alias ->
%% This only happens when checking for illegal record patterns
@@ -256,12 +281,7 @@ traverse(Tree, Map, State) ->
case cerl:unfold_literal(Tree) of
Tree ->
Type = literal_type(Tree),
- NewType =
- case erl_types:t_opaque_match_atom(Type, State#state.opaques) of
- [Opaque] -> Opaque;
- _ -> Type
- end,
- {State, Map, NewType};
+ {State, Map, Type};
NewTree -> traverse(NewTree, Map, State)
end;
module ->
@@ -286,8 +306,12 @@ traverse(Tree, Map, State) ->
SMA;
false ->
State2 =
- case (t_is_any(ArgType) orelse t_is_simple(ArgType)
- orelse is_call_to_send(Arg)) of
+ case
+ t_is_any(ArgType)
+ orelse t_is_simple(ArgType, State)
+ orelse is_call_to_send(Arg)
+ orelse is_lc_simple_list(Arg, ArgType, State)
+ of
true -> % do not warn in these cases
State1;
false ->
@@ -301,6 +325,10 @@ traverse(Tree, Map, State) ->
handle_try(Tree, Map, State);
tuple ->
handle_tuple(Tree, Map, State);
+ map ->
+ handle_map(Tree, Map, State);
+ map_pair ->
+ handle_map_pair(Tree, Map, State);
values ->
Elements = cerl:values_es(Tree),
{State1, Map1, EsType} = traverse_list(Elements, Map, State),
@@ -308,18 +336,10 @@ traverse(Tree, Map, State) ->
{State1, Map1, Type};
var ->
?debug("Looking up unknown variable: ~p\n", [Tree]),
- case state__lookup_type_for_rec_var(Tree, State) of
+ case state__lookup_type_for_letrec(Tree, State) of
error ->
LType = lookup_type(Tree, Map),
- Opaques = State#state.opaques,
- case t_opaque_match_record(LType, Opaques) of
- [Opaque] -> {State, Map, Opaque};
- _ ->
- case t_opaque_match_atom(LType, Opaques) of
- [Opaque] -> {State, Map, Opaque};
- _ -> {State, Map, LType}
- end
- end;
+ {State, Map, LType};
{ok, Type} -> {State, Map, Type}
end;
Other ->
@@ -343,20 +363,24 @@ traverse_list([], Map, State, Acc) ->
handle_apply(Tree, Map, State) ->
Args = cerl:apply_args(Tree),
Op = cerl:apply_op(Tree),
- {State1, Map1, ArgTypes} = traverse_list(Args, Map, State),
- {State2, Map2, OpType} = traverse(Op, Map1, State1),
+ {State0, Map1, ArgTypes} = traverse_list(Args, Map, State),
+ {State1, Map2, OpType} = traverse(Op, Map1, State0),
case any_none(ArgTypes) of
true ->
- {State2, Map2, t_none()};
+ {State1, Map2, t_none()};
false ->
- {CallSitesKnown, FunList} =
- case state__lookup_call_site(Tree, State2) of
- error -> {false, []};
- {ok, [external]} -> {false, []};
- {ok, List} -> {true, List}
+ FunList =
+ case state__lookup_call_site(Tree, State) of
+ error -> [external]; %% so that we go directly in the fallback
+ {ok, List} -> List
end,
- case CallSitesKnown of
- false ->
+ FunInfoList = [{local, state__fun_info(Fun, State)} || Fun <- FunList],
+ case
+ handle_apply_or_call(FunInfoList, Args, ArgTypes, Map2, Tree, State1)
+ of
+ {had_external, State2} ->
+ %% Fallback: use whatever info we collected from traversing the op
+ %% instead of the result that has been generalized to t_any().
Arity = length(Args),
OpType1 = t_inf(OpType, t_fun(Arity, t_any())),
case t_is_none(OpType1) of
@@ -367,7 +391,8 @@ handle_apply(Tree, Map, State) ->
Tree, Msg),
{State3, Map2, t_none()};
false ->
- NewArgs = t_inf_lists(ArgTypes, t_fun_args(OpType1)),
+ NewArgs = t_inf_lists(ArgTypes,
+ t_fun_args(OpType1, 'universe')),
case any_none(NewArgs) of
true ->
Msg = {fun_app_args,
@@ -378,7 +403,7 @@ handle_apply(Tree, Map, State) ->
{State3, enter_type(Op, OpType1, Map2), t_none()};
false ->
Map3 = enter_type_lists(Args, NewArgs, Map2),
- Range0 = t_fun_range(OpType1),
+ Range0 = t_fun_range(OpType1, 'universe'),
Range =
case t_is_unit(Range0) of
true -> t_none();
@@ -387,25 +412,23 @@ handle_apply(Tree, Map, State) ->
{State2, enter_type(Op, OpType1, Map3), Range}
end
end;
- true ->
- FunInfoList = [{local, state__fun_info(Fun, State)}
- || Fun <- FunList],
- handle_apply_or_call(FunInfoList, Args, ArgTypes, Map2, Tree, State1)
+ Normal -> Normal
end
end.
handle_apply_or_call(FunInfoList, Args, ArgTypes, Map, Tree, State) ->
None = t_none(),
handle_apply_or_call(FunInfoList, Args, ArgTypes, Map, Tree, State,
- [None || _ <- ArgTypes], None).
+ [None || _ <- ArgTypes], None, false).
handle_apply_or_call([{local, external}|Left], Args, ArgTypes, Map, Tree, State,
- _AccArgTypes, _AccRet) ->
+ _AccArgTypes, _AccRet, _HadExternal) ->
handle_apply_or_call(Left, Args, ArgTypes, Map, Tree, State,
- ArgTypes, t_any());
+ ArgTypes, t_any(), true);
handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left],
Args, ArgTypes, Map, Tree,
- #state{opaques = Opaques} = State, AccArgTypes, AccRet) ->
+ #state{opaques = Opaques} = State,
+ AccArgTypes, AccRet, HadExternal) ->
Any = t_any(),
AnyArgs = [Any || _ <- Args],
GenSig = {AnyArgs, fun(_) -> t_any() end},
@@ -423,83 +446,55 @@ 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) ->
- ArgPos = erl_bif_types:structure_inspecting_args(M, F, A),
- NewFunArgs =
- case ArgPos =:= [] of
- true -> FunArgs;
- false -> % some positions need to be un-opaqued
- N = length(FunArgs),
- PFs = lists:zip(lists:seq(1, N), FunArgs),
- [case ordsets:is_element(P, ArgPos) of
- true -> erl_types:t_unopaque(FArg, Opaques);
- false -> FArg
- end || {P, FArg} <- PFs]
- end,
- erl_bif_types:type(M, F, A, NewFunArgs)
+ erl_bif_types:type(M, F, A, FunArgs, Opaques)
end,
{BArgs, BRange};
- false -> IsBIF = false, GenSig
+ false ->
+ GenSig
end;
- local -> IsBIF = false, GenSig
+ local -> GenSig
end,
{SigArgs, SigRange} =
- %% if there is hard-coded or contract information with opaque types,
- %% the checking for possible type violations needs to take place w.r.t.
- %% this information and not w.r.t. the structure-based success typing.
- case prefer_opaque_types(CArgs, BifArgs) of
- true -> {AnyArgs, t_any()}; % effectively forgets the success typing
- false ->
- case Sig of
- {value, {SR, SA}} -> {SA, SR};
- none -> {AnyArgs, t_any()}
- end
- end,
- 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),
- {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}
+ case Sig of
+ {value, {SR, SA}} -> {SA, SR};
+ none -> {AnyArgs, t_any()}
end,
- ContrRet = CRange(TmpArgTypes),
- RetMode =
- case t_contains_opaque(ContrRet) orelse t_contains_opaque(BifRet) of
- true -> opaque;
- false -> structured
- end,
- RetWithoutContr = t_inf(SigRange, BifRet, RetMode),
- RetWithoutLocal = t_inf(ContrRet, RetWithoutContr, RetMode),
+
?debug("--------------------------------------------------------\n", []),
- ?debug("Fun: ~p\n", [Fun]),
- ?debug("Args: ~s\n", [erl_types:t_to_string(t_product(ArgTypes))]),
+ ?debug("Fun: ~p\n", [state__lookup_name(Fun, State)]),
+ ?debug("Module ~p\n", [State#state.module]),
+ ?debug("CArgs ~s\n", [erl_types:t_to_string(t_product(CArgs))]),
+ ?debug("ArgTypes ~s\n", [erl_types:t_to_string(t_product(ArgTypes))]),
+ ?debug("BifArgs ~p\n", [erl_types:t_to_string(t_product(BifArgs))]),
+
+ NewArgsSig = t_inf_lists(SigArgs, ArgTypes, Opaques),
+ ?debug("SigArgs ~s\n", [erl_types:t_to_string(t_product(SigArgs))]),
?debug("NewArgsSig: ~s\n", [erl_types:t_to_string(t_product(NewArgsSig))]),
+ NewArgsContract = t_inf_lists(CArgs, ArgTypes, Opaques),
?debug("NewArgsContract: ~s\n",
[erl_types:t_to_string(t_product(NewArgsContract))]),
+ NewArgsBif = t_inf_lists(BifArgs, ArgTypes, Opaques),
?debug("NewArgsBif: ~s\n", [erl_types:t_to_string(t_product(NewArgsBif))]),
- ?debug("NewArgTypes: ~s\n", [erl_types:t_to_string(t_product(NewArgTypes))]),
+ NewArgTypes0 = t_inf_lists(NewArgsSig, NewArgsContract),
+ NewArgTypes = t_inf_lists(NewArgTypes0, NewArgsBif, Opaques),
+ ?debug("NewArgTypes ~s\n", [erl_types:t_to_string(t_product(NewArgTypes))]),
+ ?debug("\n", []),
+
+ BifRet = BifRange(NewArgTypes),
+ ContrRet = CRange(NewArgTypes),
+ RetWithoutContr = t_inf(SigRange, BifRet),
+ RetWithoutLocal = t_inf(ContrRet, RetWithoutContr),
+
?debug("RetWithoutContr: ~s\n",[erl_types:t_to_string(RetWithoutContr)]),
?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(TmpArgTypes))]),
- ?debug("SigRet: ~s\n", [erl_types:t_to_string(SigRange)]),
+ ?debug("SigRange: ~s\n", [erl_types:t_to_string(SigRange)]),
+ ?debug("ContrRet: ~s\n", [erl_types:t_to_string(CRange(NewArgTypes))]),
+ ?debug("LocalRet: ~s\n", [erl_types:t_to_string(LocalRet)]),
+
State1 =
case is_race_analysis_enabled(State) of
true ->
@@ -513,6 +508,9 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left],
FailedConj = any_none([RetWithoutLocal|NewArgTypes]),
IsFailBif = t_is_none(BifRange(BifArgs)),
IsFailSig = t_is_none(SigRange),
+ ?debug("FailedConj: ~p~n", [FailedConj]),
+ ?debug("IsFailBif: ~p~n", [IsFailBif]),
+ ?debug("IsFailSig: ~p~n", [IsFailSig]),
State2 =
case FailedConj andalso not (IsFailBif orelse IsFailSig) of
true ->
@@ -532,14 +530,14 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left],
false ->
FailedSig = any_none(NewArgsSig),
FailedContract =
- any_none([CRange(TmpArgsContract)|NewArgsContract]),
+ any_none([CRange(NewArgsContract)|NewArgsContract]),
FailedBif = any_none([BifRange(NewArgsBif)|NewArgsBif]),
InfSig = t_inf(t_fun(SigArgs, SigRange),
- t_fun(BifArgs, BifRange(BifArgs))),
+ t_fun(BifArgs, BifRange(BifArgs))),
FailReason =
apply_fail_reason(FailedSig, FailedBif, FailedContract),
Msg = get_apply_fail_msg(Fun, Args, ArgTypes, NewArgTypes, InfSig,
- Contr, CArgs, State1, FailReason),
+ Contr, CArgs, State1, FailReason, Opaques),
WarnType = case Msg of
{call, _} -> ?WARN_FAILING_CALL;
{apply, _} -> ?WARN_FAILING_CALL;
@@ -547,7 +545,8 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left],
{call_without_opaque, _} -> ?WARN_OPAQUE;
{opaque_type_test, _} -> ?WARN_OPAQUE
end,
- state__add_warning(State1, WarnType, Tree, Msg)
+ Frc = {erlang, is_record, 3} =:= state__lookup_name(Fun, State),
+ state__add_warning(State1, WarnType, Tree, Msg, Frc)
end;
false -> State1
end,
@@ -571,16 +570,21 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left],
TotalRet =
case t_is_none(LocalRet) andalso t_is_unit(RetWithoutLocal) of
true -> RetWithoutLocal;
- false -> t_inf(RetWithoutLocal, LocalRet, opaque)
+ false -> t_inf(RetWithoutLocal, LocalRet)
end,
NewAccRet = t_sup(AccRet, TotalRet),
?debug("NewAccRet: ~s\n", [t_to_string(NewAccRet)]),
handle_apply_or_call(Left, Args, ArgTypes, Map, Tree,
- State3, NewAccArgTypes, NewAccRet);
+ State3, NewAccArgTypes, NewAccRet, HadExternal);
handle_apply_or_call([], Args, _ArgTypes, Map, _Tree, State,
- AccArgTypes, AccRet) ->
- NewMap = enter_type_lists(Args, AccArgTypes, Map),
- {State, NewMap, AccRet}.
+ AccArgTypes, AccRet, HadExternal) ->
+ case HadExternal of
+ false ->
+ NewMap = enter_type_lists(Args, AccArgTypes, Map),
+ {State, NewMap, AccRet};
+ true ->
+ {had_external, State}
+ end.
apply_fail_reason(FailedSig, FailedBif, FailedContract) ->
if
@@ -590,7 +594,7 @@ apply_fail_reason(FailedSig, FailedBif, FailedContract) ->
end.
get_apply_fail_msg(Fun, Args, ArgTypes, NewArgTypes,
- Sig, Contract, ContrArgs, State, FailReason) ->
+ Sig, Contract, ContrArgs, State, FailReason, Opaques) ->
ArgStrings = format_args(Args, ArgTypes, State),
ContractInfo =
case Contract of
@@ -599,44 +603,52 @@ get_apply_fail_msg(Fun, Args, ArgTypes, NewArgTypes,
dialyzer_contracts:contract_to_string(C)};
none -> {false, none}
end,
- EnumArgTypes =
- case NewArgTypes of
- [] -> [];
- _ -> lists:zip(lists:seq(1, length(NewArgTypes)), NewArgTypes)
- end,
+ EnumArgTypes = lists:zip(lists:seq(1, length(NewArgTypes)), NewArgTypes),
ArgNs = [Arg || {Arg, Type} <- EnumArgTypes, t_is_none(Type)],
case state__lookup_name(Fun, State) of
- {M, F, _A} ->
- case is_opaque_type_test_problem(Fun, NewArgTypes, State) of
- true ->
- [Opaque] = NewArgTypes,
- {opaque_type_test, [atom_to_list(F), erl_types:t_to_string(Opaque)]};
- false ->
+ {M, F, A} ->
+ case is_opaque_type_test_problem(Fun, Args, NewArgTypes, State) of
+ {yes, Arg, ArgType} ->
+ {opaque_type_test, [atom_to_list(F), ArgStrings,
+ format_arg(Arg), format_type(ArgType, State)]};
+ no ->
SigArgs = t_fun_args(Sig),
- case is_opaque_related_problem(ArgNs, ArgTypes) of
- true -> %% an opaque term is used where a structured term is expected
- ExpectedArgs =
- case FailReason of
- only_sig -> SigArgs;
- _ -> ContrArgs
- end,
- {call_with_opaque, [M, F, ArgStrings, ArgNs, ExpectedArgs]};
- false ->
- case is_opaque_related_problem(ArgNs, SigArgs) orelse
- is_opaque_related_problem(ArgNs, ContrArgs) of
- true -> %% a structured term is used where an opaque is expected
- ExpectedTriples =
- case FailReason of
- only_sig -> expected_arg_triples(ArgNs, SigArgs, State);
- _ -> expected_arg_triples(ArgNs, ContrArgs, State)
- end,
- {call_without_opaque, [M, F, ArgStrings, ExpectedTriples]};
- false -> %% there is a structured term clash in some argument
- {call, [M, F, ArgStrings,
- ArgNs, FailReason,
- format_sig_args(Sig, State),
- format_type(t_fun_range(Sig), State),
- ContractInfo]}
+ BadOpaque =
+ opaque_problems([SigArgs, ContrArgs], ArgTypes, Opaques, ArgNs),
+ %% In fact *both* 'call_with_opaque' and
+ %% 'call_without_opaque' are possible.
+ case lists:keyfind(decl, 1, BadOpaque) of
+ {decl, BadArgs} ->
+ %% a structured term is used where an opaque is expected
+ ExpectedTriples =
+ case FailReason of
+ only_sig -> expected_arg_triples(BadArgs, SigArgs, State);
+ _ -> expected_arg_triples(BadArgs, ContrArgs, State)
+ end,
+ {call_without_opaque, [M, F, ArgStrings, ExpectedTriples]};
+ false ->
+ case lists:keyfind(use, 1, BadOpaque) of
+ {use, BadArgs} ->
+ %% an opaque term is used where a structured term is expected
+ ExpectedArgs =
+ case FailReason of
+ only_sig -> SigArgs;
+ _ -> ContrArgs
+ end,
+ {call_with_opaque, [M, F, ArgStrings, BadArgs, ExpectedArgs]};
+ false ->
+ case
+ erl_bif_types:opaque_args(M, F, A, ArgTypes, Opaques)
+ of
+ [] -> %% there is a structured term clash in some argument
+ {call, [M, F, ArgStrings,
+ ArgNs, FailReason,
+ format_sig_args(Sig, State),
+ format_type(t_fun_range(Sig), State),
+ ContractInfo]};
+ Ns ->
+ {call_with_opaque, [M, F, ArgStrings, Ns, ContrArgs]}
+ end
end
end
end;
@@ -648,31 +660,48 @@ get_apply_fail_msg(Fun, Args, ArgTypes, NewArgTypes,
ContractInfo]}
end.
-%% returns 'true' if we are running with opaque on (not checked yet),
-%% and there is either a contract or hard-coded type information with
-%% opaque types
-%% TODO: check that we are running with opaque types
-%% TODO: check the return type also
-prefer_opaque_types(CArgs, BifArgs) ->
- t_contains_opaque(t_product(CArgs))
- orelse t_contains_opaque(t_product(BifArgs)).
-
-is_opaque_related_problem(ArgNs, ArgTypes) ->
- Fun = fun (N) -> erl_types:t_contains_opaque(lists:nth(N, ArgTypes)) end,
- ArgNs =/= [] andalso lists:all(Fun, ArgNs).
-
-is_opaque_type_test_problem(Fun, ArgTypes, State) ->
+%% -> [{ElementI, [ArgN]}] where [ArgN] is a non-empty list of
+%% arguments containing unknown opaque types and Element is 1 or 2.
+opaque_problems(ContractOrSigList, ArgTypes, Opaques, ArgNs) ->
+ ArgElementList = find_unknown(ContractOrSigList, ArgTypes, Opaques, ArgNs),
+ F = fun(1) -> decl; (2) -> use end,
+ [{F(ElementI), lists:usort([ArgN || {ArgN, EI} <- ArgElementList,
+ EI =:= ElementI])} ||
+ ElementI <- lists:usort([EI || {_, EI} <- ArgElementList])].
+
+%% -> [{ArgN, ElementI}] where ElementI = 1 means there is an unknown
+%% opaque type in argument ArgN of the the contract/signature,
+%% and ElementI = 2 means that there is an unknown opaque type in
+%% argument ArgN of the the (current) argument types.
+find_unknown(ContractOrSigList, ArgTypes, Opaques, NoneArgNs) ->
+ ArgNs = lists:seq(1, length(ArgTypes)),
+ [{ArgN, ElementI} ||
+ ContractOrSig <- ContractOrSigList,
+ {E1, E2, ArgN} <- lists:zip3(ContractOrSig, ArgTypes, ArgNs),
+ lists:member(ArgN, NoneArgNs),
+ ElementI <- erl_types:t_find_unknown_opaque(E1, E2, Opaques)].
+
+is_opaque_type_test_problem(Fun, Args, ArgTypes, State) ->
case Fun of
{erlang, FN, 1} when FN =:= is_atom; FN =:= is_boolean;
FN =:= is_binary; FN =:= is_bitstring;
FN =:= is_float; FN =:= is_function;
FN =:= is_integer; FN =:= is_list;
FN =:= is_number; FN =:= is_pid; FN =:= is_port;
- FN =:= is_reference; FN =:= is_tuple ->
- [Type] = ArgTypes,
- erl_types:t_is_opaque(Type) andalso
- not lists:member(Type, State#state.opaques);
- _ -> false
+ FN =:= is_reference; FN =:= is_tuple;
+ FN =:= is_map ->
+ type_test_opaque_arg(Args, ArgTypes, State#state.opaques);
+ {erlang, FN, 2} when FN =:= is_function ->
+ type_test_opaque_arg(Args, ArgTypes, State#state.opaques);
+ _ -> no
+ end.
+
+type_test_opaque_arg([], [], _Opaques) ->
+ no;
+type_test_opaque_arg([Arg|Args], [ArgType|ArgTypes], Opaques) ->
+ case erl_types:t_has_opaque_subtype(ArgType, Opaques) of
+ true -> {yes, Arg, ArgType};
+ false -> type_test_opaque_arg(Args, ArgTypes, Opaques)
end.
expected_arg_triples(ArgNs, ArgTypes, State) ->
@@ -683,47 +712,56 @@ 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),
- 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
+ Opaques = State#state.opaques,
+ Inf = t_inf(T1, T2, Opaques),
+ case
+ t_is_none(Inf) andalso (not any_none(Ts))
+ andalso (not is_int_float_eq_comp(T1, Op, T2, Opaques))
+ of
true ->
- Args = case erl_types:t_is_opaque(T1) of
- true -> [format_type(T2, State), Op, format_type(T1, State)];
- false -> [format_type(T1, State), Op, format_type(T2, State)]
- end,
- case any_opaque(Ts) of
- true ->
- state__add_warning(State, ?WARN_OPAQUE, Tree, {opaque_eq, Args});
- false ->
- state__add_warning(State, ?WARN_MATCHING, Tree, {exact_eq, Args})
+ %% Give priority to opaque warning (as usual).
+ case erl_types:t_find_unknown_opaque(T1, T2, Opaques) of
+ [] ->
+ Args = comp_format_args([], T1, Op, T2, State),
+ state__add_warning(State, ?WARN_MATCHING, Tree, {exact_eq, Args});
+ Ns ->
+ Args = comp_format_args(Ns, T1, Op, T2, State),
+ state__add_warning(State, ?WARN_OPAQUE, Tree, {opaque_eq, Args})
end;
false ->
State
end;
add_bif_warnings({erlang, Op, 2}, [T1, T2] = Ts, Tree, State)
when Op =:= '=/='; Op =:= '/=' ->
- Inf = t_inf(T1, T2),
- case t_is_none(Inf) andalso (not any_none(Ts))
- andalso (not is_int_float_eq_comp(T1, Op, T2)) andalso any_opaque(Ts) of
+ Opaques = State#state.opaques,
+ case
+ (not any_none(Ts))
+ andalso (not is_int_float_eq_comp(T1, Op, T2, Opaques))
+ of
true ->
- Args = case erl_types:t_is_opaque(T1) of
- true -> [format_type(T2, State), Op, format_type(T1, State)];
- false -> [format_type(T1, State), Op, format_type(T2, State)]
- end,
- state__add_warning(State, ?WARN_OPAQUE, Tree, {opaque_neq, Args});
+ case erl_types:t_find_unknown_opaque(T1, T2, Opaques) of
+ [] -> State;
+ Ns ->
+ Args = comp_format_args(Ns, T1, Op, T2, State),
+ state__add_warning(State, ?WARN_OPAQUE, Tree, {opaque_neq, Args})
+ end;
false ->
State
end;
add_bif_warnings(_, _, _, State) ->
State.
-is_int_float_eq_comp(T1, Op, T2) ->
+is_int_float_eq_comp(T1, Op, T2, Opaques) ->
(Op =:= '==' orelse Op =:= '/=') andalso
- ((erl_types:t_is_float(T1) andalso erl_types:t_is_integer(T2)) orelse
- (erl_types:t_is_integer(T1) andalso erl_types:t_is_float(T2))).
+ ((erl_types:t_is_float(T1, Opaques)
+ andalso t_is_integer(T2, Opaques)) orelse
+ (t_is_integer(T1, Opaques)
+ andalso erl_types:t_is_float(T2, Opaques))).
+
+comp_format_args([1|_], T1, Op, T2, State) ->
+ [format_type(T2, State), Op, format_type(T1, State)];
+comp_format_args(_, T1, Op, T2, State) ->
+ [format_type(T1, State), Op, format_type(T2, State)].
%%----------------------------------------
@@ -784,16 +822,27 @@ handle_bitstr(Tree, Map, State) ->
{State3, Map2, t_none()};
false ->
UnitVal = cerl:concrete(cerl:bitstr_unit(Tree)),
- Type =
- case t_number_vals(SizeType) of
- [OneSize] -> t_bitstr(0, OneSize * UnitVal);
- _ ->
- MinSize = erl_types:number_min(SizeType),
- t_bitstr(UnitVal, UnitVal * MinSize)
- end,
+ Opaques = State2#state.opaques,
+ NumberVals = t_number_vals(SizeType, Opaques),
+ {State3, Type} =
+ case t_contains_opaque(SizeType, Opaques) of
+ true ->
+ Msg = {opaque_size, [format_type(SizeType, State2),
+ format_cerl(Size)]},
+ {state__add_warning(State2, ?WARN_OPAQUE, Size, Msg),
+ t_none()};
+ false ->
+ case NumberVals of
+ [OneSize] -> {State2, t_bitstr(0, OneSize * UnitVal)};
+ unknown -> {State2, t_bitstr()};
+ _ ->
+ MinSize = erl_types:number_min(SizeType, Opaques),
+ {State2, t_bitstr(UnitVal, UnitVal * MinSize)}
+ end
+ end,
Map3 = enter_type_lists([Val, Size, Tree],
[ValType, SizeType, Type], Map2),
- {State2, Map3, Type}
+ {State3, Map3, Type}
end
end.
@@ -805,34 +854,47 @@ 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 *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)),
+ Opaques = State#state.opaques,
+ MType = t_inf(t_module(), MType0, Opaques),
+ FType = t_inf(t_atom(), FType0, Opaques),
Map2 = enter_type_lists([M, F], [MType, FType], Map1),
+ MOpaque = t_is_none(MType) andalso (not t_is_none(MType0)),
+ FOpaque = t_is_none(FType) andalso (not t_is_none(FType0)),
case any_none([MType, FType|As]) of
true ->
State2 =
- case t_is_none(MType) andalso (not t_is_none(MType0)) of
- true -> % This is a problem we just detected; not a known one
- MS = format_cerl(M),
- Msg = {app_call, [MS, format_cerl(F),
- format_args(Args, As, State1),
- MS, format_type(t_module(), State1),
- format_type(MType0, State1)]},
- state__add_warning(State1, ?WARN_FAILING_CALL, Tree, Msg);
- false ->
- case t_is_none(FType) andalso (not t_is_none(FType0)) of
- true ->
- FS = format_cerl(F),
- Msg = {app_call, [format_cerl(M), FS,
- format_args(Args, As, State1),
- FS, format_type(t_atom(), State1),
- format_type(FType0, State1)]},
- state__add_warning(State1, ?WARN_FAILING_CALL, Tree, Msg);
- false -> State1
- end
+ if
+ MOpaque -> % This is a problem we just detected; not a known one
+ MS = format_cerl(M),
+ case t_is_none(t_inf(t_module(), MType0)) of
+ true ->
+ Msg = {app_call, [MS, format_cerl(F),
+ format_args(Args, As, State1),
+ MS, format_type(t_module(), State1),
+ format_type(MType0, State1)]},
+ state__add_warning(State1, ?WARN_FAILING_CALL, Tree, Msg);
+ false ->
+ Msg = {opaque_call, [MS, format_cerl(F),
+ format_args(Args, As, State1),
+ MS, format_type(MType0, State1)]},
+ state__add_warning(State1, ?WARN_FAILING_CALL, Tree, Msg)
+ end;
+ FOpaque ->
+ FS = format_cerl(F),
+ case t_is_none(t_inf(t_atom(), FType0)) of
+ true ->
+ Msg = {app_call, [format_cerl(M), FS,
+ format_args(Args, As, State1),
+ FS, format_type(t_atom(), State1),
+ format_type(FType0, State1)]},
+ state__add_warning(State1, ?WARN_FAILING_CALL, Tree, Msg);
+ false ->
+ Msg = {opaque_call, [format_cerl(M), FS,
+ format_args(Args, As, State1),
+ FS, format_type(FType0, State1)]},
+ state__add_warning(State1, ?WARN_FAILING_CALL, Tree, Msg)
+ end;
+ true -> State1
end,
{State2, Map2, t_none()};
false ->
@@ -874,7 +936,7 @@ handle_case(Tree, Map, State) ->
handle_clauses(Clauses, Arg, ArgType, ArgType, State2,
[], Map2, [], []),
Map3 = join_maps_end(MapList, Map2),
- debug_pp_map(Map2),
+ debug_pp_map(Map3),
{State3, Map3, Type}
end.
@@ -886,7 +948,7 @@ handle_cons(Tree, Map, State) ->
{State1, Map1, HdType} = traverse(Hd, Map, State),
{State2, Map2, TlType} = traverse(Tl, Map1, State1),
State3 =
- case t_is_none(t_inf(TlType, t_list())) of
+ case t_is_none(t_inf(TlType, t_list(), State2#state.opaques)) of
true ->
Msg = {improper_list_constr, [format_type(TlType, State2)]},
state__add_warning(State2, ?WARN_NON_PROPER_LIST, Tree, Msg);
@@ -979,8 +1041,9 @@ handle_receive(Tree, Map, State) ->
[], []),
Map1 = join_maps(MapList, Map),
{State3, Map2, TimeoutType} = traverse(Timeout, Map1, State2),
- case (t_is_atom(TimeoutType) andalso
- (t_atom_vals(TimeoutType) =:= ['infinity'])) of
+ Opaques = State3#state.opaques,
+ case (t_is_atom(TimeoutType, Opaques) andalso
+ (t_atom_vals(TimeoutType, Opaques) =:= ['infinity'])) of
true ->
{State3, Map2, ReceiveType};
false ->
@@ -1023,6 +1086,19 @@ handle_try(Tree, Map, State) ->
%%----------------------------------------
+handle_map(Tree,Map,State) ->
+ Pairs = cerl:map_es(Tree),
+ {State1, Map1, TypePairs} = traverse_list(Pairs,Map,State),
+ {State1, Map1, t_map(TypePairs)}.
+
+handle_map_pair(Tree,Map,State) ->
+ Key = cerl:map_pair_key(Tree),
+ Val = cerl:map_pair_val(Tree),
+ {State1, Map1, [K,V]} = traverse_list([Key,Val],Map,State),
+ {State1, Map1, {K,V}}.
+
+%%----------------------------------------
+
handle_tuple(Tree, Map, State) ->
Elements = cerl:tuple_es(Tree),
{State1, Map1, EsType} = traverse_list(Elements, Map, State),
@@ -1031,55 +1107,46 @@ handle_tuple(Tree, Map, State) ->
true ->
{State1, Map1, t_none()};
false ->
- %% Let's find out if this is a record or opaque construction.
+ %% Let's find out if this is a record
case Elements of
[Tag|Left] ->
case cerl:is_c_atom(Tag) of
true ->
TagVal = cerl:atom_val(Tag),
- case t_opaque_match_record(TupleType, State1#state.opaques) of
- [Opaque] ->
- RecStruct = t_opaque_matching_structure(TupleType, Opaque),
- RecFields = t_tuple_args(RecStruct),
- case bind_pat_vars(Elements, RecFields, [], Map1, State1) of
- {error, _, ErrorPat, ErrorType, _} ->
- Msg = {record_constr,
- [TagVal, format_patterns(ErrorPat),
- format_type(ErrorType, State1)]},
- State2 = state__add_warning(State1, ?WARN_MATCHING,
- Tree, Msg),
- {State2, Map1, t_none()};
- {Map2, _ETypes} ->
- {State1, Map2, Opaque}
- end;
- _ ->
- case state__lookup_record(TagVal, length(Left), State1) of
- error -> {State1, Map1, TupleType};
- {ok, RecType} ->
- InfTupleType = t_inf(RecType, TupleType),
- case t_is_none(InfTupleType) of
- true ->
- RecC = format_type(TupleType, State1),
- FieldDiffs = format_field_diffs(TupleType, State1),
- Msg = {record_constr, [RecC, FieldDiffs]},
- State2 = state__add_warning(State1, ?WARN_MATCHING,
- Tree, Msg),
- {State2, Map1, t_none()};
- false ->
- case bind_pat_vars(Elements, t_tuple_args(RecType),
- [], Map1, State1) of
- {error, bind, ErrorPat, ErrorType, _} ->
- Msg = {record_constr,
- [TagVal, format_patterns(ErrorPat),
- format_type(ErrorType, State1)]},
- State2 = state__add_warning(State1, ?WARN_MATCHING,
- Tree, Msg),
- {State2, Map1, t_none()};
- {Map2, ETypes} ->
- {State1, Map2, t_tuple(ETypes)}
- end
- end
- end
+ case state__lookup_record(TagVal, length(Left), State1) of
+ error -> {State1, Map1, TupleType};
+ {ok, RecType} ->
+ InfTupleType = t_inf(RecType, TupleType),
+ case t_is_none(InfTupleType) of
+ true ->
+ RecC = format_type(TupleType, State1),
+ FieldDiffs = format_field_diffs(TupleType, State1),
+ Msg = {record_constr, [RecC, FieldDiffs]},
+ State2 = state__add_warning(State1, ?WARN_MATCHING,
+ Tree, Msg),
+ {State2, Map1, t_none()};
+ false ->
+ case bind_pat_vars(Elements, t_tuple_args(RecType),
+ [], Map1, State1) of
+ {error, bind, ErrorPat, ErrorType, _} ->
+ Msg = {record_constr,
+ [TagVal, format_patterns(ErrorPat),
+ format_type(ErrorType, State1)]},
+ State2 = state__add_warning(State1, ?WARN_MATCHING,
+ Tree, Msg),
+ {State2, Map1, t_none()};
+ {error, opaque, ErrorPat, ErrorType, OpaqueType} ->
+ Msg = {opaque_match,
+ [format_patterns(ErrorPat),
+ format_type(ErrorType, State1),
+ format_type(OpaqueType, State1)]},
+ State2 = state__add_warning(State1, ?WARN_OPAQUE,
+ Tree, Msg),
+ {State2, Map1, t_none()};
+ {Map2, ETypes} ->
+ {State1, Map2, t_tuple(ETypes)}
+ end
+ end
end;
false ->
{State1, Map1, t_tuple(EsType)}
@@ -1356,7 +1423,9 @@ bind_pat_vars_reverse(Pats, Types, Acc, Map, State) ->
end.
bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) ->
- ?debug("Binding pat: ~w to ~s\n", [cerl:type(Pat), format_type(Type, State)]),
+ ?debug("Binding pat: ~w to ~s\n", [cerl:type(Pat), format_type(Type, State)]
+),
+ Opaques = State#state.opaques,
{NewMap, TypeOut} =
case cerl:type(Pat) of
alias ->
@@ -1372,9 +1441,15 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) ->
case Rev of
true -> {Map, t_bitstr()};
false ->
- BinType = t_inf(t_bitstr(), Type),
+ BinType = t_inf(t_bitstr(), Type, Opaques),
case t_is_none(BinType) of
- true -> bind_error([Pat], Type, t_none(), bind);
+ true ->
+ case t_find_opaque_mismatch(t_bitstr(), Type) of
+ {ok, T1, T2} ->
+ bind_error([Pat], T1, T2, opaque);
+ error ->
+ bind_error([Pat], Type, t_none(), bind)
+ end;
false ->
Segs = cerl:binary_segments(Pat),
{Map1, SegTypes} = bind_bin_segs(Segs, BinType, Map, State),
@@ -1382,29 +1457,27 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) ->
end
end;
cons ->
- Cons = t_inf(Type, t_cons()),
+ Cons = t_inf(Type, t_cons(), Opaques),
case t_is_none(Cons) of
true ->
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)],
- [t_cons_hd(Cons), t_cons_tl(Cons)],
+ [t_cons_hd(Cons, Opaques),
+ t_cons_tl(Cons, Opaques)],
[], Map, State, Rev),
{Map1, t_cons(HdType, TlType)}
end;
literal ->
Literal = literal_type(Pat),
- LiteralOrOpaque =
- case t_opaque_match_atom(Literal, State#state.opaques) of
- [Opaque] -> Opaque;
- _ -> Literal
- end,
- case t_is_none(t_inf(LiteralOrOpaque, Type)) of
+ case t_is_none(t_inf(Literal, Type, Opaques)) of
true ->
bind_opaque_pats(Literal, Type, Pat, Map, State, Rev);
- false -> {Map, LiteralOrOpaque}
+ false -> {Map, Literal}
end;
+ map ->
+ {Map, t_map([])};
tuple ->
Es = cerl:tuple_es(Pat),
{TypedRecord, Prototype} =
@@ -1419,27 +1492,28 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) ->
{ok, Record} ->
[_Head|AnyTail] = [t_any() || _ <- Es],
UntypedRecord = t_tuple([t_atom(TagAtom)|AnyTail]),
- {not erl_types:t_is_equal(Record, UntypedRecord), Record}
+ {not t_is_equal(Record, UntypedRecord), Record}
end;
false -> {false, t_tuple(length(Es))}
end
end,
- Tuple = t_inf(Prototype, Type),
+ Tuple = t_inf(Prototype, Type, Opaques),
case t_is_none(Tuple) of
true ->
bind_opaque_pats(Prototype, Type, Pat, Map, State, Rev);
false ->
- SubTuples = t_tuple_subtypes(Tuple),
+ SubTuples = t_tuple_subtypes(Tuple, Opaques),
%% Need to call the top function to get the try-catch wrapper
MapJ = join_maps_begin(Map),
Results =
case Rev of
true ->
- [bind_pat_vars_reverse(Es, t_tuple_args(SubTuple), [],
- MapJ, State)
+ [bind_pat_vars_reverse(Es, t_tuple_args(SubTuple, Opaques),
+ [], MapJ, State)
|| SubTuple <- SubTuples];
false ->
- [bind_pat_vars(Es, t_tuple_args(SubTuple), [], MapJ, State)
+ [bind_pat_vars(Es, t_tuple_args(SubTuple, Opaques), [],
+ MapJ, State)
|| SubTuple <- SubTuples]
end,
case lists:keyfind(opaque, 2, Results) of
@@ -1466,37 +1540,14 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) ->
bind_pat_vars(Es, t_to_tlist(Type), [], Map, State, Rev),
{Map1, t_product(EsTypes)};
var ->
- Opaques = State#state.opaques,
VarType1 =
- case state__lookup_type_for_rec_var(Pat, State) of
- error ->
- LType = lookup_type(Pat, Map),
- case t_opaque_match_record(LType, Opaques) of
- [Opaque] -> Opaque;
- _ ->
- case t_opaque_match_atom(LType, Opaques) of
- [Opaque] -> Opaque;
- _ -> LType
- end
- end;
+ case state__lookup_type_for_letrec(Pat, State) of
+ error -> lookup_type(Pat, Map);
{ok, RecType} -> RecType
end,
%% Must do inf when binding args to pats. Vars in pats are fresh.
- VarType2 = t_inf(VarType1, Type),
- VarType3 =
- case Opaques =/= [] of
- true ->
- case t_opaque_match_record(VarType2, Opaques) of
- [OpaqueRec] -> OpaqueRec;
- _ ->
- case t_opaque_match_atom(VarType2, Opaques) of
- [OpaqueAtom] -> OpaqueAtom;
- _ -> VarType2
- end
- end;
- false -> VarType2
- end,
- case t_is_none(VarType3) of
+ VarType2 = t_inf(VarType1, Type, Opaques),
+ case t_is_none(VarType2) of
true ->
case t_find_opaque_mismatch(VarType1, Type) of
{ok, T1, T2} ->
@@ -1505,8 +1556,8 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) ->
bind_error([Pat], Type, t_none(), bind)
end;
false ->
- Map1 = enter_type(Pat, VarType3, Map),
- {Map1, VarType3}
+ Map1 = enter_type(Pat, VarType2, Map),
+ {Map1, VarType2}
end;
_Other ->
%% Catch all is needed when binding args to pats
@@ -1529,7 +1580,8 @@ bind_bin_segs([Seg|Segs], BinType, Acc, Map, State) ->
binary = SegType, [] = Segs, %% just an assert
T = t_inf(t_bitstr(UnitVal, 0), BinType),
{Map1, [Type]} = bind_pat_vars([Val], [T], [], Map, State, false),
- bind_bin_segs(Segs, t_bitstr(0, 0), [Type|Acc], Map1, State);
+ Type1 = remove_local_opaque_types(Type, State#state.opaques),
+ bind_bin_segs(Segs, t_bitstr(0, 0), [Type1|Acc], Map1, State);
utf -> % XXX: possibly can be strengthened
true = lists:member(SegType, [utf8, utf16, utf32]),
{Map1, [_]} = bind_pat_vars([Val], [t_integer()], [], Map, State, false),
@@ -1539,11 +1591,17 @@ bind_bin_segs([Seg|Segs], BinType, Acc, Map, State) ->
Size = cerl:bitstr_size(Seg),
{Map1, [SizeType]} =
bind_pat_vars([Size], [t_non_neg_integer()], [], Map, State, false),
+ Opaques = State#state.opaques,
+ NumberVals = t_number_vals(SizeType, Opaques),
+ case t_contains_opaque(SizeType, Opaques) of
+ true -> bind_error([Seg], SizeType, t_none(), opaque);
+ false -> ok
+ end,
Type =
- case t_number_vals(SizeType) of
+ case NumberVals of
[OneSize] -> t_bitstr(0, UnitVal * OneSize);
- _ ->
- MinSize = erl_types:number_min(SizeType),
+ _ -> % 'unknown' too
+ MinSize = erl_types:number_min(SizeType, Opaques),
t_bitstr(UnitVal, UnitVal * MinSize)
end,
ValConstr =
@@ -1551,7 +1609,7 @@ bind_bin_segs([Seg|Segs], BinType, Acc, Map, State) ->
binary -> Type; %% The same constraints as for the whole bitstr
float -> t_float();
integer ->
- case t_number_vals(SizeType) of
+ case NumberVals of
unknown -> t_integer();
List ->
SizeVal = lists:max(List),
@@ -1579,7 +1637,7 @@ bind_error(Pats, Type, OpaqueType, Error) ->
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
+ case erl_types:is_opaque_type(T2, State#state.opaques) of
true ->
NewType = erl_types:t_struct_from_opaque(Type, [T2]),
{Map1, _} =
@@ -1630,6 +1688,8 @@ bind_guard(Guard, Map, Env, Eval, State) ->
Es0 = cerl:tuple_es(Guard),
{Map1, Es} = bind_guard_list(Es0, Map, Env, dont_know, State),
{Map1, t_tuple(Es)};
+ map ->
+ {Map, t_map([])};
'let' ->
Arg = cerl:let_arg(Guard),
[Var] = cerl:let_vars(Guard),
@@ -1700,19 +1760,9 @@ handle_guard_call(Guard, Map, Env, Eval, State) ->
handle_guard_gen_fun({M, F, A}, Guard, Map, Env, Eval, State) ->
Args = cerl:call_args(Guard),
- {Map1, As0} = bind_guard_list(Args, Map, Env, dont_know, State),
- MapFun = fun(Type) ->
- case lists:member(Type, State#state.opaques) of
- true -> erl_types:t_opaque_structure(Type);
- false -> Type
- end
- end,
- As = lists:map(MapFun, As0),
- Mode = case As =:= As0 of
- true -> structured;
- false -> opaque
- end,
- BifRet = erl_bif_types:type(M, F, A, As),
+ {Map1, As} = bind_guard_list(Args, Map, Env, dont_know, State),
+ Opaques = State#state.opaques,
+ BifRet = erl_bif_types:type(M, F, A, As, Opaques),
case t_is_none(BifRet) of
true ->
%% Is this an error-bif?
@@ -1721,11 +1771,8 @@ handle_guard_gen_fun({M, F, A}, Guard, Map, Env, Eval, State) ->
false -> signal_guard_fatal_fail(Eval, Guard, As, State)
end;
false ->
- BifArgs = case erl_bif_types:arg_types(M, F, A) of
- unknown -> lists:duplicate(A, t_any());
- List -> List
- end,
- Map2 = enter_type_lists(Args, t_inf_lists(BifArgs, As0, Mode), Map1),
+ BifArgs = bif_args(M, F, A),
+ Map2 = enter_type_lists(Args, t_inf_lists(BifArgs, As, Opaques), Map1),
Ret =
case Eval of
pos -> t_inf(t_atom(true), BifRet);
@@ -1771,29 +1818,19 @@ bind_type_test(Eval, TypeTest, ArgType, State) ->
is_reference -> t_reference();
is_tuple -> t_tuple()
end,
- Mode = determine_mode(ArgType, State#state.opaques),
case Eval of
pos ->
- Inf = t_inf(Type, ArgType, Mode),
+ Inf = t_inf(Type, ArgType, State#state.opaques),
case t_is_none(Inf) of
true -> error;
false -> {ok, Inf, t_atom(true)}
end;
neg ->
- case Mode of
- opaque ->
- Struct = erl_types:t_opaque_structure(ArgType),
- case t_is_none(t_subtract(Struct, Type)) of
- true -> error;
- false -> {ok, ArgType, t_atom(false)}
- end;
- structured ->
- Sub = t_subtract(ArgType, Type),
- case t_is_none(Sub) of
- true -> error;
- false -> {ok, Sub, t_atom(false)}
- end
- end;
+ Sub = t_subtract(ArgType, Type),
+ case t_is_none(Sub) of
+ true -> error;
+ false -> {ok, Sub, t_atom(false)}
+ end;
dont_know ->
{ok, ArgType, t_boolean()}
end.
@@ -1802,9 +1839,10 @@ handle_guard_comp(Guard, Comp, Map, Env, Eval, State) ->
Args = cerl:call_args(Guard),
[Arg1, Arg2] = Args,
{Map1, ArgTypes} = bind_guard_list(Args, Map, Env, dont_know, State),
+ Opaques = State#state.opaques,
[Type1, Type2] = ArgTypes,
- IsInt1 = t_is_integer(Type1),
- IsInt2 = t_is_integer(Type2),
+ IsInt1 = t_is_integer(Type1, Opaques),
+ IsInt2 = t_is_integer(Type2, Opaques),
case {cerl:type(Arg1), cerl:type(Arg2)} of
{literal, literal} ->
case erlang:Comp(cerl:concrete(Arg1), cerl:concrete(Arg2)) of
@@ -1817,12 +1855,13 @@ handle_guard_comp(Guard, Comp, Map, Env, Eval, State) ->
false when Eval =:= neg -> {Map, t_atom(false)}
end;
{literal, var} when IsInt1 andalso IsInt2 andalso (Eval =:= pos) ->
- case bind_comp_literal_var(Arg1, Arg2, Type2, Comp, Map1) of
+ case bind_comp_literal_var(Arg1, Arg2, Type2, Comp, Map1, Opaques) of
error -> signal_guard_fail(Eval, Guard, ArgTypes, State);
{ok, NewMap} -> {NewMap, t_atom(true)}
end;
{var, literal} when IsInt1 andalso IsInt2 andalso (Eval =:= pos) ->
- case bind_comp_literal_var(Arg2, Arg1, Type1, invert_comp(Comp), Map1) of
+ case bind_comp_literal_var(Arg2, Arg1, Type1, invert_comp(Comp),
+ Map1, Opaques) of
error -> signal_guard_fail(Eval, Guard, ArgTypes, State);
{ok, NewMap} -> {NewMap, t_atom(true)}
end;
@@ -1835,10 +1874,10 @@ invert_comp('<') -> '>';
invert_comp('>=') -> '=<';
invert_comp('>') -> '<'.
-bind_comp_literal_var(Lit, Var, VarType, CompOp, Map) ->
+bind_comp_literal_var(Lit, Var, VarType, CompOp, Map, Opaques) ->
LitVal = cerl:concrete(Lit),
NewVarType =
- case t_number_vals(VarType) of
+ case t_number_vals(VarType, Opaques) of
unknown ->
Range =
case CompOp of
@@ -1847,7 +1886,7 @@ bind_comp_literal_var(Lit, Var, VarType, CompOp, Map) ->
'>=' -> t_from_range(neg_inf, LitVal);
'>' -> t_from_range(neg_inf, LitVal - 1)
end,
- t_inf(Range, VarType);
+ t_inf(Range, VarType, Opaques);
NumberVals ->
NewNumberVals = [X || X <- NumberVals, erlang:CompOp(LitVal, X)],
t_integers(NewNumberVals)
@@ -1861,17 +1900,18 @@ handle_guard_is_function(Guard, Map, Env, Eval, State) ->
Args = cerl:call_args(Guard),
{Map1, ArgTypes0} = bind_guard_list(Args, Map, Env, dont_know, State),
[FunType0, ArityType0] = ArgTypes0,
- ArityType = t_inf(ArityType0, t_integer()),
+ Opaques = State#state.opaques,
+ ArityType = t_inf(ArityType0, t_integer(), Opaques),
case t_is_none(ArityType) of
true -> signal_guard_fail(Eval, Guard, ArgTypes0, State);
false ->
FunTypeConstr =
- case t_number_vals(ArityType) of
+ case t_number_vals(ArityType, State#state.opaques) of
unknown -> t_fun();
Vals ->
t_sup([t_fun(lists:duplicate(X, t_any()), t_any()) || X <- Vals])
end,
- FunType = t_inf(FunType0, FunTypeConstr),
+ FunType = t_inf(FunType0, FunTypeConstr, Opaques),
case t_is_none(FunType) of
true ->
case Eval of
@@ -1896,33 +1936,45 @@ handle_guard_is_record(Guard, Map, Env, Eval, State) ->
Arity = cerl:int_val(Arity0),
{Map1, RecType} = bind_guard(Rec, Map, Env, dont_know, State),
ArityMin1 = Arity - 1,
- TupleType =
- case state__lookup_record(Tag, ArityMin1, State) of
- error -> t_tuple([t_atom(Tag)|lists:duplicate(ArityMin1, t_any())]);
- {ok, Prototype} -> Prototype
- end,
- Mode = determine_mode(RecType, State#state.opaques),
- NewTupleType =
- case t_opaque_match_record(TupleType, State#state.opaques) of
- [Opaque] -> Opaque;
- _ -> TupleType
- end,
- Type = t_inf(NewTupleType, RecType, Mode),
- case t_is_none(Type) of
+ Opaques = State#state.opaques,
+ Tuple = t_tuple([t_atom(Tag)|lists:duplicate(ArityMin1, t_any())]),
+ case t_is_none(t_inf(Tuple, RecType, Opaques)) of
true ->
- case Eval of
- pos -> signal_guard_fail(Eval, Guard,
- [RecType, t_from_term(Tag),
- t_from_term(Arity)],
- State);
- neg -> {Map1, t_atom(false)};
- dont_know -> {Map1, t_atom(false)}
+ case erl_types:t_has_opaque_subtype(RecType, Opaques) of
+ true ->
+ signal_guard_fail(Eval, Guard,
+ [RecType, t_from_term(Tag),
+ t_from_term(Arity)],
+ State);
+ false ->
+ case Eval of
+ pos -> signal_guard_fail(Eval, Guard,
+ [RecType, t_from_term(Tag),
+ t_from_term(Arity)],
+ State);
+ neg -> {Map1, t_atom(false)};
+ dont_know -> {Map1, t_atom(false)}
+ end
end;
false ->
- case Eval of
- pos -> {enter_type(Rec, Type, Map1), t_atom(true)};
- neg -> {Map1, t_atom(false)};
- dont_know -> {Map1, t_boolean()}
+ TupleType =
+ case state__lookup_record(Tag, ArityMin1, State) of
+ error -> Tuple;
+ {ok, Prototype} -> Prototype
+ end,
+ Type = t_inf(TupleType, RecType, State#state.opaques),
+ case t_is_none(Type) of
+ true ->
+ %% No special handling of opaque errors.
+ FArgs = "record " ++ format_type(RecType, State),
+ Msg = {record_matching, [FArgs, Tag]},
+ throw({fail, {Guard, Msg}});
+ false ->
+ case Eval of
+ pos -> {enter_type(Rec, Type, Map1), t_atom(true)};
+ neg -> {Map1, t_atom(false)};
+ dont_know -> {Map1, t_boolean()}
+ end
end
end.
@@ -1975,14 +2027,24 @@ handle_guard_eq(Guard, Map, Env, Eval, State) ->
bind_eq_guard(Guard, Arg1, Arg2, Map, Env, Eval, State) ->
{Map1, Type1} = bind_guard(Arg1, Map, Env, dont_know, State),
{Map2, Type2} = bind_guard(Arg2, Map1, Env, dont_know, State),
- case (t_is_nil(Type1) orelse t_is_nil(Type2) orelse
- t_is_atom(Type1) orelse t_is_atom(Type2)) of
+ Opaques = State#state.opaques,
+ case
+ t_is_nil(Type1, Opaques) orelse t_is_nil(Type2, Opaques)
+ orelse t_is_atom(Type1, Opaques) orelse t_is_atom(Type2, Opaques)
+ of
true -> bind_eqeq_guard(Guard, Arg1, Arg2, Map, Env, Eval, State);
false ->
- case Eval of
- pos -> {Map2, t_atom(true)};
- neg -> {Map2, t_atom(false)};
- dont_know -> {Map2, t_boolean()}
+ %% XXX. Is this test OK?
+ OpArgs = erl_types:t_find_unknown_opaque(Type1, Type2, Opaques),
+ case OpArgs =:= [] of
+ true ->
+ case Eval of
+ pos -> {Map2, t_atom(true)};
+ neg -> {Map2, t_atom(false)};
+ dont_know -> {Map2, t_boolean()}
+ end;
+ false ->
+ signal_guard_fail(Eval, Guard, [Type1, Type2], State)
end
end.
@@ -2021,44 +2083,52 @@ bind_eqeq_guard(Guard, Arg1, Arg2, Map, Env, Eval, State) ->
{Map2, Type2} = bind_guard(Arg2, Map1, Env, dont_know, State),
?debug("Types are:~s =:= ~s\n", [t_to_string(Type1),
t_to_string(Type2)]),
- Inf = t_inf(Type1, Type2),
+ Opaques = State#state.opaques,
+ Inf = t_inf(Type1, Type2, Opaques),
case t_is_none(Inf) of
true ->
- case Eval of
- neg -> {Map2, t_atom(false)};
- dont_know -> {Map2, t_atom(false)};
- pos -> signal_guard_fail(Eval, Guard, [Type1, Type2], State)
+ OpArgs = erl_types:t_find_unknown_opaque(Type1, Type2, Opaques),
+ case OpArgs =:= [] of
+ true ->
+ case Eval of
+ neg -> {Map2, t_atom(false)};
+ dont_know -> {Map2, t_atom(false)};
+ pos -> signal_guard_fail(Eval, Guard, [Type1, Type2], State)
+ end;
+ false ->
+ signal_guard_fail(Eval, Guard, [Type1, Type2], State)
end;
false ->
case Eval of
- pos ->
- case {cerl:type(Arg1), cerl:type(Arg2)} of
- {var, var} ->
- Map3 = enter_subst(Arg1, Arg2, Map2),
- Map4 = enter_type(Arg2, Inf, Map3),
- {Map4, t_atom(true)};
- {var, _} ->
- Map3 = enter_type(Arg1, Inf, Map2),
- {Map3, t_atom(true)};
- {_, var} ->
- Map3 = enter_type(Arg2, Inf, Map2),
- {Map3, t_atom(true)};
- {_, _} ->
- {Map2, t_atom(true)}
- end;
- neg ->
- {Map2, t_atom(false)};
- dont_know ->
- {Map2, t_boolean()}
+ pos ->
+ case {cerl:type(Arg1), cerl:type(Arg2)} of
+ {var, var} ->
+ Map3 = enter_subst(Arg1, Arg2, Map2),
+ Map4 = enter_type(Arg2, Inf, Map3),
+ {Map4, t_atom(true)};
+ {var, _} ->
+ Map3 = enter_type(Arg1, Inf, Map2),
+ {Map3, t_atom(true)};
+ {_, var} ->
+ Map3 = enter_type(Arg2, Inf, Map2),
+ {Map3, t_atom(true)};
+ {_, _} ->
+ {Map2, t_atom(true)}
+ end;
+ neg ->
+ {Map2, t_atom(false)};
+ dont_know ->
+ {Map2, t_boolean()}
end
end.
bind_eqeq_guard_lit_other(Guard, Arg1, Arg2, Map, Env, State) ->
Eval = dont_know,
+ Opaques = State#state.opaques,
case cerl:concrete(Arg1) of
true ->
{_, Type} = MT = bind_guard(Arg2, Map, Env, pos, State),
- case t_is_atom(true, Type) of
+ case t_is_any_atom(true, Type, Opaques) of
true -> MT;
false ->
{_, Type0} = bind_guard(Arg2, Map, Env, Eval, State),
@@ -2066,7 +2136,7 @@ bind_eqeq_guard_lit_other(Guard, Arg1, Arg2, Map, Env, State) ->
end;
false ->
{Map1, Type} = bind_guard(Arg2, Map, Env, neg, State),
- case t_is_atom(false, Type) of
+ case t_is_any_atom(false, Type, Opaques) of
true -> {Map1, t_atom(true)};
false ->
{_, Type0} = bind_guard(Arg2, Map, Env, Eval, State),
@@ -2087,14 +2157,15 @@ bind_eqeq_guard_lit_other(Guard, Arg1, Arg2, Map, Env, State) ->
handle_guard_and(Guard, Map, Env, Eval, State) ->
[Arg1, Arg2] = cerl:call_args(Guard),
+ Opaques = State#state.opaques,
case Eval of
pos ->
{Map1, Type1} = bind_guard(Arg1, Map, Env, Eval, State),
- case t_is_atom(true, Type1) of
+ case t_is_any_atom(true, Type1, Opaques) of
false -> signal_guard_fail(Eval, Guard, [Type1, t_any()], State);
true ->
{Map2, Type2} = bind_guard(Arg2, Map1, Env, Eval, State),
- case t_is_atom(true, Type2) of
+ case t_is_any_atom(true, Type2, Opaques) of
false -> signal_guard_fail(Eval, Guard, [Type1, Type2], State);
true -> {Map2, t_atom(true)}
end
@@ -2109,7 +2180,10 @@ handle_guard_and(Guard, Map, Env, Eval, State) ->
try bind_guard(Arg2, MapJ, Env, neg, State)
catch throw:{fail, _} -> bind_guard(Arg1, MapJ, Env, pos, State)
end,
- case t_is_atom(false, Type1) orelse t_is_atom(false, Type2) of
+ case
+ t_is_any_atom(false, Type1, Opaques)
+ orelse t_is_any_atom(false, Type2, Opaques)
+ of
true -> {join_maps_end([Map1, Map2], MapJ), t_atom(false)};
false -> signal_guard_fail(Eval, Guard, [Type1, Type2], State)
end;
@@ -2124,11 +2198,16 @@ handle_guard_and(Guard, Map, Env, Eval, State) ->
false ->
NewMap = join_maps_end([Map1, Map2], MapJ),
NewType =
- case {t_atom_vals(Bool1), t_atom_vals(Bool2)} of
+ case {t_atom_vals(Bool1, Opaques), t_atom_vals(Bool2, Opaques)} of
{['true'] , ['true'] } -> t_atom(true);
{['false'], _ } -> t_atom(false);
{_ , ['false']} -> t_atom(false);
+ {unknown , _ } ->
+ signal_guard_fail(Eval, Guard, [Type1, Type2], State);
+ {_ , unknown } ->
+ signal_guard_fail(Eval, Guard, [Type1, Type2], State);
{_ , _ } -> t_boolean()
+
end,
{NewMap, NewType}
end
@@ -2136,6 +2215,7 @@ handle_guard_and(Guard, Map, Env, Eval, State) ->
handle_guard_or(Guard, Map, Env, Eval, State) ->
[Arg1, Arg2] = cerl:call_args(Guard),
+ Opaques = State#state.opaques,
case Eval of
pos ->
MapJ = join_maps_begin(Map),
@@ -2149,19 +2229,23 @@ handle_guard_or(Guard, Map, Env, Eval, State) ->
catch
throw:{fail,_} -> bind_guard(Arg2, MapJ, Env, dont_know, State)
end,
- case ((t_is_atom(true, Bool1) andalso t_is_boolean(Bool2))
- orelse
- (t_is_atom(true, Bool2) andalso t_is_boolean(Bool1))) of
+ case
+ ((t_is_any_atom(true, Bool1, Opaques)
+ andalso t_is_boolean(Bool2, Opaques))
+ orelse
+ (t_is_any_atom(true, Bool2, Opaques)
+ andalso t_is_boolean(Bool1, Opaques)))
+ of
true -> {join_maps_end([Map1, Map2], MapJ), t_atom(true)};
false -> signal_guard_fail(Eval, Guard, [Bool1, Bool2], State)
end;
neg ->
{Map1, Type1} = bind_guard(Arg1, Map, Env, neg, State),
- case t_is_atom(false, Type1) of
+ case t_is_any_atom(false, Type1, Opaques) of
false -> signal_guard_fail(Eval, Guard, [Type1, t_any()], State);
true ->
{Map2, Type2} = bind_guard(Arg2, Map1, Env, neg, State),
- case t_is_atom(false, Type2) of
+ case t_is_any_atom(false, Type2, Opaques) of
false -> signal_guard_fail(Eval, Guard, [Type1, Type2], State);
true -> {Map2, t_atom(false)}
end
@@ -2177,10 +2261,14 @@ handle_guard_or(Guard, Map, Env, Eval, State) ->
false ->
NewMap = join_maps_end([Map1, Map2], MapJ),
NewType =
- case {t_atom_vals(Bool1), t_atom_vals(Bool2)} of
+ case {t_atom_vals(Bool1, Opaques), t_atom_vals(Bool2, Opaques)} of
{['false'], ['false']} -> t_atom(false);
{['true'] , _ } -> t_atom(true);
{_ , ['true'] } -> t_atom(true);
+ {unknown , _ } ->
+ signal_guard_fail(Eval, Guard, [Type1, Type2], State);
+ {_ , unknown } ->
+ signal_guard_fail(Eval, Guard, [Type1, Type2], State);
{_ , _ } -> t_boolean()
end,
{NewMap, NewType}
@@ -2189,10 +2277,11 @@ handle_guard_or(Guard, Map, Env, Eval, State) ->
handle_guard_not(Guard, Map, Env, Eval, State) ->
[Arg] = cerl:call_args(Guard),
+ Opaques = State#state.opaques,
case Eval of
neg ->
{Map1, Type} = bind_guard(Arg, Map, Env, pos, State),
- case t_is_atom(true, Type) of
+ case t_is_any_atom(true, Type, Opaques) of
true -> {Map1, t_atom(false)};
false ->
{_, Type0} = bind_guard(Arg, Map, Env, Eval, State),
@@ -2200,7 +2289,7 @@ handle_guard_not(Guard, Map, Env, Eval, State) ->
end;
pos ->
{Map1, Type} = bind_guard(Arg, Map, Env, neg, State),
- case t_is_atom(false, Type) of
+ case t_is_any_atom(false, Type, Opaques) of
true -> {Map1, t_atom(true)};
false ->
{_, Type0} = bind_guard(Arg, Map, Env, Eval, State),
@@ -2212,10 +2301,11 @@ handle_guard_not(Guard, Map, Env, Eval, State) ->
case t_is_none(Bool) of
true -> throw({fatal_fail, none});
false ->
- case t_atom_vals(Bool) of
+ case t_atom_vals(Bool, Opaques) of
['true'] -> {Map1, t_atom(false)};
['false'] -> {Map1, t_atom(true)};
- [_, _] -> {Map1, Bool}
+ [_, _] -> {Map1, Bool};
+ unknown -> signal_guard_fail(Eval, Guard, [Type], State)
end
end
end.
@@ -2231,31 +2321,47 @@ bind_guard_list([], Map, _Env, _Eval, _State, Acc) ->
-type eval() :: 'pos' | 'neg' | 'dont_know'.
--spec signal_guard_fail(eval(), cerl:c_call(), [erl_types:erl_type()],
+-spec signal_guard_fail(eval(), cerl:c_call(), [type()],
state()) -> no_return().
signal_guard_fail(Eval, Guard, ArgTypes, State) ->
+ signal_guard_failure(Eval, Guard, ArgTypes, fail, State).
+
+-spec signal_guard_fatal_fail(eval(), cerl:c_call(), [erl_types:erl_type()],
+ state()) -> no_return().
+
+signal_guard_fatal_fail(Eval, Guard, ArgTypes, State) ->
+ signal_guard_failure(Eval, Guard, ArgTypes, fatal_fail, State).
+
+signal_guard_failure(Eval, Guard, ArgTypes, Tag, State) ->
Args = cerl:call_args(Guard),
F = cerl:atom_val(cerl:call_name(Guard)),
- MFA = {cerl:atom_val(cerl:call_module(Guard)), F, length(Args)},
- Msg =
+ {M, F, A} = MFA = {cerl:atom_val(cerl:call_module(Guard)), F, length(Args)},
+ Opaques = State#state.opaques,
+ {Kind, XInfo} =
+ case erl_bif_types:opaque_args(M, F, A, ArgTypes, Opaques) of
+ [] ->
+ {case Eval of
+ neg -> neg_guard_fail;
+ pos -> guard_fail;
+ dont_know -> guard_fail
+ end,
+ []};
+ Ns -> {opaque_guard, [Ns]}
+ end,
+ FArgs =
case is_infix_op(MFA) of
true ->
[ArgType1, ArgType2] = ArgTypes,
[Arg1, Arg2] = Args,
- Kind =
- case Eval of
- neg -> neg_guard_fail;
- pos -> guard_fail;
- dont_know -> guard_fail
- end,
- {Kind, [format_args_1([Arg1], [ArgType1], State),
- atom_to_list(F),
- format_args_1([Arg2], [ArgType2], State)]};
+ [format_args_1([Arg1], [ArgType1], State),
+ atom_to_list(F),
+ format_args_1([Arg2], [ArgType2], State)] ++ XInfo;
false ->
- mk_guard_msg(Eval, F, Args, ArgTypes, State)
+ [F, format_args(Args, ArgTypes, State)]
end,
- throw({fail, {Guard, Msg}}).
+ Msg = {Kind, FArgs},
+ throw({Tag, {Guard, Msg}}).
is_infix_op({erlang, '=:=', 2}) -> true;
is_infix_op({erlang, '==', 2}) -> true;
@@ -2268,25 +2374,10 @@ 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(eval(), cerl:c_call(), [erl_types:erl_type()],
- state()) -> no_return().
-
-signal_guard_fatal_fail(Eval, Guard, ArgTypes, State) ->
- Args = cerl:call_args(Guard),
- F = cerl:atom_val(cerl:call_name(Guard)),
- Msg = mk_guard_msg(Eval, F, Args, ArgTypes, State),
- throw({fatal_fail, {Guard, Msg}}).
-
-mk_guard_msg(Eval, F, Args, ArgTypes, State) ->
- FArgs = [F, format_args(Args, ArgTypes, State)],
- case any_has_opaque_subtype(ArgTypes) of
- true -> {opaque_guard, FArgs};
- false ->
- case Eval of
- neg -> {neg_guard_fail, FArgs};
- pos -> {guard_fail, FArgs};
- dont_know -> {guard_fail, FArgs}
- end
+bif_args(M, F, A) ->
+ case erl_bif_types:arg_types(M, F, A) of
+ unknown -> lists:duplicate(A, t_any());
+ List -> List
end.
bind_guard_case_clauses(Arg, Clauses, Map0, Env, Eval, State) ->
@@ -2366,14 +2457,15 @@ bind_guard_case_clauses(GenArgType, GenMap, ArgExpr, [Clause|Left],
end,
{NewMap3, CType} = bind_guard(cerl:clause_body(Clause), NewMap2,
Env, Eval, State),
+ Opaques = State#state.opaques,
case Eval of
pos ->
- case t_is_atom(true, CType) of
+ case t_is_any_atom(true, CType, Opaques) of
true -> ok;
false -> throw({fail, none})
end;
neg ->
- case t_is_atom(false, CType) of
+ case t_is_any_atom(false, CType, Opaques) of
true -> ok;
false -> throw({fail, none})
end;
@@ -2501,8 +2593,11 @@ enter_type(Key, Val, MS) ->
error ->
?debug("Entering ~p :: ~s\n", [KeyLabel, t_to_string(Val)]),
case dict:find(KeyLabel, Dict) of
- {ok, Val} -> MS;
- {ok, _OldVal} -> store_map(KeyLabel, Val, MS);
+ {ok, Value} ->
+ case erl_types:t_is_equal(Val, Value) of
+ true -> MS;
+ false -> store_map(KeyLabel, Val, MS)
+ end;
error -> store_map(KeyLabel, Val, MS)
end
end
@@ -2611,10 +2706,15 @@ get_label(L) when is_integer(L) ->
get_label(T) ->
cerl_trees:get_label(T).
-t_is_simple(ArgType) ->
- t_is_atom(ArgType) orelse t_is_number(ArgType) orelse t_is_port(ArgType)
- orelse t_is_pid(ArgType) orelse t_is_reference(ArgType)
- orelse t_is_nil(ArgType).
+t_is_simple(ArgType, State) ->
+ Opaques = State#state.opaques,
+ t_is_atom(ArgType, Opaques) orelse t_is_number(ArgType, Opaques)
+ orelse t_is_port(ArgType, Opaques)
+ orelse t_is_pid(ArgType, Opaques) orelse t_is_reference(ArgType, Opaques)
+ orelse t_is_nil(ArgType, Opaques).
+
+remove_local_opaque_types(Type, Opaques) ->
+ t_unopaque(Type, Opaques).
%% t_is_structured(ArgType) ->
%% case t_is_nil(ArgType) of
@@ -2638,11 +2738,12 @@ is_call_to_send(Tree) ->
andalso (Arity =:= 2)
end.
-any_opaque(Ts) ->
- lists:any(fun erl_types:t_is_opaque/1, Ts).
-
-any_has_opaque_subtype(Ts) ->
- lists:any(fun erl_types:t_has_opaque_subtype/1, Ts).
+is_lc_simple_list(Tree, TreeType, State) ->
+ Opaques = State#state.opaques,
+ Ann = cerl:get_ann(Tree),
+ lists:member(list_comprehension, Ann)
+ andalso t_is_list(TreeType)
+ andalso t_is_simple(t_list_elements(TreeType, Opaques), State).
filter_match_fail([Clause] = Cls) ->
Body = cerl:clause_body(Clause),
@@ -2662,12 +2763,6 @@ filter_match_fail([]) ->
%% receive after 1 -> ok end
[].
-determine_mode(Type, Opaques) ->
- case lists:member(Type, Opaques) of
- true -> opaque;
- false -> structured
- end.
-
%%% ===========================================================================
%%%
%%% The State.
@@ -2679,7 +2774,7 @@ state__new(Callgraph, Tree, Plt, Module, Records) ->
erl_types:t_opaque_from_records(Records),
TreeMap = build_tree_map(Tree),
Funs = dict:fetch_keys(TreeMap),
- FunTab = init_fun_tab(Funs, dict:new(), TreeMap, Callgraph, Plt, Opaques),
+ FunTab = init_fun_tab(Funs, dict:new(), TreeMap, Callgraph, Plt),
ExportedFuns =
[Fun || Fun <- Funs--[top], dialyzer_callgraph:is_escaping(Fun, Callgraph)],
Work = init_work(ExportedFuns),
@@ -2740,12 +2835,14 @@ state__add_warning(#state{warnings = Warnings, warning_mode = true} = State,
case Force of
true ->
Warn = {Tag, {get_file(Ann), abs(get_line(Ann))}, Msg},
+ ?debug("MSG ~s\n", [dialyzer:format_warning(Warn)]),
State#state{warnings = [Warn|Warnings]};
false ->
case is_compiler_generated(Ann) of
true -> State;
false ->
Warn = {Tag, {get_file(Ann), get_line(Ann)}, Msg},
+ ?debug("MSG ~s\n", [dialyzer:format_warning(Warn)]),
State#state{warnings = [Warn|Warnings]}
end
end.
@@ -2829,12 +2926,11 @@ state__get_warnings(#state{tree_map = TreeMap, fun_tab = FunTab,
state__is_escaping(Fun, #state{callgraph = Callgraph}) ->
dialyzer_callgraph:is_escaping(Fun, Callgraph).
-state__lookup_type_for_rec_var(Var, #state{callgraph = Callgraph} = State) ->
+state__lookup_type_for_letrec(Var, #state{callgraph = Callgraph} = State) ->
Label = get_label(Var),
- case dialyzer_callgraph:lookup_rec_var(Label, Callgraph) of
+ case dialyzer_callgraph:lookup_letrec(Label, Callgraph) of
error -> error;
- {ok, MFA} ->
- {ok, FunLabel} = dialyzer_callgraph:lookup_label(MFA, Callgraph),
+ {ok, FunLabel} ->
{ok, state__fun_type(FunLabel, State)}
end.
@@ -2876,10 +2972,10 @@ build_tree_map(Tree) ->
end,
cerl_trees:fold(Fun, dict:new(), Tree).
-init_fun_tab([top|Left], Dict, TreeMap, Callgraph, Plt, Opaques) ->
+init_fun_tab([top|Left], Dict, TreeMap, Callgraph, Plt) ->
NewDict = dict:store(top, {[], t_none()}, Dict),
- init_fun_tab(Left, NewDict, TreeMap, Callgraph, Plt, Opaques);
-init_fun_tab([Fun|Left], Dict, TreeMap, Callgraph, Plt, Opaques) ->
+ init_fun_tab(Left, NewDict, TreeMap, Callgraph, Plt);
+init_fun_tab([Fun|Left], Dict, TreeMap, Callgraph, Plt) ->
Arity = cerl:fun_arity(dict:fetch(Fun, TreeMap)),
FunEntry =
case dialyzer_callgraph:is_escaping(Fun, Callgraph) of
@@ -2896,8 +2992,8 @@ init_fun_tab([Fun|Left], Dict, TreeMap, Callgraph, Plt, Opaques) ->
false -> {not_handled, {lists:duplicate(Arity, t_none()), t_unit()}}
end,
NewDict = dict:store(Fun, FunEntry, Dict),
- init_fun_tab(Left, NewDict, TreeMap, Callgraph, Plt, Opaques);
-init_fun_tab([], Dict, _TreeMap, _Callgraph, _Plt, _Opaques) ->
+ init_fun_tab(Left, NewDict, TreeMap, Callgraph, Plt);
+init_fun_tab([], Dict, _TreeMap, _Callgraph, _Plt) ->
?debug("DICT:~p\n",[dict:to_list(Dict)]),
Dict.
@@ -2946,34 +3042,27 @@ state__update_fun_entry(Tree, ArgTypes, Out0,
if Fun =:= top -> Out0;
true ->
case lookup_fun_sig(Fun, CG, Plt) of
- {value, {SigRet, _}} -> t_inf(SigRet, Out0, opaque);
+ {value, {SigRet, _}} -> t_inf(SigRet, Out0);
none -> Out0
end
end,
Out = t_limit(Out1, ?TYPE_LIMIT),
- case dict:find(Fun, FunTab) of
- {ok, {ArgTypes, OldOut}} ->
- case t_is_equal(OldOut, Out) of
- true ->
- ?debug("Fixpoint for ~w: ~s\n",
- [state__lookup_name(Fun, State),
- t_to_string(t_fun(ArgTypes, Out))]),
- State;
- false ->
- NewEntry = {ArgTypes, Out},
- ?debug("New Entry for ~w: ~s\n",
- [state__lookup_name(Fun, State),
- t_to_string(t_fun(ArgTypes, Out))]),
- NewFunTab = dict:store(Fun, NewEntry, FunTab),
- State1 = State#state{fun_tab = NewFunTab},
- state__add_work_from_fun(Tree, State1)
- end;
- {ok, {NewArgTypes, _OldOut}} ->
- %% Can only happen in self-recursive functions. Only update the out type.
- NewEntry = {NewArgTypes, Out},
+ {ok, {OldArgTypes, OldOut}} = dict:find(Fun, FunTab),
+ SameArgs = lists:all(fun({A, B}) -> erl_types:t_is_equal(A, B)
+ end, lists:zip(OldArgTypes, ArgTypes)),
+ SameOut = t_is_equal(OldOut, Out),
+ if
+ SameArgs, SameOut ->
+ ?debug("Fixpoint for ~w: ~s\n",
+ [state__lookup_name(Fun, State),
+ t_to_string(t_fun(ArgTypes, Out))]),
+ State;
+ true ->
+ %% Can only happen in self-recursive functions.
+ NewEntry = {OldArgTypes, Out},
?debug("New Entry for ~w: ~s\n",
[state__lookup_name(Fun, State),
- t_to_string(t_fun(NewArgTypes, Out))]),
+ t_to_string(t_fun(OldArgTypes, Out))]),
NewFunTab = dict:store(Fun, NewEntry, FunTab),
State1 = State#state{fun_tab = NewFunTab},
state__add_work_from_fun(Tree, State1)
@@ -2994,7 +3083,7 @@ state__add_work_from_fun(Tree, #state{callgraph = Callgraph,
%% Must filter the result for results in this module.
FilteredList = [L || {ok, L} <- LabelList, dict:is_key(L, TreeMap)],
?debug("~w: Will try to add:~w\n",
- [state__lookup_name(get_label(Tree), State), MFAList]),
+ [state__lookup_name(Label, State), MFAList]),
lists:foldl(fun(L, AccState) ->
state__add_work(L, AccState)
end, State, FilteredList)
@@ -3055,7 +3144,8 @@ forward_args(Fun, ArgTypes, #state{work = Work, fun_tab = FunTab} = State) ->
case Fixpoint of
true -> State;
false ->
- NewArgTypes = [t_sup(X, Y) || {X, Y} <- lists:zip(ArgTypes, OldArgTypes)],
+ NewArgTypes = [t_sup(X, Y) ||
+ {X, Y} <- lists:zip(ArgTypes, OldArgTypes)],
NewWork = add_work(Fun, Work),
?debug("~w: forwarding args ~s\n",
[state__lookup_name(Fun, State),
@@ -3093,7 +3183,7 @@ state__get_callgraph(#state{callgraph = Callgraph}) ->
state__get_races(#state{races = Races}) ->
Races.
--spec state__get_records(state()) -> dict().
+-spec state__get_records(state()) -> types().
state__get_records(#state{records = Records}) ->
Records.
@@ -3192,7 +3282,7 @@ get_file([_|Tail]) -> get_file(Tail).
is_compiler_generated(Ann) ->
lists:member(compiler_generated, Ann) orelse (get_line(Ann) < 1).
--spec format_args([cerl:cerl()], [erl_types:erl_type()], state()) ->
+-spec format_args([cerl:cerl()], [type()], state()) ->
nonempty_string().
format_args([], [], _State) ->
@@ -3227,25 +3317,25 @@ format_arg(Arg) ->
Default
end.
--spec format_type(erl_types:erl_type(), state()) -> string().
+-spec format_type(type(), state()) -> string().
format_type(Type, #state{records = R}) ->
t_to_string(Type, R).
--spec format_field_diffs(erl_types:erl_type(), state()) -> string().
+-spec format_field_diffs(type(), state()) -> string().
format_field_diffs(RecConstruction, #state{records = R}) ->
erl_types:record_field_diffs_to_string(RecConstruction, R).
--spec format_sig_args(erl_types:erl_type(), state()) -> string().
+-spec format_sig_args(type(), state()) -> string().
-format_sig_args(Type, #state{records = R}) ->
- SigArgs = t_fun_args(Type),
+format_sig_args(Type, #state{opaques = Opaques} = State) ->
+ SigArgs = t_fun_args(Type, Opaques),
case SigArgs of
[] -> "()";
[SArg|SArgs] ->
- lists:flatten("(" ++ t_to_string(SArg, R)
- ++ ["," ++ t_to_string(T, R) || T <- SArgs] ++ ")")
+ lists:flatten("(" ++ format_type(SArg, State)
+ ++ ["," ++ format_type(T, State) || T <- SArgs] ++ ")")
end.
format_cerl(Tree) ->
diff --git a/lib/dialyzer/src/dialyzer_dep.erl b/lib/dialyzer/src/dialyzer_dep.erl
index febb65b766..572e60278d 100644
--- a/lib/dialyzer/src/dialyzer_dep.erl
+++ b/lib/dialyzer/src/dialyzer_dep.erl
@@ -2,7 +2,7 @@
%%-----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2010. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2014. 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
@@ -39,7 +39,7 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
-%% analyze(CoreTree) -> {Deps, Esc, Calls}.
+%% analyze(CoreTree) -> {Deps, Esc, Calls, Letrecs}.
%%
%% Deps = a dict mapping labels of functions to an ordset of functions
%% it calls.
@@ -53,18 +53,24 @@
%% which the operation can refer to. If 'external' is part of
%% the set the operation can be externally defined.
%%
+%% Letrecs = a dict mapping var labels to their recursive definition.
+%% top-level letrecs are not included as they are handled
+%% separately.
+%%
--spec analyze(cerl:c_module()) -> {dict(), ordset('external' | label()), dict()}.
+-spec analyze(cerl:c_module()) ->
+ {dict:dict(), ordsets:ordset('external' | label()), dict:dict(), dict:dict()}.
analyze(Tree) ->
%% io:format("Handling ~w\n", [cerl:atom_val(cerl:module_name(Tree))]),
{_, State} = traverse(Tree, map__new(), state__new(Tree), top),
Esc = state__esc(State),
- %% Add dependency from 'external' to all escaping function
+ %% Add dependency from 'external' to all escaping functions
State1 = state__add_deps(external, output(Esc), State),
Deps = state__deps(State1),
Calls = state__calls(State1),
- {map__finalize(Deps), set__to_ordsets(Esc), map__finalize(Calls)}.
+ Letrecs = state__letrecs(State1),
+ {map__finalize(Deps), set__to_ordsets(Esc), map__finalize(Calls), Letrecs}.
traverse(Tree, Out, State, CurrentFun) ->
%% io:format("Type: ~w\n", [cerl:type(Tree)]),
@@ -118,8 +124,10 @@ traverse(Tree, Out, State, CurrentFun) ->
TmpState = state__add_deps(Label, O1, State),
state__add_deps(CurrentFun, O2,TmpState)
end,
- {BodyFuns, State2} = traverse(Body, Out, State1,
- cerl_trees:get_label(Tree)),
+ Vars = cerl:fun_vars(Tree),
+ Out1 = bind_single(Vars, output(set__singleton(external)), Out),
+ {BodyFuns, State2} =
+ traverse(Body, Out1, State1, cerl_trees:get_label(Tree)),
{output(set__singleton(Label)), state__add_esc(BodyFuns, State2)};
'let' ->
Vars = cerl:let_vars(Tree),
@@ -131,9 +139,12 @@ traverse(Tree, Out, State, CurrentFun) ->
letrec ->
Defs = cerl:letrec_defs(Tree),
Body = cerl:letrec_body(Tree),
+ State1 = lists:foldl(fun({ Var, Fun }, Acc) ->
+ state__add_letrecs(cerl_trees:get_label(Var), cerl_trees:get_label(Fun), Acc)
+ end, State, Defs),
Out1 = bind_defs(Defs, Out),
- State1 = traverse_defs(Defs, Out1, State, CurrentFun),
- traverse(Body, Out1, State1, CurrentFun);
+ State2 = traverse_defs(Defs, Out1, State1, CurrentFun),
+ traverse(Body, Out1, State2, CurrentFun);
literal ->
{output(none), State};
module ->
@@ -173,6 +184,15 @@ traverse(Tree, Out, State, CurrentFun) ->
Args = cerl:tuple_es(Tree),
{List, State1} = traverse_list(Args, Out, State, CurrentFun),
{merge_outs(List), State1};
+ map ->
+ Args = cerl:map_es(Tree),
+ {List, State1} = traverse_list(Args, Out, State, CurrentFun),
+ {merge_outs(List), State1};
+ map_pair ->
+ Key = cerl:map_pair_key(Tree),
+ Val = cerl:map_pair_val(Tree),
+ {List, State1} = traverse_list([Key,Val], Out, State, CurrentFun),
+ {merge_outs(List), State1};
values ->
traverse_list(cerl:values_es(Tree), Out, State, CurrentFun);
var ->
@@ -291,7 +311,7 @@ primop(Tree, ArgFuns, State) ->
%% Set
%%
--record(set, {set :: set()}).
+-record(set, {set :: sets:set()}).
set__singleton(Val) ->
#set{set = sets:add_element(Val, sets:new())}.
@@ -460,18 +480,30 @@ all_vars(Tree, AccIn) ->
-type local_set() :: 'none' | #set{}.
--record(state, {deps :: dict(),
+-record(state, {deps :: dict:dict(),
esc :: local_set(),
- call :: dict(),
- arities :: dict()}).
+ call :: dict:dict(),
+ arities :: dict:dict(),
+ letrecs :: dict:dict()}).
state__new(Tree) ->
Exports = set__from_list([X || X <- cerl:module_exports(Tree)]),
- InitEsc = set__from_list([cerl_trees:get_label(Fun)
- || {Var, Fun} <- cerl:module_defs(Tree),
- set__is_element(Var, Exports)]),
+ %% get the labels of all exported functions
+ ExpLs = [cerl_trees:get_label(Fun) || {Var, Fun} <- cerl:module_defs(Tree),
+ set__is_element(Var, Exports)],
+ %% make sure to also initiate an analysis from all functions called
+ %% from on_load attributes; in Core these exist as a list of {F,A} pairs
+ OnLoadFAs = lists:flatten([cerl:atom_val(Args)
+ || {Attr, Args} <- cerl:module_attrs(Tree),
+ cerl:atom_val(Attr) =:= on_load]),
+ OnLoadLs = [cerl_trees:get_label(Fun)
+ || {Var, Fun} <- cerl:module_defs(Tree),
+ lists:member(cerl:var_name(Var), OnLoadFAs)],
+ %% init the escaping function labels to exported + called from on_load
+ InitEsc = set__from_list(OnLoadLs ++ ExpLs),
Arities = cerl_trees:fold(fun find_arities/2, dict:new(), Tree),
- #state{deps = map__new(), esc = InitEsc, call = map__new(), arities = Arities}.
+ #state{deps = map__new(), esc = InitEsc, call = map__new(),
+ arities = Arities, letrecs = map__new()}.
find_arities(Tree, AccMap) ->
case cerl:is_c_fun(Tree) of
@@ -490,9 +522,15 @@ state__add_deps(From, #output{type = single, content=To},
%% io:format("Adding deps from ~w to ~w\n", [From, set__to_ordsets(To)]),
State#state{deps = map__add(From, To, Map)}.
+state__add_letrecs(Var, Fun, #state{letrecs = Map} = State) ->
+ State#state{letrecs = map__store(Var, Fun, Map)}.
+
state__deps(#state{deps = Deps}) ->
Deps.
+state__letrecs(#state{letrecs = Letrecs}) ->
+ Letrecs.
+
state__add_esc(#output{content = none}, State) ->
State;
state__add_esc(#output{type = single, content = Set},
diff --git a/lib/dialyzer/src/dialyzer_gui.erl b/lib/dialyzer/src/dialyzer_gui.erl
deleted file mode 100644
index 97e5752577..0000000000
--- a/lib/dialyzer/src/dialyzer_gui.erl
+++ /dev/null
@@ -1,1381 +0,0 @@
-%% -*- erlang-indent-level: 2 -*-
-%%------------------------------------------------------------------------
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2006-2013. 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_gui.erl
-%%% Authors : Tobias Lindahl <[email protected]>
-%%% Kostis Sagonas <[email protected]>
-%%% Description : The graphical user interface for the Dialyzer tool.
-%%%
-%%% Created : 27 Apr 2004 by Tobias Lindahl <[email protected]>
-%%%-----------------------------------------------------------------------
-
--module(dialyzer_gui).
--compile([{nowarn_deprecated_function,{gs,button,2}},
- {nowarn_deprecated_function,{gs,config,2}},
- {nowarn_deprecated_function,{gs,destroy,1}},
- {nowarn_deprecated_function,{gs,editor,2}},
- {nowarn_deprecated_function,{gs,entry,2}},
- {nowarn_deprecated_function,{gs,frame,2}},
- {nowarn_deprecated_function,{gs,label,2}},
- {nowarn_deprecated_function,{gs,listbox,2}},
- {nowarn_deprecated_function,{gs,menu,2}},
- {nowarn_deprecated_function,{gs,menubar,2}},
- {nowarn_deprecated_function,{gs,menubutton,2}},
- {nowarn_deprecated_function,{gs,menuitem,2}},
- {nowarn_deprecated_function,{gs,radiobutton,2}},
- {nowarn_deprecated_function,{gs,read,2}},
- {nowarn_deprecated_function,{gs,start,0}},
- {nowarn_deprecated_function,{gs,stop,0}},
- {nowarn_deprecated_function,{gs,window,2}}]).
-
--export([start/1]).
-
--include("dialyzer.hrl").
-
-%%------------------------------------------------------------------------
-
--define(DIALYZER_ERROR_TITLE, "Dialyzer Error").
--define(DIALYZER_MESSAGE_TITLE, "Dialyzer Message").
-
-%%------------------------------------------------------------------------
-
--type gs_object() :: any(). %% XXX: should be imported from gs
-
--record(mode, {start_byte_code :: gs_object(),
- start_src_code :: gs_object()}).
-
--record(menu, {file_save_log :: gs_object(),
- file_save_warn :: gs_object(),
- file_quit :: gs_object(),
- help_about :: gs_object(),
- help_manual :: gs_object(),
- help_warnings :: gs_object(),
- opts_macros :: gs_object(),
- opts_includes :: gs_object(),
- plt_empty :: gs_object(),
- plt_search_doc :: gs_object(),
- plt_show_doc :: gs_object(),
- warnings :: gs_object()}).
-
--record(gui_state, {add_all :: gs_object(),
- add_file :: gs_object(),
- add_rec :: gs_object(),
- chosen_box :: gs_object(),
- analysis_pid :: pid(),
- del_file :: gs_object(),
- doc_plt :: dialyzer_plt:plt(),
- clear_chosen :: gs_object(),
- clear_log :: gs_object(),
- clear_warn :: gs_object(),
- init_plt :: dialyzer_plt:plt(),
- dir_entry :: gs_object(),
- file_box :: gs_object(),
- file_wd :: gs_object(),
- gs :: gs_object(),
- log :: gs_object(),
- menu :: #menu{},
- mode :: #mode{},
- options :: #options{},
- packer :: gs_object(),
- run :: gs_object(),
- stop :: gs_object(),
- top :: gs_object(),
- warnings_box :: gs_object(),
- backend_pid :: pid()}).
-
-%%------------------------------------------------------------------------
-
--spec start(#options{}) -> ?RET_NOTHING_SUSPICIOUS.
-
-start(#options{from = From, init_plts = InitPltFiles,
- legal_warnings = LegalWarnings} = DialyzerOptions) ->
- process_flag(trap_exit, true),
-
- GS = gs:start(),
- code:add_pathsa(["."]),
- WH = [{width, 1000}, {height, 550}],
- EmptySpace = {stretch, 1},
-
- {ok, Host} = inet:gethostname(),
- %% --------- Top Window --------------
- TopWin = gs:window(GS, [{title, "Dialyzer " ++ ?VSN ++ " @ " ++ Host},
- {configure, true},
- {default, listbox, {bg, white}},
- {default, editor, {bg, white}},
- {default, entry, {bg, white}},
- {default, button, {font, {helvetica, bold, 12}}},
- {default, label, {font, {helvetica, bold, 12}}}
- |WH]),
- Packer = gs:frame(TopWin, [{packer_x, [{stretch, 3},{fixed, 200},
- {stretch, 7}]},
- {packer_y, [{fixed, 25}, {fixed, 20},
- {stretch, 1, 50},
- {fixed, 25}, {fixed, 20},
- {stretch, 1, 50},
- {fixed, 25}]}]),
-
- %% --------- Chosen box --------------
- gs:label(Packer, [{label, {text, "Directories or modules to analyze"}},
- {height, 20}, {pack_xy, {1, 2}}]),
- ChosenBox = gs:listbox(Packer, [{pack_xy, {1, 3}}, {vscroll, right},
- {selectmode, multiple}]),
-
- %% --------- File box --------------
- gs:label(Packer, [{label, {text, "File"}}, {height, 20}, {pack_xy, {1,5}}]),
- FilePacker = gs:frame(Packer, [{packer_x, [{fixed, 30}, {stretch, 1, 100}]},
- {packer_y, [{fixed, 25}, {stretch, 1, 25}]},
- {pack_xy, {1, 6}}]),
- gs:label(FilePacker, [{label, {text, "Dir:"}}, {pack_xy, {1, 1}}]),
- DirEntry = gs:entry(FilePacker, [{height, 30}, {pack_xy, {2, 1}},
- {keypress, true}]),
- File = gs:listbox(FilePacker, [{pack_x, {1,2}}, {pack_y, 2},
- {selectmode, multiple}, {doubleclick, true},
- {vscroll, right}]),
-
- %% --------- Options --------------
- gs:label(Packer, [{label, {text, "Analysis Options"}},
- {height, 20}, {pack_xy, {2, 2}}]),
- ModePacker = gs:frame(Packer, [{packer_x, [{fixed, 75}, {fixed, 120}]},
- {packer_y, [{fixed, 20}, {fixed, 20},
- {fixed, 20},
- %% EmptySpace,
- {fixed, 20}, {fixed, 20},
- {fixed, 20}, EmptySpace]},
- {bw, 10}, {relief, flat},
- {default, {radiobutton, {align, w}}},
- {default, {label, {align, w}}},
- {pack_xy, {2, 3}}]),
-
- %% Bytecode vs. Source code
- gs:label(ModePacker, [{label, {text, "File Type:"}},
- {height, 20}, {pack_xy, {1,1}}]),
- {ByteSel, SrcSel} = case From of
- byte_code -> {[{select, true}], []};
- src_code -> {[], [{select, true}]}
- end,
- ModeByteCode = gs:radiobutton(ModePacker,
- ByteSel ++ [{group, start_from},
- {label, {text,"BeamFiles"}},
- {pack_xy, {2,1}}]),
- ModeSrcCode = gs:radiobutton(ModePacker,
- SrcSel ++ [{group, start_from},
- {label, {text,"SourceFiles"}},
- {pack_xy, {2,2}}]),
- Mode = #mode{start_byte_code = ModeByteCode,
- start_src_code = ModeSrcCode},
-
- %% --------- Log box --------------
- gs:label(Packer, [{label, {text, "Log"}}, {height, 20}, {pack_xy, {3,2}}]),
- Log = gs:editor(Packer, [{pack_x, 3}, {pack_y, 3}, {enable, false},
- {font, {courier, 12}}, {vscroll, right},
- {wrap, word}]),
-
- %% --------- Warnings box --------------
- gs:label(Packer, [{label, {text, "Warnings"}},{height, 20},{pack_xy, {3,5}}]),
- WarningsBox = gs:editor(Packer, [{pack_x, {2,3}}, {pack_y, 6},
- {enable, false},
- {font, {courier, 12}}, {vscroll, right},
- {wrap, word}]),
-
- %% --------- Buttons --------------
- ButtonPackerHighLeft =
- gs:frame(Packer, [{packer_x, [{fixed, 50}, {fixed, 65}, EmptySpace]},
- {pack_xy, {1,4}}]),
- ButtonPackerHighRight =
- gs:frame(Packer, [{packer_x, [{fixed, 70}, {fixed, 70}, EmptySpace]},
- {pack_xy, {3,4}}]),
- ButtonPackerLowLeft =
- gs:frame(Packer, [{packer_x, [{fixed, 50},
- {fixed, 60},
- {fixed, 110},
- EmptySpace]},
- {pack_xy, {1,7}}]),
- ButtonPackerLowRight =
- gs:frame(Packer, [{packer_x, [{fixed, 100},
- {fixed, 70},
- EmptySpace,
- {fixed, 70},
- {fixed, 70}]},
- {pack_x, {2,3}}, {pack_y, 7}]),
-
- WHButton = [{width, 60}, {height, 20}],
- AddFile = gs:button(ButtonPackerLowLeft, [{pack_xy, {1, 1}},
- {label, {text,"Add"}}|WHButton]),
- AddAll = gs:button(ButtonPackerLowLeft, [{pack_xy, {2, 1}},
- {label, {text,"Add All"}}|WHButton]),
- AddRec = gs:button(ButtonPackerLowLeft, [{pack_xy, {3, 1}},
- {label, {text,"Add Recursively"}}
- |WHButton]),
- DelFile = gs:button(ButtonPackerHighLeft, [{pack_xy, {1, 1}},
- {label, {text,"Delete"}}|WHButton]),
- ClearChosen = gs:button(ButtonPackerHighLeft, [{pack_xy, {2, 1}},
- {label, {text,"Delete All"}}
- |WHButton]),
- ClearLog = gs:button(ButtonPackerHighRight, [{pack_xy, {1, 1}},
- {label, {text,"Clear Log"}}
- |WHButton]),
- ClearWarn = gs:button(ButtonPackerLowRight, [{pack_xy, {1, 1}},
- {label, {text,"Clear Warnings"}}
- |WHButton]),
-
- Run = gs:button(ButtonPackerLowRight, [{pack_xy, {4, 1}},
- {label, {text,"Run"}}|WHButton]),
- Stop = gs:button(ButtonPackerLowRight, [{pack_xy, {5, 1}}, {enable, false},
- {label, {text,"Stop"}}|WHButton]),
-
- %% --------- Menu --------------
- MenuBar = gs:menubar(TopWin, []),
-
- %% File Menu
- MenuBarFile = gs:menubutton(MenuBar, [{label, {text, "File"}}]),
- MenuFile = gs:menu(MenuBarFile, []),
- MenuFileSaveWarn = gs:menuitem(MenuFile, [{label, {text, "Save Warnings"}}]),
- MenuFileSaveLog = gs:menuitem(MenuFile, [{label, {text, "Save Log"}}]),
- MenuFileQuit = gs:menuitem(MenuFile, [{label, {text, "Quit"}}]),
-
- %% Warnings Menu
- MenuBarWarn = gs:menubutton(MenuBar, [{label, {text, "Warnings"}}]),
- MenuWarn = gs:menu(MenuBarWarn, []),
- MenuWarnMatch = gs:menuitem(MenuWarn, [{label, {text, "Match failures"}},
- {itemtype, check}, {select, true}]),
- MenuWarnFailingCall = gs:menuitem(MenuWarn,
- [{label, {text, "Failing function calls"}},
- {itemtype, check}, {select, true}]),
- MenuWarnFunApp = gs:menuitem(MenuWarn, [{label,
- {text, "Bad fun applications"}},
- {itemtype, check}, {select, true}]),
- MenuWarnOpaque = gs:menuitem(MenuWarn, [{label,
- {text, "Opaqueness violations"}},
- {itemtype, check}, {select, true}]),
- MenuWarnLists = gs:menuitem(MenuWarn,
- [{label, {text, "Improper list constructions"}},
- {itemtype, check}, {select, true}]),
- MenuWarnNotCalled = gs:menuitem(MenuWarn,
- [{label, {text, "Unused functions"}},
- {itemtype, check}, {select, true}]),
- MenuWarnReturnOnlyExit = gs:menuitem(MenuWarn,
- [{label,
- {text, "Error handling functions"}},
- {itemtype, check}, {select, false}]),
- MenuWarnReturnNoReturn = gs:menuitem(MenuWarn,
- [{label,
- {text, "Functions of no return"}},
- {itemtype, check}, {select, true}]),
- MenuWarnCallNonExported = gs:menuitem(MenuWarn,
- [{label,
- {text, "Call to unexported function"}},
- {itemtype, check}, {select, true}]),
- MenuWarnRaceCondition = gs:menuitem(MenuWarn,
- [{label,
- {text,"Possible race conditions"}},
- {itemtype, check}, {select, false}]),
- MenuWarnContractTypes = gs:menuitem(MenuWarn,
- [{label, {text, "Wrong contracts"}},
- {itemtype, check}, {select, true}]),
- MenuWarnContractSyntax = gs:menuitem(MenuWarn,
- [{label,
- {text, "Wrong contract syntax"}},
- {itemtype, check}, {select, true}]),
-
- %% PLT Menu
- MenuBarPLT = gs:menubutton(MenuBar, [{label, {text,"PLT"}}]),
- MenuPLT = gs:menu(MenuBarPLT, []),
- MenuPLTEmpty = gs:menuitem(MenuPLT, [{label, {text, "Init with empty PLT"}},
- {itemtype, check}, {select, false}]),
- MenuPLTShow = gs:menuitem(MenuPLT, [{label, {text, "Show contents"}}]),
- MenuPLTSearch = gs:menuitem(MenuPLT, [{label, {text, "Search contents"}}]),
-
- %% Options Menu
- MenuBarOpts = gs:menubutton(MenuBar, [{label,{text,"Options"}}]),
- MenuOpts = gs:menu(MenuBarOpts, []),
- MenuOptsMacros = gs:menuitem(MenuOpts,
- [{label, {text, "Manage Macro Definitions"}}]),
- MenuOptsIncludes = gs:menuitem(MenuOpts,
- [{label, {text, "Manage Include Directories"}}]),
-
- %% Help
- MenuBarHelp = gs:menubutton(MenuBar, [{label, {text, "Help"}}, {side, right}]),
- MenuHelp = gs:menu(MenuBarHelp, []),
- MenuHelpManual = gs:menuitem(MenuHelp, [{label, {text, "Manual"}}]),
- MenuHelpWarnings = gs:menuitem(MenuHelp, [{label, {text, "Warning Options"}}]),
- MenuHelpAbout = gs:menuitem(MenuHelp, [{label, {text, "About"}}]),
-
- Warnings = [{?WARN_RETURN_NO_RETURN, MenuWarnReturnNoReturn},
- {?WARN_RETURN_ONLY_EXIT, MenuWarnReturnOnlyExit},
- {?WARN_NOT_CALLED, MenuWarnNotCalled},
- {?WARN_NON_PROPER_LIST, MenuWarnLists},
- {?WARN_FUN_APP, MenuWarnFunApp},
- {?WARN_MATCHING, MenuWarnMatch},
- {?WARN_OPAQUE, MenuWarnOpaque},
- {?WARN_FAILING_CALL, MenuWarnFailingCall},
- {?WARN_CALLGRAPH, MenuWarnCallNonExported},
- {?WARN_RACE_CONDITION, MenuWarnRaceCondition},
- %% For contracts.
- {?WARN_CONTRACT_TYPES, MenuWarnContractTypes},
- {?WARN_CONTRACT_SYNTAX, MenuWarnContractSyntax}
- ],
-
- init_warnings(Warnings, LegalWarnings),
-
- Menu = #menu{file_quit = MenuFileQuit,
- plt_empty = MenuPLTEmpty,
- help_manual = MenuHelpManual,
- help_about = MenuHelpAbout,
- help_warnings = MenuHelpWarnings,
- opts_macros = MenuOptsMacros,
- opts_includes = MenuOptsIncludes,
- plt_search_doc = MenuPLTSearch,
- plt_show_doc = MenuPLTShow,
- file_save_log = MenuFileSaveLog,
- file_save_warn = MenuFileSaveWarn,
- warnings = Warnings},
-
- %% --------- Init --------------
- gs:config(TopWin, [{map, true}]),
- gs:config(Packer, WH),
- {ok, CWD} = file:get_cwd(),
-
- InitPlt =
- case InitPltFiles of
- [] -> dialyzer_plt:new();
- _ ->
- Plts = [dialyzer_plt:from_file(F) || F <- InitPltFiles],
- dialyzer_plt:merge_plts_or_report_conflicts(InitPltFiles, Plts)
- end,
-
- State = #gui_state{add_all = AddAll,
- add_file = AddFile,
- add_rec = AddRec,
- chosen_box = ChosenBox,
- clear_chosen = ClearChosen,
- clear_log = ClearLog,
- clear_warn = ClearWarn,
- del_file = DelFile,
- doc_plt = dialyzer_plt:new(),
- dir_entry = DirEntry,
- file_box = File,
- file_wd = CWD,
- gs = GS,
- init_plt = InitPlt,
- log = Log,
- menu = Menu,
- mode = Mode,
- options = DialyzerOptions,
- packer = Packer,
- run = Run,
- stop = Stop,
- top = TopWin,
- warnings_box = WarningsBox},
- NewState = change_dir_or_add_file(State, "."),
- gui_loop(NewState).
-
-%% ----------------------------------------------------------------
-%%
-%% Main GUI Loop
-%%
-
--spec gui_loop(#gui_state{}) -> ?RET_NOTHING_SUSPICIOUS.
-
-gui_loop(#gui_state{add_all = AddAll, add_file = AddFile, add_rec = AddRec,
- backend_pid = BackendPid, chosen_box = ChosenBox,
- clear_chosen = ClearChosen, clear_log = ClearLog,
- clear_warn = ClearWarn, del_file = DelFile,
- dir_entry = DirEntry, file_box = File, log = Log,
- menu = Menu, packer = Packer, run = Run, stop = Stop,
- top = TopWin, warnings_box = Warn} = State) ->
- %% --- Menu ---
- Quit = Menu#menu.file_quit,
- Manual = Menu#menu.help_manual,
- Warnings = Menu#menu.help_warnings,
- About = Menu#menu.help_about,
- SaveLog = Menu#menu.file_save_log,
- SaveWarn = Menu#menu.file_save_warn,
- SearchPlt = Menu#menu.plt_search_doc,
- ShowPlt = Menu#menu.plt_show_doc,
- Macros = Menu#menu.opts_macros,
- Includes = Menu#menu.opts_includes,
-
- receive
- {gs, TopWin, configure, _Data, [W, H|_]} ->
- gs:config(Packer, [{width, W}, {height, H}]),
- gui_loop(State);
- {gs, TopWin, destroy, _Data, _Args} ->
- ?RET_NOTHING_SUSPICIOUS;
- {gs, File, doubleclick, _, [_Id, Text|_]} ->
- NewState = change_dir_or_add_file(State, Text),
- gui_loop(NewState);
- {gs, DirEntry, keypress, _, ['Return'|_]} ->
- gs:config(TopWin, [{setfocus, true}]),
- NewState = change_dir_absolute(State, gs:read(DirEntry, text)),
- gui_loop(NewState);
- {gs, DirEntry, keypress, _, _} ->
- gui_loop(State);
- %% ----- Buttons -----
- {gs, AddFile, click, _, _} ->
- handle_add_files(State),
- gui_loop(State);
- {gs, AddAll, click, _, _} ->
- handle_add_all_click(State),
- gui_loop(State);
- {gs, AddRec, click, _, _} ->
- handle_add_rec_click(State),
- gui_loop(State);
- {gs, DelFile, click, _, _} ->
- handle_file_delete(State),
- gui_loop(State);
- {gs, ClearChosen, click, _, _} ->
- gs:config(ChosenBox, [clear]),
- gui_loop(State);
- {gs, ClearLog, click, _, _} ->
- Log = State#gui_state.log,
- gs:config(Log, [{enable, true}]),
- gs:config(Log, [clear]),
- gs:config(Log, [{enable, false}]),
- gui_loop(State);
- {gs, ClearWarn, click, _, _} ->
- Warn = State#gui_state.warnings_box,
- gs:config(Warn, [{enable, true}]),
- gs:config(Warn, [clear]),
- gs:config(Warn, [{enable, false}]),
- gui_loop(State);
- {gs, Run, click, _, _} ->
- NewState = start_analysis(State),
- gui_loop(NewState);
- {gs, Stop, click, _, _} ->
- config_gui_stop(State),
- BackendPid ! {self(), stop},
- update_editor(Log, "\n***** Analysis stopped ****\n"),
- gui_loop(State);
- %% ----- Menu -----
- {gs, Quit, click, _, _} ->
- case maybe_quit(State) of
- true -> ?RET_NOTHING_SUSPICIOUS;
- false -> gui_loop(State)
- end;
- {gs, Manual, click, _, _} ->
- spawn_link(fun() -> manual(State) end),
- gui_loop(State);
- {gs, Warnings, click, _, _} ->
- spawn_link(fun() -> warnings(State) end),
- gui_loop(State);
- {gs, About, click, _, _} ->
- spawn_link(fun() -> about(State) end),
- gui_loop(State);
- {gs, SaveLog, click, _, _} ->
- save_log(State),
- gui_loop(State);
- {gs, SaveWarn, click, _, _} ->
- save_warn(State),
- gui_loop(State);
- {gs, SearchPlt, click, _, _} ->
- spawn_link(fun() -> search_doc_plt(State) end),
- gui_loop(State);
- {gs, ShowPlt, click, _, _} ->
- spawn_link(fun() -> show_doc_plt(State) end),
- gui_loop(State);
- {gs, Macros, click, _, _} ->
- Self = self(),
- spawn_link(fun() -> macro_dialog(State, Self) end),
- gui_loop(State);
- {gs, Includes, click, _, _} ->
- Self = self(),
- spawn_link(fun() -> include_dialog(State, Self) end),
- gui_loop(State);
- {new_options, NewOptions} ->
- NewState = State#gui_state{options = NewOptions},
- gui_loop(NewState);
- %% ----- Analysis -----
- {BackendPid, ext_calls, ExtCalls} ->
- Msg = io_lib:format("The following functions are called "
- "but type information about them is not available.\n"
- "The analysis might get more precise by including "
- "the modules containing these functions:\n\n\t~p\n",
- [ExtCalls]),
- free_editor(State, "Analysis done", Msg),
- gui_loop(State);
- {BackendPid, ext_types, ExtTypes} ->
- Map = fun({M,F,A}) -> io_lib:format("~p:~p/~p",[M,F,A]) end,
- ExtTypeString = string:join(lists:map(Map, ExtTypes), "\n"),
- Msg = io_lib:format("The following remote types are being used "
- "but information about them is not available.\n"
- "The analysis might get more precise by including "
- "the modules containing these types and making sure "
- "that they are exported:\n~s\n", [ExtTypeString]),
- free_editor(State, "Analysis done", Msg),
- gui_loop(State);
- {BackendPid, log, LogMsg} ->
- update_editor(Log, LogMsg),
- gui_loop(State);
- {BackendPid, warnings, Warns} ->
- SortedWarns = lists:keysort(2, Warns), %% Sort on file/line
- WarnList = [dialyzer:format_warning(W) || W <- SortedWarns],
- update_editor(Warn, lists:flatten(WarnList)),
- gui_loop(State);
- {BackendPid, done, _NewPlt, NewDocPlt} ->
- message(State, "Analysis done"),
- config_gui_stop(State),
- gui_loop(State#gui_state{doc_plt = NewDocPlt});
- {'EXIT', BackendPid, {error, Reason}} ->
- free_editor(State, ?DIALYZER_ERROR_TITLE, Reason),
- config_gui_stop(State),
- gui_loop(State);
- {'EXIT', BackendPid, Reason} when Reason =/= 'normal' ->
- free_editor(State, ?DIALYZER_ERROR_TITLE, io_lib:format("~p", [Reason])),
- config_gui_stop(State),
- gui_loop(State);
- _Other ->
- %% io:format("Received ~p\n", [Other]),
- gui_loop(State)
- end.
-
-%% ----------------------------------------------------------------
-%%
-%% Main window actions
-%%
-
-%% ---- Adding and deleting files ----
-
-handle_add_all_click(#gui_state{chosen_box = ChosenBox, file_box = File,
- file_wd = FWD, mode = Mode}) ->
- case gs:read(File, items) of
- [] ->
- ok;
- Add0 ->
- gs:config(File, [{selection, clear}]),
- Add1 = ordsets:subtract(Add0, [".."]),
- Add = ordsets:from_list([filename:join(FWD, X) || X <- Add1]),
- case gs:read(Mode#mode.start_byte_code, select) of
- true ->
- add_files(filter_mods(Add, ".beam"), ChosenBox, byte_code);
- false ->
- add_files(filter_mods(Add, ".erl"), ChosenBox, src_code)
- end
- end.
-
-all_subdirs(Dirs) ->
- all_subdirs(Dirs, []).
-
-all_subdirs([Dir|T], Acc) ->
- {ok, Files} = file:list_dir(Dir),
- SubDirs = lists:zf(fun(F) ->
- SubDir = filename:join(Dir, F),
- case filelib:is_dir(SubDir) of
- true -> {true, SubDir};
- false -> false
- end
- end, Files),
- NewAcc = ordsets:union(ordsets:from_list(SubDirs), Acc),
- all_subdirs(T ++ SubDirs, NewAcc);
-all_subdirs([], Acc) ->
- Acc.
-
-handle_add_rec_click(#gui_state{chosen_box = ChosenBox, file_box = File,
- file_wd = FWD, mode = Mode}) ->
- case gs:read(File, selection) of
- [] ->
- ok;
- List ->
- gs:config(File, [{selection, clear}]),
- Dirs1 = [gs:read(File, {get, X}) || X <- List],
- Dirs2 = ordsets:from_list([filename:join(FWD, X) || X <- Dirs1]),
- Dirs3 = ordsets:filter(fun(X) -> filelib:is_dir(X) end, Dirs2),
- TargetDirs = ordsets:union(Dirs3, all_subdirs(Dirs3)),
- {Code, Ext} = case gs:read(Mode#mode.start_byte_code, select) of
- true -> {byte_code, ".beam"};
- false -> {src_code, ".erl"}
- end,
- add_files(filter_mods(TargetDirs, Ext), ChosenBox, Code)
- end.
-
-handle_add_files(#gui_state{chosen_box = ChosenBox, file_box = File,
- file_wd = FWD, mode = Mode}) ->
- case gs:read(File, selection) of
- [] ->
- ok;
- List ->
- gs:config(File, [{selection, clear}]),
- Add0 = [gs:read(File, {get, X}) || X <- List],
- Add = ordsets:from_list([filename:join(FWD, X) || X <- Add0]),
- case gs:read(Mode#mode.start_byte_code, select) of
- true ->
- add_files(filter_mods(Add, ".beam"), ChosenBox, byte_code);
- false ->
- add_files(filter_mods(Add, ".erl"), ChosenBox, src_code)
- end
- end.
-
-filter_mods(Mods, Extension) ->
- Fun = fun(X) ->
- filename:extension(X) =:= Extension
- orelse
- (filelib:is_dir(X) andalso
- contains_files(X, Extension))
- end,
- ordsets:filter(Fun, Mods).
-
-contains_files(Dir, Extension) ->
- {ok, Files} = file:list_dir(Dir),
- lists:any(fun(X) -> filename:extension(X) =:= Extension end, Files).
-
-add_files(Add, ChosenBox, Type) ->
- Set = gs:read(ChosenBox, items),
- Set1 =
- case Type of
- byte_code -> filter_mods(Set, ".beam");
- src_code -> filter_mods(Set, ".erl")
- end,
- Files = ordsets:union(Add, Set1),
- gs:config(ChosenBox, [{items, Files}]),
- ok.
-
-handle_file_delete(#gui_state{chosen_box = ChosenBox}) ->
- List = gs:read(ChosenBox, selection),
- lists:foreach(fun(X) -> gs:config(ChosenBox, [{del, X}]) end,
- lists:reverse(lists:sort(List))).
-
-%% ---- Other ----
-
-change_dir_or_add_file(#gui_state{file_wd = FWD, mode = Mode, dir_entry = Dir,
- chosen_box = CBox, file_box = File} = State,
- Text) ->
- NewWDorFile =
- case Text of
- ".." -> filename:join(butlast(filename:split(FWD)));
- "." -> FWD;
- _ -> filename:join(FWD, Text)
- end,
- case filelib:is_dir(NewWDorFile) of
- true ->
- gs:config(Dir, [{text, NewWDorFile}]),
- {ok, List} = file:list_dir(NewWDorFile),
- gs:config(File, [{items, [".."|lists:sort(List)]}]),
- State#gui_state{file_wd = NewWDorFile};
- false ->
- case gs:read(Mode#mode.start_byte_code, select) of
- true ->
- case filter_mods([NewWDorFile], ".beam") of
- [] -> ok;
- RealFiles -> add_files(RealFiles, CBox, byte_code)
- end;
- false ->
- case filter_mods([NewWDorFile], ".erl") of
- [] -> ok;
- RealFiles -> add_files(RealFiles, CBox, src_code)
- end
- end,
- State
- end.
-
-butlast([H1, H2 | T]) ->
- [H1 | butlast([H2|T])];
-butlast([_]) ->
- [];
-butlast([]) ->
- ["/"].
-
-change_dir_absolute(#gui_state{file_wd = FWD, dir_entry = Dir,
- file_box = File} = State,
- Text) ->
- case filelib:is_dir(Text) of
- true ->
- WD = filename:join(FWD, Text),
- gs:config(Dir, [{text, WD}]),
- {ok, List} = file:list_dir(WD),
- gs:config(File, [{items, [".."|lists:sort(List)]}]),
- State#gui_state{file_wd = WD};
- false ->
- State
- end.
-
-init_warnings([{Tag, GSItem}|Left], LegalWarnings) ->
- Select = ordsets:is_element(Tag, LegalWarnings),
- gs:config(GSItem, [{select, Select}]),
- init_warnings(Left, LegalWarnings);
-init_warnings([], _LegalWarnings) ->
- ok.
-
-config_gui_start(State) ->
- Enabled = [{enable, true}],
- Disabled = [{enable, false}],
- gs:config(State#gui_state.stop, Enabled),
- gs:config(State#gui_state.run, Disabled),
- gs:config(State#gui_state.del_file, Disabled),
- gs:config(State#gui_state.clear_chosen, Disabled),
- gs:config(State#gui_state.add_file, Disabled),
- gs:config(State#gui_state.add_all, Disabled),
- gs:config(State#gui_state.add_rec, Disabled),
- gs:config(State#gui_state.clear_warn, Disabled),
- gs:config(State#gui_state.clear_log, Disabled),
- Menu = State#gui_state.menu,
- gs:config(Menu#menu.file_save_warn, Disabled),
- gs:config(Menu#menu.file_save_log, Disabled),
- gs:config(Menu#menu.opts_macros, Disabled),
- gs:config(Menu#menu.opts_includes, Disabled),
- gs:config(Menu#menu.plt_empty, Disabled),
- gs:config(Menu#menu.plt_search_doc, Disabled),
- gs:config(Menu#menu.plt_show_doc, Disabled),
- Mode = State#gui_state.mode,
- gs:config(Mode#mode.start_byte_code, Disabled),
- gs:config(Mode#mode.start_src_code, Disabled).
-
-config_gui_stop(State) ->
- Enabled = [{enable, true}],
- Disabled = [{enable, false}],
- gs:config(State#gui_state.stop, Disabled),
- gs:config(State#gui_state.run, Enabled),
- gs:config(State#gui_state.del_file, Enabled),
- gs:config(State#gui_state.clear_chosen, Enabled),
- gs:config(State#gui_state.add_file, Enabled),
- gs:config(State#gui_state.add_all, Enabled),
- gs:config(State#gui_state.add_rec, Enabled),
- gs:config(State#gui_state.clear_warn, Enabled),
- gs:config(State#gui_state.clear_log, Enabled),
- Menu = State#gui_state.menu,
- gs:config(Menu#menu.file_save_warn, Enabled),
- gs:config(Menu#menu.file_save_log, Enabled),
- gs:config(Menu#menu.opts_macros, Enabled),
- gs:config(Menu#menu.opts_includes, Enabled),
- gs:config(Menu#menu.plt_empty, Enabled),
- gs:config(Menu#menu.plt_search_doc, Enabled),
- gs:config(Menu#menu.plt_show_doc, Enabled),
- Mode = State#gui_state.mode,
- gs:config(Mode#mode.start_byte_code, Enabled),
- gs:config(Mode#mode.start_src_code, Enabled).
-
-%% ----------------------------------------------------------------
-%%
-%% Messages
-%%
-
-message(State, Message) ->
- output_sms(State, ?DIALYZER_MESSAGE_TITLE, Message).
-
-error_sms(State, Message) ->
- output_sms(State, ?DIALYZER_ERROR_TITLE, Message).
-
-%%
-%% This function is to be used *only* for small messages because lines
-%% are not wrapped and the created window has a limited area for text.
-%% For bigger messages, the function free_editor/3 is to be used.
-%%
-output_sms(#gui_state{gs = GS, top = TopWin}, Title, Message) ->
- %% Lines = string:words(Message, $\n),
- %% io:format("The message has ~w lines\n", [Lines]),
- WH = [{width, 400}, {height, 100}],
- MessageWin = gs:window(GS, [{title, Title},
- {default, button, {font, {helvetica, bold, 12}}}
- |WH]),
- MessagePacker = gs:frame(MessageWin, [{packer_y, [{fixed, 75}, {fixed, 25}]},
- {packer_x, [{fixed, 175},{fixed, 50},
- {fixed, 175}]}]),
- gs:label(MessagePacker, [{pack_x, {1, 3}}, {pack_y, 1},
- {label, {text, Message}}]),
- OK = gs:button(MessagePacker, [{label, {text, "OK"}}, {pack_xy, {2, 2}}]),
- gs:config(MessageWin, [{map, true}]),
- gs:config(MessagePacker, WH),
- message_loop(OK, MessageWin, TopWin).
-
-message_loop(Ok, Win, TopWin) ->
- receive
- {gs, Ok, click, _, _} ->
- gs:destroy(Win);
- {gs, Win, destroy, _, _} ->
- ok;
- {gs, TopWin, destroy, _, _} ->
- exit(normal);
- {gs, _, _, _, _} ->
- message_loop(Ok, Win, TopWin)
- end.
-
-dialog(#gui_state{gs = GS, top = TopWin}, Message, OkLabel, CancelLabel) ->
- WH = [{width, 400}, {height, 100}],
- WHButton = [{width, 70}, {height, 20}],
- DialogWin = gs:window(GS, [{title, "Dialyzer Message"},
- {default, button, {font, {helvetica, bold, 12}}}
- |WH]),
- DialogPacker = gs:frame(DialogWin, [{packer_y, [{fixed, 75}, {fixed, 25}]},
- {packer_x, [{fixed, 150}, {fixed, 50},
- {fixed, 50}, {fixed, 150}]}]),
- gs:label(DialogPacker, [{pack_x, {1,4}}, {pack_y, 1},
- {label, {text, Message}}]),
- Ok = gs:button(DialogPacker, [{label, {text, OkLabel}},
- {pack_xy, {2,2}}|WHButton]),
- Cancel = gs:button(DialogPacker, [{label, {text, CancelLabel}},
- {pack_xy, {3,2}}|WHButton]),
- gs:config(DialogWin, [{map, true}]),
- gs:config(DialogPacker, WH),
- dialog_loop(Ok, Cancel, DialogWin, TopWin).
-
-dialog_loop(Ok, Cancel, Win, TopWin) ->
- receive
- {gs, Ok, click, _, _} ->
- gs:destroy(Win),
- true;
- {gs, Cancel, click, _, _} ->
- gs:destroy(Win),
- false;
- {gs, Win, destroy, _, _} ->
- false;
- {gs, TopWin, destroy, _, _} ->
- exit(normal);
- {gs, _, _, _, _} ->
- dialog_loop(Ok, Cancel, Win, TopWin)
- end.
-
-maybe_quit(#gui_state{top = TopWin} = State) ->
- case dialog(State, "Do you really want to quit?", "Yes", "No") of
- true ->
- flush(),
- gs:destroy(TopWin),
- gs:stop(),
- true;
- false ->
- false
- end.
-
-
-%% ----------------------------------------------------------------
-%%
-%% Menu actions
-%%
-
-%% ---- Help Menu ----
-
-manual(State) ->
- help_menu_common(State, "Dialyzer Manual", 500, "manual.txt", white).
-
-warnings(State) ->
- help_menu_common(State, "Dialyzer Warnings", 500, "warnings.txt", white).
-
-about(State) ->
- help_menu_common(State, "About Dialyzer", 160, "about.txt", yellow).
-
-help_menu_common(#gui_state{gs = GS, top = TopWin} = State,
- Title, Height, TxtFileName, BackGroundColor) ->
- WH = [{width, 600}, {height, Height}],
- Win = gs:window(GS, [{title, Title}, {configure, true},
- {default, editor, {bg, BackGroundColor}} | WH]),
- EmptySpace = {stretch, 1},
- Frame = gs:frame(Win, [{packer_x, [EmptySpace, {fixed, 60}, EmptySpace]},
- {packer_y, [EmptySpace, {fixed, 30}]} | WH]),
- Editor = gs:editor(Frame, [{pack_x, {1, 3}}, {pack_y, 1},
- {font, {courier, 12}}, {vscroll, right},
- {wrap, word}]),
- Button = gs:button(Frame, [{label, {text, "Ok"}}, {pack_xy, {2, 2}}]),
- gs:config(Win, [{map, true}]),
- gs:config(Frame, WH),
- AboutFile = filename:join([code:lib_dir(dialyzer), "doc", TxtFileName]),
- case gs:config(Editor, {load, AboutFile}) of
- {error, Reason} ->
- gs:destroy(Win),
- error_sms(State,
- io_lib:format("Could not find doc/~s file!\n\n ~p",
- [TxtFileName, Reason]));
- ok ->
- gs:config(Editor, [{enable, false}]),
- show_info_loop(TopWin, Win, Frame, Button)
- end.
-
-%% ---- File Menu ----
-
-save_log(#gui_state{file_wd = CWD, log = Log} = State) ->
- {Win, Entry, OkButton, CancelButton} = file_box(State, "Save Log", CWD),
- save_loop(State, OkButton, CancelButton, Entry, Win, Log).
-
-save_warn(#gui_state{file_wd = CWD, warnings_box = WBox} = State) ->
- {Win, Entry, OkButton, CancelButton} = file_box(State, "Save Warnings", CWD),
- save_loop(State, OkButton, CancelButton, Entry, Win, WBox).
-
-file_box(#gui_state{gs = GS}, Title, Default) ->
- WH = [{width, 400}, {height, 75}],
- Win = gs:window(GS, [{title, Title}|WH]),
- Fix25 = {fixed, 27}, Fix75 = {fixed, 75},
- WinPacker = gs:frame(Win, [{packer_y, [Fix25, Fix25, Fix25]},
- {packer_x, [Fix75, Fix75, Fix75, {fixed, 175}]}]),
- gs:label(WinPacker, [{pack_xy, {1,2}}, {label, {text, "Enter file:"}}]),
- Entry = gs:entry(WinPacker, [{pack_x, {2,4}}, {pack_y, 2}, {keypress, true}]),
- OkButton = gs:button(WinPacker, [{label, {text, "Ok"}}, {pack_xy, {2,3}}]),
- CancelButton = gs:button(WinPacker, [{label, {text, "Cancel"}},
- {pack_xy, {3,3}}]),
- gs:config(Entry, [{text, Default}]),
- gs:config(Win, [{map, true}]),
- gs:config(WinPacker, WH),
- {Win, Entry, OkButton, CancelButton}.
-
-save_loop(#gui_state{top = TopWin} = State,
- OkButton, CancelButton, Entry, Save, Editor) ->
- receive
- {gs, OkButton, click, _, _} ->
- File = gs:read(Entry, text),
- case gs:config(Editor, [{save, File}]) of
- {error, _} ->
- error_sms(State, "Could not write to file:\n" ++ File),
- save_loop(State, OkButton, CancelButton, Entry, Save, Editor);
- _ ->
- gs:destroy(Save)
- end;
- {gs, Entry, keypress, _, ['Return'|_]} ->
- File = gs:read(Entry, text),
- case gs:config(Editor, [{save, File}]) of
- {error, _} ->
- error_sms(State, "Could not write to file:\n" ++ File),
- save_loop(State, OkButton, CancelButton, Entry, Save, Editor);
- _ ->
- gs:destroy(Save)
- end;
- {gs, Entry, keypress, _, _} ->
- save_loop(State, OkButton, CancelButton, Entry, Save, Editor);
- {gs, CancelButton, click, _, _} ->
- gs:destroy(Save);
- {gs, TopWin, destroy, _, _} ->
- exit(normal);
- {gs, Save, destroy, _, _} ->
- ok;
- {gs, _, _, _, _} ->
- save_loop(State, OkButton, CancelButton, Entry, Save, Editor)
- end.
-
-%% ---- Plt Menu ----
-
-search_doc_plt(#gui_state{gs = GS, top = TopWin} = State) ->
- WH = [{width, 400}, {height, 100}],
- WHB = [{width, 120}, {height, 30}],
- Title = io_lib:format("Search the PLT", []),
- Win = gs:window(GS, [{title, Title}, {configure, true},
- {default, editor, {bg, white}} | WH]),
- EmptySpace = {stretch, 1},
- Frame = gs:frame(Win, [{packer_x, [EmptySpace, EmptySpace, EmptySpace]},
- {packer_y, [{fixed, 30}, {fixed, 30},
- EmptySpace, {fixed, 30}]} | WH]),
- gs:label(Frame, [{pack_xy, {1,1}}, {label, {text, "Module"}}]),
- ModEntry = gs:entry(Frame, [{pack_xy, {1,2}}]),
- gs:label(Frame, [{pack_xy, {2,1}}, {label, {text, "Function"}}]),
- FunEntry = gs:entry(Frame, [{pack_xy, {2,2}}]),
- gs:label(Frame, [{pack_xy, {3,1}}, {label, {text, "Arity"}}]),
- ArityEntry = gs:entry(Frame, [{pack_xy, {3,2}}]),
- ButtonPacker = gs:frame(Frame, [{pack_xy, {2,4}},
- {packer_x, [{fixed, 60}, {fixed, 60}]},
- {packer_y, {fixed, 30}}]),
- SearchButton = gs:button(ButtonPacker, [{label, {text, "Search"}},
- {pack_xy, {1,1}}]),
- CancelButton = gs:button(ButtonPacker, [{label, {text, "Cancel"}},
- {pack_xy, {2,1}}]),
- gs:config(Win, [{map, true}]),
- gs:config(Frame, WH),
- gs:config(ButtonPacker, WHB),
- search_doc_plt_loop(State, CancelButton, SearchButton, ModEntry,
- FunEntry, ArityEntry, Win, TopWin).
-
-search_doc_plt_loop(State, CancelButton, SearchButton, ModEntry,
- FunEntry, ArityEntry, Win, TopWin) ->
- receive
- {gs, CancelButton, click, _, _} ->
- gs:destroy(Win),
- ok;
- {gs, TopWin, destroy, _, _} ->
- exit(normal);
- {gs, SearchButton, click, _, _} ->
- M = format_search(gs:read(ModEntry, text)),
- F = format_search(gs:read(FunEntry, text)),
- A = format_search(gs:read(ArityEntry, text)),
- case dialyzer_plt:get_specs(State#gui_state.doc_plt, M, F, A) of
- "" ->
- error_sms(State, "No such function"),
- search_doc_plt_loop(State, CancelButton, SearchButton, ModEntry,
- FunEntry, ArityEntry, Win, TopWin);
- NonEmptyString ->
- gs:destroy(Win),
- free_editor(State, "Content of PLT", NonEmptyString)
- end
- end.
-
-format_search([]) ->
- '_';
-format_search(String) ->
- try list_to_integer(String)
- catch error:_ -> list_to_atom(String)
- end.
-
-show_doc_plt(#gui_state{doc_plt = DocPLT} = State) ->
- case dialyzer_plt:get_specs(DocPLT) of
- "" -> error_sms(State, "No analysis has been made yet!\n");
- NonEmptyString -> free_editor(State, "Content of PLT", NonEmptyString)
- end.
-
-free_editor(#gui_state{gs = GS, top = TopWin}, Title, Contents0) ->
- Contents = lists:flatten(Contents0),
- Tokens = string:tokens(Contents, "\n"),
- NofLines = length(Tokens),
- LongestLine = lists:max([length(X) || X <- Tokens]),
- Height0 = NofLines * 25 + 80,
- Height = if Height0 > 500 -> 500; true -> Height0 end,
- Width0 = LongestLine * 7 + 60,
- Width = if Width0 > 800 -> 800; true -> Width0 end,
- WH = [{width, Width}, {height, Height}],
- Win = gs:window(GS, [{title, Title}, {configure, true},
- {default, editor, {bg, white}} | WH]),
- EmptySpace = {stretch, 1},
- Frame = gs:frame(Win, [{packer_x, [EmptySpace, {fixed, 60}, EmptySpace]},
- {packer_y, [EmptySpace, {fixed, 30}]}
- | WH]),
- Editor = gs:editor(Frame, [{pack_x, {1,3}}, {pack_y, 1},
- {font, {courier, 12}}, {vscroll, right},
- {wrap, word}, {enable, true}]),
- Button = gs:button(Frame, [{label, {text, "Ok"}}, {pack_xy, {2,2}}]),
- gs:config(Editor, [{insert, {insert, Contents}}]),
- gs:config(Editor, [{enable, false}]),
- gs:config(Win, [{map, true}]),
- gs:config(Frame, WH),
- show_info_loop(TopWin, Win, Frame, Button).
-
-%% ---- Common ----
-
-show_info_loop(TopWin, Win, Frame, Button) ->
- receive
- {gs, Button, click, _, _} ->
- gs:destroy(Win);
- {gs, TopWin, destroy, _, _} ->
- exit(normal);
- {gs, Win, destroy, _, _} ->
- ok;
- {gs, Win, configure, _Data, [W, H|_]} ->
- gs:config(Frame, [{width, W}, {height, H}]),
- show_info_loop(TopWin, Win, Frame, Button)
- end.
-
-include_dialog(#gui_state{gs = GS, options = Options}, Parent) ->
- WH = [{width, 300}, {height, 400}],
- Title = io_lib:format("Include Directories", []),
- Win = gs:window(GS, [{title, Title}, {configure, true},
- {default, entry, {bg, white}}| WH]),
- EmptySpace = {stretch, 1},
- Frame = gs:frame(Win, [{packer_x, [EmptySpace]},
- {packer_y, [{fixed, 30}, {fixed, 30}, {fixed, 30},
- EmptySpace, {fixed, 30}, {fixed, 30}]}
- | WH]),
- gs:label(Frame, [{pack_xy, {1,1}}, {label, {text, "Directory"}}]),
- DirEntry = gs:entry(Frame, [{pack_xy, {1,2}}]),
- ButtonPacker1 = gs:frame(Frame, [{pack_xy, {1,3}},
- {packer_x, [{fixed, 70}, {fixed, 70},
- EmptySpace]},
- {packer_y, {fixed, 30}}]),
- AddButton = gs:button(ButtonPacker1, [{label, {text, "Add"}},
- {pack_xy, {1,1}}]),
- Dirs = [io_lib:format("~s", [X]) || X <- Options#options.include_dirs],
- DirBox = gs:listbox(Frame, [{pack_xy, {1,4}}, {vscroll, right},
- {bg, white}, {configure, true},
- {selectmode, multiple}, {items, Dirs}]),
- ButtonPacker2 = gs:frame(Frame, [{pack_xy, {1,5}},
- {packer_x, [{fixed, 60}, {fixed, 70},
- EmptySpace]},
- {packer_y, {fixed, 30}}]),
- DeleteButton = gs:button(ButtonPacker2, [{label, {text, "Delete"}},
- {pack_xy, {1,1}}]),
- DeleteAllButton = gs:button(ButtonPacker2, [{label, {text, "Delete All"}},
- {pack_xy, {2,1}}]),
- ButtonPacker3 = gs:frame(Frame, [{pack_xy, {1,6}},
- {packer_x, [EmptySpace,
- {fixed, 60}, {fixed, 60}]},
- {packer_y, {fixed, 30}}]),
- OkButton = gs:button(ButtonPacker3, [{label, {text, "Ok"}},
- {pack_xy, {2,1}}]),
- CancelButton = gs:button(ButtonPacker3, [{label, {text, "Cancel"}},
- {pack_xy, {3,1}}]),
- gs:config(Win, [{map, true}]),
- gs:config(Frame, WH),
- include_loop(Parent, Options, Frame, AddButton, DeleteAllButton, DeleteButton,
- DirBox, DirEntry, OkButton, CancelButton, Win).
-
-include_loop(Parent, Options, Frame, AddButton, DeleteAllButton, DeleteButton,
- DirBox, DirEntry, OkButton, CancelButton, Win) ->
- receive
- {gs, CancelButton, click, _, _} ->
- gs:destroy(Win),
- ok;
- {gs, OkButton, click, _, _} ->
- gs:destroy(Win),
- Parent ! {new_options, Options},
- ok;
- {gs, Win, configure, _Data, [W, H|_]} ->
- gs:config(Frame, [{width, W}, {height, H}]),
- include_loop(Parent, Options, Frame, AddButton, DeleteAllButton,
- DeleteButton, DirBox, DirEntry, OkButton, CancelButton, Win);
- {gs, AddButton, click, _, _} ->
- Dirs = Options#options.include_dirs,
- NewDirs =
- case gs:read(DirEntry, text) of
- [] -> Dirs;
- Add -> [Add|Dirs]
- end,
- NewOptions = Options#options{include_dirs = NewDirs},
- gs:config(DirBox, [{items, NewDirs}]),
- include_loop(Parent, NewOptions, Frame, AddButton, DeleteAllButton,
- DeleteButton, DirBox, DirEntry, OkButton, CancelButton, Win);
- {gs, DeleteAllButton, click, _, _} ->
- gs:config(DirBox, [clear]),
- NewOptions = Options#options{include_dirs = []},
- include_loop(Parent, NewOptions, Frame, AddButton, DeleteAllButton,
- DeleteButton, DirBox, DirEntry, OkButton, CancelButton, Win);
- {gs, DeleteButton, click, _, _} ->
- NewOptions =
- case gs:read(DirBox, selection) of
- [] ->
- Options;
- List ->
- lists:foreach(fun(X) -> gs:config(DirBox, [{del, X}]) end,
- lists:sort(List)),
- NewDirs = gs:read(DirBox, items),
- Options#options{include_dirs = NewDirs}
- end,
- include_loop(Parent, NewOptions, Frame, AddButton, DeleteAllButton,
- DeleteButton, DirBox, DirEntry, OkButton, CancelButton, Win);
- {gs, Win, destroy, _, _} ->
- ok
- end.
-
-macro_dialog(#gui_state{gs = GS, options = Options}, Parent) ->
- WH = [{width, 300}, {height, 400}],
- Title = io_lib:format("Macro Definitions", []),
- Win = gs:window(GS, [{title, Title}, {configure, true},
- {default, entry, {bg, white}}| WH]),
- EmptySpace = {stretch, 1},
- Frame = gs:frame(Win, [{packer_x, [EmptySpace, EmptySpace]},
- {packer_y, [{fixed, 30}, {fixed, 30}, {fixed, 30},
- EmptySpace, {fixed, 30}, {fixed, 30}]}
- | WH]),
- gs:label(Frame, [{pack_xy, {1,1}}, {label, {text, "Macro"}}]),
- MacroEntry = gs:entry(Frame, [{pack_xy, {1,2}}]),
- gs:label(Frame, [{pack_xy, {2,1}}, {label, {text, "Term"}}]),
- TermEntry = gs:entry(Frame, [{pack_xy, {2,2}}]),
- ButtonPacker1 = gs:frame(Frame, [{pack_x, {1,2}}, {pack_y, 3},
- {packer_x, [{fixed, 70},{fixed, 70},
- EmptySpace]},
- {packer_y, {fixed, 30}}]),
- AddButton = gs:button(ButtonPacker1, [{label, {text, "Add"}},
- {pack_xy, {1,1}}]),
- Macros = [io_lib:format("~p = ~p",[X,Y]) || {X,Y} <- Options#options.defines],
- MacroBox = gs:listbox(Frame, [{pack_x, {1,2}}, {pack_y, 4}, {vscroll, right},
- {bg, white}, {configure, true},
- {selectmode, multiple},
- {items, Macros}]),
- ButtonPacker2 = gs:frame(Frame, [{pack_x, {1,2}}, {pack_y, 5},
- {packer_x, [{fixed, 60}, {fixed, 70},
- EmptySpace]},
- {packer_y, {fixed, 30}}]),
- DeleteButton = gs:button(ButtonPacker2, [{label, {text, "Delete"}},
- {pack_xy, {1,1}}]),
- DeleteAllButton = gs:button(ButtonPacker2, [{label, {text, "Delete All"}},
- {pack_xy, {2,1}}]),
- ButtonPacker3 = gs:frame(Frame, [{pack_x, {1,2}}, {pack_y, 6},
- {packer_x, [EmptySpace,
- {fixed, 60}, {fixed, 60}]},
- {packer_y, {fixed, 30}}]),
- OkButton = gs:button(ButtonPacker3, [{label, {text, "Ok"}},
- {pack_xy, {2,1}}]),
- CancelButton = gs:button(ButtonPacker3, [{label, {text, "Cancel"}},
- {pack_xy, {3,1}}]),
- gs:config(Win, [{map, true}]),
- gs:config(Frame, WH),
- macro_loop(Parent, Options, Frame, AddButton, DeleteAllButton, DeleteButton,
- MacroBox, MacroEntry, TermEntry, OkButton, CancelButton, Win).
-
-macro_loop(Parent, Options, Frame, AddButton, DeleteAllButton, DeleteButton,
- MacroBox, MacroEntry, TermEntry, OkButton, CancelButton, Win) ->
- receive
- {gs, CancelButton, click, _, _} ->
- gs:destroy(Win),
- ok;
- {gs, OkButton, click, _, _} ->
- gs:destroy(Win),
- Parent ! {new_options, Options},
- ok;
- {gs, Win, configure, _Data, [W, H|_]} ->
- gs:config(Frame, [{width, W}, {height, H}]),
- macro_loop(Parent, Options, Frame, AddButton, DeleteAllButton,
- DeleteButton, MacroBox, MacroEntry, TermEntry, OkButton,
- CancelButton, Win);
- {gs, AddButton, click, _, _} ->
- Defines = Options#options.defines,
- NewDefines =
- case gs:read(MacroEntry, text) of
- "" -> Defines;
- Macro ->
- Empty = [{text, ""}],
- case gs:read(TermEntry, text) of
- "" ->
- gs:config(MacroEntry, Empty),
- orddict:store(list_to_atom(Macro), true, Defines);
- String ->
- case parse(String) of
- {ok, Term} ->
- gs:config(MacroEntry, Empty),
- gs:config(TermEntry, Empty),
- orddict:store(list_to_atom(Macro), Term, Defines);
- {error, _Reason} ->
- Defines
- end
- end
- end,
- NewOptions = Options#options{defines = NewDefines},
- NewEntries = [io_lib:format("~p = ~p", [X, Y]) || {X, Y} <- NewDefines],
- gs:config(MacroBox, [{items, NewEntries}]),
- macro_loop(Parent, NewOptions, Frame, AddButton, DeleteAllButton,
- DeleteButton, MacroBox, MacroEntry, TermEntry, OkButton,
- CancelButton, Win);
- {gs, DeleteAllButton, click, _, _} ->
- gs:config(MacroBox, [clear]),
- NewOptions = Options#options{defines = []},
- macro_loop(Parent, NewOptions, Frame, AddButton, DeleteAllButton,
- DeleteButton, MacroBox, MacroEntry, TermEntry, OkButton,
- CancelButton, Win);
- {gs, DeleteButton, click, _, _} ->
- NewOptions =
- case gs:read(MacroBox, selection) of
- [] ->
- Options;
- List ->
- gs:config(MacroBox, [{selection, clear}]),
- Fun =
- fun(X) ->
- Val = gs:read(MacroBox, {get, X}),
- [MacroName|_] = re:split(Val, " ", [{return, list}]),
- list_to_atom(MacroName)
- end,
- Delete = [Fun(X) || X <- List],
- lists:foreach(fun(X) -> gs:config(MacroBox, [{del, X}]) end,
- lists:reverse(lists:sort(List))),
- Defines = Options#options.defines,
- NewDefines = lists:foldl(fun(X, Acc) ->
- orddict:erase(X, Acc)
- end,
- Defines, Delete),
- Options#options{defines = NewDefines}
- end,
- macro_loop(Parent, NewOptions, Frame, AddButton, DeleteAllButton,
- DeleteButton, MacroBox, MacroEntry, TermEntry, OkButton,
- CancelButton, Win);
- {gs, Win, destroy, _, _} ->
- ok
- end.
-
-parse(String) ->
- case erl_scan:string(String ++ ".", 1) of
- {ok, Ts, _} ->
- case erl_parse:parse_exprs(Ts) of
- {ok, [Expr]} ->
- try erl_parse:normalise(Expr)
- catch error:Reason -> {error, Reason}
- end;
- {error, E} ->
- parse_error(E)
- end;
- {error, E, _} ->
- parse_error(E)
- end.
-
-parse_error(E) ->
- S = io_lib:fwrite("Error parsing expression: ~P.", [E,15]),
- {error, S}.
-
-%% ----------------------------------------------------------------
-%%
-%% Run the analysis
-%%
-
-start_analysis(State) ->
- Analysis = build_analysis_record(State),
- case get_anal_files(State, Analysis#analysis.start_from) of
- error ->
- Msg = "You must choose one or more files or dirs\n"
- "before starting the analysis!",
- error_sms(State, Msg),
- config_gui_stop(State),
- State;
- {ok, Files} ->
- Msg = "\n========== Starting Analysis ==========\n\n",
- update_editor(State#gui_state.log, Msg),
- NewAnalysis = Analysis#analysis{files = Files},
- run_analysis(State, NewAnalysis)
- end.
-
-build_analysis_record(#gui_state{mode = Mode, menu = Menu, options = Options,
- init_plt = InitPlt0}) ->
- StartFrom =
- case gs:read(Mode#mode.start_byte_code, select) of
- true -> byte_code;
- false -> src_code
- end,
- InitPlt =
- case gs:read(Menu#menu.plt_empty, select) of
- true -> dialyzer_plt:new();
- false -> InitPlt0
- end,
- #analysis{defines = Options#options.defines,
- include_dirs = Options#options.include_dirs,
- plt = InitPlt,
- start_from = StartFrom,
- solvers = Options#options.solvers}.
-
-get_anal_files(#gui_state{chosen_box = ChosenBox}, StartFrom) ->
- Files = gs:read(ChosenBox, items),
- FilteredMods =
- case StartFrom of
- src_code -> filter_mods(Files, ".erl");
- byte_code -> filter_mods(Files, ".beam")
- end,
- FilteredDirs = [X || X <- Files, filelib:is_dir(X)],
- case ordsets:union(FilteredMods, FilteredDirs) of
- [] -> error;
- Set -> {ok, Set}
- end.
-
-run_analysis(State, Analysis) ->
- config_gui_start(State),
- Self = self(),
- NewAnalysis = Analysis#analysis{doc_plt = dialyzer_plt:new()},
- LegalWarnings = find_legal_warnings(State),
- Fun =
- fun() ->
- dialyzer_analysis_callgraph:start(Self, LegalWarnings, NewAnalysis)
- end,
- BackendPid = spawn_link(Fun),
- State#gui_state{backend_pid = BackendPid}.
-
-find_legal_warnings(#gui_state{menu = #menu{warnings = Warnings}}) ->
- ordsets:from_list([Tag || {Tag, GSItem} <- Warnings,
- gs:read(GSItem, select) =:= true]).
-
-flush() ->
- receive
- _ -> flush()
- after
- 0 -> ok
- end.
-
-update_editor(Editor, Msg) ->
- gs:config(Editor, [{enable, true}]),
- NofRows = gs:read(Editor, size),
- gs:config(Editor, [{insertpos, 'end'}]),
- gs:config(Editor, [{insert, {insert, Msg}}]),
- NewNofRows = gs:read(Editor, size),
- ScrollPos = gs:read(Editor, vscrollpos),
- gs:config(Editor, [{vscrollpos, ScrollPos + NewNofRows - NofRows}]),
- gs:config(Editor, [{enable, false}]).
diff --git a/lib/dialyzer/src/dialyzer_gui_wx.erl b/lib/dialyzer/src/dialyzer_gui_wx.erl
index 08f31c1e13..7070fa240d 100644
--- a/lib/dialyzer/src/dialyzer_gui_wx.erl
+++ b/lib/dialyzer/src/dialyzer_gui_wx.erl
@@ -61,7 +61,7 @@
init_plt :: dialyzer_plt:plt(),
dir_entry :: wx:wx_object(),
file_box :: wx:wx_object(),
- files_to_analyze :: ordset(string()),
+ files_to_analyze :: ordsets:ordset(string()),
gui :: wx:wx_object(),
log :: wx:wx_object(),
menu :: menu(),
diff --git a/lib/dialyzer/src/dialyzer_options.erl b/lib/dialyzer/src/dialyzer_options.erl
index 06672e595f..a92b8b1958 100644
--- a/lib/dialyzer/src/dialyzer_options.erl
+++ b/lib/dialyzer/src/dialyzer_options.erl
@@ -2,7 +2,7 @@
%%-----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2014. 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
@@ -51,7 +51,8 @@ build(Opts) ->
?WARN_CONTRACT_TYPES,
?WARN_CONTRACT_SYNTAX,
?WARN_BEHAVIOUR,
- ?WARN_UNDEFINED_CALLBACK],
+ ?WARN_UNDEFINED_CALLBACK,
+ ?WARN_UNKNOWN],
DefaultWarns1 = ordsets:from_list(DefaultWarns),
InitPlt = dialyzer_plt:get_default_plt(),
DefaultOpts = #options{},
@@ -310,6 +311,8 @@ build_warnings([Opt|Opts], Warnings) ->
ordsets:add_element(?WARN_CONTRACT_SUBTYPE, Warnings);
underspecs ->
ordsets:add_element(?WARN_CONTRACT_SUPERTYPE, Warnings);
+ no_unknown ->
+ ordsets:del_element(?WARN_UNKNOWN, Warnings);
OtherAtom ->
bad_option("Unknown dialyzer warning option", OtherAtom)
end,
diff --git a/lib/dialyzer/src/dialyzer_plt.erl b/lib/dialyzer/src/dialyzer_plt.erl
index 5f64099210..63798f44b1 100644
--- a/lib/dialyzer/src/dialyzer_plt.erl
+++ b/lib/dialyzer/src/dialyzer_plt.erl
@@ -2,7 +2,7 @@
%%----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2014. 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
@@ -67,7 +67,7 @@
%%----------------------------------------------------------------------
--type mod_deps() :: dict().
+-type mod_deps() :: dialyzer_callgraph:mod_deps().
-type deep_string() :: string() | [deep_string()].
@@ -80,11 +80,11 @@
%%----------------------------------------------------------------------
--record(plt, {info = table_new() :: dict(),
- types = table_new() :: dict(),
- contracts = table_new() :: dict(),
- callbacks = table_new() :: dict(),
- exported_types = sets:new() :: set()}).
+-record(plt, {info = table_new() :: dict:dict(),
+ types = table_new() :: dict:dict(),
+ contracts = table_new() :: dict:dict(),
+ callbacks = table_new() :: dict:dict(),
+ exported_types = sets:new() :: sets:set()}).
-record(mini_plt, {info :: ets:tid(),
contracts :: ets:tid(),
@@ -96,15 +96,15 @@
-include("dialyzer.hrl").
-type file_md5() :: {file:filename(), binary()}.
--type plt_info() :: {[file_md5()], dict()}.
+-type plt_info() :: {[file_md5()], dict:dict()}.
-record(file_plt, {version = "" :: string(),
file_md5_list = [] :: [file_md5()],
- info = dict:new() :: dict(),
- contracts = dict:new() :: dict(),
- callbacks = dict:new() :: dict(),
- types = dict:new() :: dict(),
- exported_types = sets:new() :: set(),
+ info = dict:new() :: dict:dict(),
+ contracts = dict:new() :: dict:dict(),
+ callbacks = dict:new() :: dict:dict(),
+ types = dict:new() :: dict:dict(),
+ exported_types = sets:new() :: sets:set(),
mod_deps :: mod_deps(),
implementation_md5 = [] :: [file_md5()]}).
@@ -184,22 +184,22 @@ lookup(Plt, Label) when is_integer(Label) ->
lookup_1(#mini_plt{info = Info}, MFAorLabel) ->
ets_table_lookup(Info, MFAorLabel).
--spec insert_types(plt(), dict()) -> plt().
+-spec insert_types(plt(), dict:dict()) -> plt().
insert_types(PLT, Rec) ->
PLT#plt{types = Rec}.
--spec insert_exported_types(plt(), set()) -> plt().
+-spec insert_exported_types(plt(), sets:set()) -> plt().
insert_exported_types(PLT, Set) ->
PLT#plt{exported_types = Set}.
--spec get_types(plt()) -> dict().
+-spec get_types(plt()) -> dict:dict().
get_types(#plt{types = Types}) ->
Types.
--spec get_exported_types(plt()) -> set().
+-spec get_exported_types(plt()) -> sets:set().
get_exported_types(#plt{exported_types = ExpTypes}) ->
ExpTypes.
@@ -211,7 +211,7 @@ get_exported_types(#plt{exported_types = ExpTypes}) ->
lookup_module(#plt{info = Info}, M) when is_atom(M) ->
table_lookup_module(Info, M).
--spec all_modules(plt()) -> set().
+-spec all_modules(plt()) -> sets:set().
all_modules(#plt{info = Info, contracts = Cs}) ->
sets:union(table_all_modules(Info), table_all_modules(Cs)).
diff --git a/lib/dialyzer/src/dialyzer_races.erl b/lib/dialyzer/src/dialyzer_races.erl
index 2aa8343bce..b1f849b16f 100644
--- a/lib/dialyzer/src/dialyzer_races.erl
+++ b/lib/dialyzer/src/dialyzer_races.erl
@@ -2,7 +2,7 @@
%%-----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2014. 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
@@ -85,6 +85,12 @@
-type race_tag() :: 'whereis_register' | 'whereis_unregister'
| 'ets_lookup_insert' | 'mnesia_dirty_read_write'.
+%% The following type is similar to the dial_warning() type but has a
+%% tag which is local to this module and is not propagated to outside
+-type dial_race_warning() :: {race_warn_tag(), file_line(), {atom(), [term()]}}.
+-type race_warn_tag() :: ?WARN_WHEREIS_REGISTER | ?WARN_WHEREIS_UNREGISTER
+ | ?WARN_ETS_LOOKUP_INSERT | ?WARN_MNESIA_DIRTY_READ_WRITE.
+
-record(beg_clause, {arg :: var_to_map1(),
pats :: var_to_map1(),
guard :: cerl:cerl()}).
@@ -98,14 +104,14 @@
def_vars :: [core_vars()],
arg_types :: [erl_types:erl_type()],
call_vars :: [core_vars()],
- var_map :: dict()}).
+ var_map :: dict:dict()}).
-record(dep_call, {call_name :: dep_calls(),
args :: args(),
arg_types :: [erl_types:erl_type()],
vars :: [core_vars()],
- state :: _, %% XXX: recursive
+ state :: dialyzer_dataflow:state(),
file_line :: file_line(),
- var_map :: dict()}).
+ var_map :: dict:dict()}).
-record(fun_call, {caller :: dialyzer_callgraph:mfa_or_funlbl(),
callee :: dialyzer_callgraph:mfa_or_funlbl(),
arg_types :: [erl_types:erl_type()],
@@ -114,7 +120,7 @@
arg :: var_to_map1()}).
-record(warn_call, {call_name :: warn_calls(),
args :: args(),
- var_map :: dict()}).
+ var_map :: dict:dict()}).
-type case_tags() :: 'beg_case' | #beg_clause{} | #end_clause{} | #end_case{}.
-type code() :: [#dep_call{} | #fun_call{} | #warn_call{} |
@@ -141,7 +147,7 @@
race_tags = [] :: [#race_fun{}],
%% true for fun types and warning mode
race_analysis = false :: boolean(),
- race_warnings = [] :: [dial_warning()]}).
+ race_warnings = [] :: [dial_race_warning()]}).
%%% ===========================================================================
%%%
@@ -1565,7 +1571,7 @@ any_args(StrList) ->
end
end.
--spec bind_dict_vars(label(), label(), dict()) -> dict().
+-spec bind_dict_vars(label(), label(), dict:dict()) -> dict:dict().
bind_dict_vars(Key, Label, RaceVarMap) ->
case Key =:= Label of
@@ -1751,7 +1757,7 @@ compare_vars(Var1, Var2, RaceVarMap) when is_integer(Var1), is_integer(Var2) ->
compare_vars(_Var1, _Var2, _RaceVarMap) ->
false.
--spec compare_var_list(label_type(), [label_type()], dict()) -> boolean().
+-spec compare_var_list(label_type(), [label_type()], dict:dict()) -> boolean().
compare_var_list(Var, VarList, RaceVarMap) ->
lists:any(fun (V) -> compare_vars(Var, V, RaceVarMap) end, VarList).
@@ -1763,7 +1769,7 @@ ets_list_args(MaybeList) ->
catch _:_ -> [?no_label]
end;
false -> [ets_tuple_args(MaybeList)]
- end.
+ end.
ets_list_argtypes(ListStr) ->
ListStr1 = string:strip(ListStr, left, $[),
@@ -1956,7 +1962,8 @@ mnesia_tuple_argtypes(TupleStr) ->
[TupleStr2|_T] = string:tokens(TupleStr1, " ,"),
lists:flatten(string:tokens(TupleStr2, " |")).
--spec race_var_map(var_to_map1(), var_to_map2(), dict(), op()) -> dict().
+-spec race_var_map(var_to_map1(), var_to_map2(), dict:dict(), op()) ->
+ dict:dict().
race_var_map(Vars1, Vars2, RaceVarMap, Op) ->
case Vars1 =:= ?no_arg orelse Vars1 =:= ?bypassed
diff --git a/lib/dialyzer/src/dialyzer_succ_typings.erl b/lib/dialyzer/src/dialyzer_succ_typings.erl
index 84379642bf..ef9b00e203 100644
--- a/lib/dialyzer/src/dialyzer_succ_typings.erl
+++ b/lib/dialyzer/src/dialyzer_succ_typings.erl
@@ -2,7 +2,7 @@
%%-----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2014. 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
@@ -72,7 +72,7 @@
-record(st, {callgraph :: dialyzer_callgraph:callgraph(),
codeserver :: dialyzer_codeserver:codeserver(),
- no_warn_unused :: set(),
+ no_warn_unused :: sets:set(mfa()),
parent = none :: parent(),
timing_server :: dialyzer_timing:timing_server(),
solvers :: [solver()],
@@ -137,7 +137,7 @@ get_refined_success_typings(SCCs, #st{callgraph = Callgraph,
-type doc_plt() :: 'undefined' | dialyzer_plt:plt().
-spec get_warnings(dialyzer_callgraph:callgraph(), dialyzer_plt:plt(),
- doc_plt(), dialyzer_codeserver:codeserver(), set(),
+ doc_plt(), dialyzer_codeserver:codeserver(), sets:set(mfa()),
dialyzer_timing:timing_server(), [solver()], pid()) ->
{[dial_warning()], dialyzer_plt:plt(), doc_plt()}.
@@ -149,8 +149,10 @@ get_warnings(Callgraph, Plt, DocPlt, Codeserver,
NewState = InitState#st{no_warn_unused = NoWarnUnused},
Mods = dialyzer_callgraph:modules(NewState#st.callgraph),
MiniPlt = NewState#st.plt,
+ FindOpaques = lookup_and_find_opaques_fun(Codeserver),
CWarns =
- dialyzer_contracts:get_invalid_contract_warnings(Mods, Codeserver, MiniPlt),
+ dialyzer_contracts:get_invalid_contract_warnings(Mods, Codeserver,
+ MiniPlt, FindOpaques),
MiniDocPlt = dialyzer_plt:get_mini_plt(DocPlt),
ModWarns =
?timing(TimingServer, "warning",
@@ -261,7 +263,16 @@ refine_one_module(M, {CodeServer, Callgraph, Plt, _Solvers}) ->
FunTypes = get_fun_types_from_plt(AllFuns, Callgraph, Plt),
NewFunTypes =
dialyzer_dataflow:get_fun_types(ModCode, Plt, Callgraph, Records),
- case reached_fixpoint(FunTypes, NewFunTypes) of
+ Contracts1 = dialyzer_codeserver:lookup_mod_contracts(M, CodeServer),
+ Contracts = orddict:from_list(dict:to_list(Contracts1)),
+ FindOpaques = find_opaques_fun(Records),
+ DecoratedFunTypes =
+ decorate_succ_typings(Contracts, Callgraph, NewFunTypes, FindOpaques),
+ %% ?debug("NewFunTypes ~p\n ~n", [dict:to_list(NewFunTypes)]),
+ %% ?debug("refine DecoratedFunTypes ~p\n ~n", [dict:to_list(DecoratedFunTypes)]),
+ debug_pp_functions("Refine", NewFunTypes, DecoratedFunTypes, Callgraph),
+
+ case reached_fixpoint(FunTypes, DecoratedFunTypes) of
true -> [];
{false, NotFixpoint} ->
?debug("Not fixpoint\n", []),
@@ -357,9 +368,16 @@ find_succ_types_for_scc(SCC, {Codeserver, Callgraph, Plt, Solvers}) ->
AllFunSet = sets:from_list([X || {X, _} <- AllFuns]),
FilteredFunTypes =
dict:filter(fun(X, _) -> sets:is_element(X, AllFunSet) end, FunTypes),
+ FindOpaques = lookup_and_find_opaques_fun(Codeserver),
+ DecoratedFunTypes =
+ decorate_succ_typings(Contracts3, Callgraph, FilteredFunTypes, FindOpaques),
%% Check contracts
PltContracts =
- dialyzer_contracts:check_contracts(Contracts3, Callgraph, FilteredFunTypes),
+ dialyzer_contracts:check_contracts(Contracts3, Callgraph,
+ DecoratedFunTypes, FindOpaques),
+ %% ?debug("FilteredFunTypes ~p\n ~n", [dict:to_list(FilteredFunTypes)]),
+ %% ?debug("SCC DecoratedFunTypes ~p\n ~n", [dict:to_list(DecoratedFunTypes)]),
+ debug_pp_functions("SCC", FilteredFunTypes, DecoratedFunTypes, Callgraph),
ContractFixpoint =
lists:all(fun({MFA, _C}) ->
%% Check the non-deleted PLT
@@ -368,16 +386,47 @@ find_succ_types_for_scc(SCC, {Codeserver, Callgraph, Plt, Solvers}) ->
{value, _} -> true
end
end, PltContracts),
- Plt = insert_into_plt(FilteredFunTypes, Callgraph, Plt),
+ Plt = insert_into_plt(DecoratedFunTypes, Callgraph, Plt),
Plt = dialyzer_plt:insert_contract_list(Plt, PltContracts),
case (ContractFixpoint andalso
- reached_fixpoint_strict(PropTypes, FilteredFunTypes)) of
+ reached_fixpoint_strict(PropTypes, DecoratedFunTypes)) of
true -> [];
false ->
?debug("Not fixpoint for: ~w\n", [AllFuns]),
[Fun || {Fun, _Arity} <- AllFuns]
end.
+decorate_succ_typings(Contracts, Callgraph, FunTypes, FindOpaques) ->
+ F = fun(Label, Type) ->
+ case dialyzer_callgraph:lookup_name(Label, Callgraph) of
+ {ok, MFA} ->
+ case orddict:find(MFA, Contracts) of
+ {ok, {_FileLine, Contract}} ->
+ Args = dialyzer_contracts:get_contract_args(Contract),
+ Ret = dialyzer_contracts:get_contract_return(Contract),
+ C = erl_types:t_fun(Args, Ret),
+ {M, _, _} = MFA,
+ Opaques = FindOpaques(M),
+ erl_types:t_decorate_with_opaque(Type, C, Opaques);
+ error -> Type
+ end;
+ error -> Type
+ end
+ end,
+ dict:map(F, FunTypes).
+
+lookup_and_find_opaques_fun(Codeserver) ->
+ fun(Module) ->
+ Records = dialyzer_codeserver:lookup_mod_records(Module, Codeserver),
+ (find_opaques_fun(Records))(Module)
+ end.
+
+find_opaques_fun(Records) ->
+ fun(Module) ->
+ erl_types:module_builtin_opaques(Module) ++
+ erl_types:t_opaque_from_records(Records)
+ end.
+
get_fun_types_from_plt(FunList, Callgraph, Plt) ->
get_fun_types_from_plt(FunList, Callgraph, Plt, dict:new()).
@@ -443,9 +492,30 @@ debug_pp_succ_typings(SuccTypes) ->
|| {MFA, {contract, RetFun, ArgT}} <- SuccTypes],
?debug("\n", []),
ok.
+
+debug_pp_functions(Header, FunTypes, DecoratedFunTypes, Callgraph) ->
+ ?debug("FunTypes (~s)\n", [Header]),
+ FTypes = lists:keysort(1, dict:to_list(FunTypes)),
+ DTypes = lists:keysort(1, dict:to_list(DecoratedFunTypes)),
+ Fun = fun({{Label, Type},{Label, DecoratedType}}) ->
+ Name = lookup_name(Label, Callgraph),
+ ?debug("~w (~w): ~s\n",
+ [Name, Label, erl_types:t_to_string(Type)]),
+ case erl_types:t_is_equal(Type, DecoratedType) of
+ true -> ok;
+ false ->
+ ?debug(" With opaque types: ~s\n",
+ [erl_types:t_to_string(DecoratedType)])
+ end
+ end,
+ lists:foreach(Fun, lists:zip(FTypes, DTypes)),
+ ?debug("\n", []).
-else.
debug_pp_succ_typings(_) ->
ok.
+
+debug_pp_functions(_, _, _, _) ->
+ ok.
-endif.
lookup_name(F, CG) ->
diff --git a/lib/dialyzer/src/dialyzer_typesig.erl b/lib/dialyzer/src/dialyzer_typesig.erl
index a418a11e65..31ceaf5ac5 100644
--- a/lib/dialyzer/src/dialyzer_typesig.erl
+++ b/lib/dialyzer/src/dialyzer_typesig.erl
@@ -2,7 +2,7 @@
%%-----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2014. 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
@@ -31,29 +31,33 @@
-export([analyze_scc/6]).
-export([get_safe_underapprox/2]).
+%%-import(helper, %% 'helper' could be any module doing sanity checks...
+-import(erl_types,
+ [t_has_var/1, t_inf/2, t_is_equal/2, t_is_subtype/2,
+ t_subtract/2, t_subtract_list/2, t_sup/1, t_sup/2,t_unify/2]).
+
-import(erl_types,
[t_any/0, t_atom/0, t_atom_vals/1,
t_binary/0, t_bitstr/0, t_bitstr/2, t_bitstr_concat/1, t_boolean/0,
t_collect_vars/1, t_cons/2, t_cons_hd/1, t_cons_tl/1,
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_has_var/1,
- t_inf/2, t_inf/3, t_integer/0,
- t_is_any/1, t_is_atom/1, t_is_atom/2, t_is_cons/1, t_is_equal/2,
+ t_integer/0,
+ t_is_any/1, t_is_atom/1, t_is_any_atom/2, t_is_cons/1,
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_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,
- t_opaque_match_record/2, t_opaque_matching_structure/2,
- t_opaque_from_records/1,
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_subst/2,
t_timeout/0, t_tuple/0, t_tuple/1,
- t_unify/3, t_var/1, t_var_name/1,
- t_none/0, t_unit/0]).
+ t_var/1, t_var_name/1,
+ t_none/0, t_unit/0,
+ t_map/1
+ ]).
-include("dialyzer.hrl").
@@ -79,7 +83,7 @@
list :: [constr()],
deps :: [dep()],
masks :: [{dep(),[non_neg_integer()]}] |
- {'d',dict()},
+ {'d',dict:dict(dep(), [non_neg_integer()])},
id :: {'list', dep()}}).
-type constraint_list() :: #constraint_list{}.
@@ -90,26 +94,30 @@
-type constr() :: constraint() | constraint_list() | constraint_ref().
--type typesig_scc() :: [{mfa(), {cerl:c_var(), cerl:c_fun()}, dict()}].
+-type types() :: erl_types:type_table().
+
+-type typesig_scc() :: [{mfa(), {cerl:c_var(), cerl:c_fun()}, types()}].
-type typesig_funmap() :: [{type_var(), type_var()}]. %% Orddict
--type dict_or_ets() :: {'d', dict()} | {'e', ets:tid()}.
+-type prop_types() :: dict:dict(label(), types()).
+
+-type dict_or_ets() :: {'d', prop_types()} | {'e', ets:tid()}.
-record(state, {callgraph :: dialyzer_callgraph:callgraph(),
cs = [] :: [constr()],
cmap = {'d', dict:new()} :: dict_or_ets(),
fun_map = [] :: typesig_funmap(),
- fun_arities = dict:new() :: dict(),
+ fun_arities = dict:new() :: dict:dict(type_var(), arity()),
in_match = false :: boolean(),
in_guard = false :: boolean(),
module :: module(),
- name_map = dict:new() :: dict(),
+ name_map = dict:new() :: dict:dict(mfa(),
+ cerl:c_fun()),
next_label = 0 :: label(),
- self_rec :: erl_types:erl_type(),
+ self_rec :: 'false' | erl_types:erl_type(),
plt :: dialyzer_plt:plt(),
prop_types = {'d', dict:new()} :: dict_or_ets(),
- records = dict:new() :: dict(),
- opaques = [] :: [erl_types:erl_type()],
+ records = dict:new() :: types(),
scc = [] :: [type_var()],
mfas :: [tuple()],
solvers = [] :: [solver()]
@@ -164,7 +172,7 @@
-spec analyze_scc(typesig_scc(), label(),
dialyzer_callgraph:callgraph(),
- dialyzer_plt:plt(), dict(), [solver()]) -> dict().
+ dialyzer_plt:plt(), prop_types(), [solver()]) -> prop_types().
analyze_scc(SCC, NextLabel, CallGraph, Plt, PropTypes, Solvers0) ->
Solvers = solvers(Solvers0),
@@ -192,11 +200,10 @@ solvers(Solvers) -> Solvers.
%%
%% ============================================================================
-traverse_scc([{MFA, Def, Rec}|Left], DefSet, AccState) ->
+traverse_scc([{_MFA, Def, Rec}|Left], DefSet, AccState) ->
TmpState1 = state__set_rec_dict(AccState, Rec),
- TmpState2 = state__set_opaques(TmpState1, MFA),
DummyLetrec = cerl:c_letrec([Def], cerl:c_atom(foo)),
- {NewAccState, _} = traverse(DummyLetrec, DefSet, TmpState2),
+ {NewAccState, _} = traverse(DummyLetrec, DefSet, TmpState1),
traverse_scc(Left, DefSet, NewAccState);
traverse_scc([], _DefSet, AccState) ->
AccState.
@@ -386,12 +393,7 @@ traverse(Tree, DefinedVars, State) ->
case cerl:unfold_literal(Tree) of
Tree ->
Type = t_from_term(cerl:concrete(Tree)),
- NewType =
- case erl_types:t_opaque_match_atom(Type, State#state.opaques) of
- [Opaque] -> Opaque;
- _ -> Type
- end,
- {State, NewType};
+ {State, Type};
NewTree -> traverse(NewTree, DefinedVars, State)
end;
module ->
@@ -462,27 +464,21 @@ traverse(Tree, DefinedVars, State) ->
[Tag|Fields] ->
case cerl:is_c_atom(Tag) of
true ->
- %% Check if an opaque term is constructed.
- case t_opaque_match_record(TupleType, State#state.opaques) of
- [Opaque] ->
- OpStruct = t_opaque_matching_structure(TupleType, Opaque),
- State3 = state__store_conj(TupleType, sub, OpStruct, State2),
- {State3, Opaque};
- %% Check if a record is constructed.
- _ ->
- Arity = length(Fields),
- Records = State2#state.records,
- case lookup_record(Records, cerl:atom_val(Tag), Arity) of
- error -> {State2, TupleType};
- {ok, RecType} ->
- State3 = state__store_conj(TupleType, sub, RecType, State2),
- {State3, TupleType}
- end
- end;
+ %% Check if a record is constructed.
+ Arity = length(Fields),
+ Records = State2#state.records,
+ case lookup_record(Records, cerl:atom_val(Tag), Arity) of
+ error -> {State2, TupleType};
+ {ok, RecType} ->
+ State3 = state__store_conj(TupleType, sub, RecType, State2),
+ {State3, TupleType}
+ end;
false -> {State2, TupleType}
- end;
+ end;
[] -> {State2, TupleType}
end;
+ map ->
+ {State, t_map([])};
values ->
%% We can get into trouble when unifying products that have the
%% same element appearing several times. Handle these cases by
@@ -591,9 +587,13 @@ handle_try(Tree, DefinedVars, State) ->
case state__is_in_guard(State) of
true ->
Conj1 = mk_conj_constraint_list([ArgBodyCs,
- mk_constraint(BodyVar, eq, TreeVar)]),
+ mk_constraint(BodyVar,
+ eq,
+ TreeVar)]),
Disj = mk_disj_constraint_list([Conj1,
- mk_constraint(HandlerVar, eq, TreeVar)]),
+ mk_constraint(HandlerVar,
+ eq,
+ TreeVar)]),
NewState1 = state__new_constraint_context(HandlerState),
Conj2 = mk_conj_constraint_list([OldCs, Disj]),
NewState2 = state__store_conj(Conj2, NewState1),
@@ -604,19 +604,27 @@ handle_try(Tree, DefinedVars, State) ->
{false, false} ->
Conj1 =
mk_conj_constraint_list([ArgBodyCs,
- mk_constraint(TreeVar, eq, BodyVar)]),
+ mk_constraint(TreeVar,
+ eq,
+ BodyVar)]),
Conj2 =
mk_conj_constraint_list([HandlerCs,
- mk_constraint(TreeVar, eq, HandlerVar)]),
+ mk_constraint(TreeVar,
+ eq,
+ HandlerVar)]),
Disj = mk_disj_constraint_list([Conj1, Conj2]),
{Disj, TreeVar};
{false, true} ->
{mk_conj_constraint_list([ArgBodyCs,
- mk_constraint(TreeVar, eq, BodyVar)]),
+ mk_constraint(TreeVar,
+ eq,
+ BodyVar)]),
BodyVar};
{true, false} ->
{mk_conj_constraint_list([HandlerCs,
- mk_constraint(TreeVar, eq, HandlerVar)]),
+ mk_constraint(TreeVar,
+ eq,
+ HandlerVar)]),
HandlerVar};
{true, true} ->
?debug("Throw failed\n", []),
@@ -668,10 +676,7 @@ 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,
SCCMFAs = State#state.mfas,
- {FunModule, _, _} = MFA,
Contract =
case lists:member(MFA, SCCMFAs) of
true -> none;
@@ -691,28 +696,24 @@ get_plt_constr(MFA, Dst, ArgVars, State) ->
none ->
{?mk_fun_var(fun(Map) ->
ArgTypes = lookup_type_list(ArgVars, Map),
- dialyzer_contracts:get_contract_return(C, ArgTypes)
+ get_contract_return(C, ArgTypes)
end, ArgVars), GenArgs};
{value, {PltRetType, PltArgTypes}} ->
%% Need to combine the contract with the success typing.
{?mk_fun_var(
fun(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)
+ ArgTypes = lookup_type_list(ArgVars, Map),
+ CRet = get_contract_return(C, ArgTypes),
+ t_inf(CRet, PltRetType)
end, ArgVars),
- [t_inf(X, Y, opaque) || {X, Y} <- lists:zip(GenArgs, PltArgTypes)]}
+ [t_inf(X, Y) || {X, Y} <- lists:zip(GenArgs, PltArgTypes)]}
end,
state__store_conj_lists([Dst|ArgVars], sub, [RetType|ArgCs], State)
end.
+get_contract_return(C, ArgTypes) ->
+ dialyzer_contracts:get_contract_return(C, ArgTypes).
+
filter_match_fail([Clause] = Cls) ->
Body = cerl:clause_body(Clause),
case cerl:type(Body) of
@@ -1045,6 +1046,9 @@ get_safe_underapprox_1([Pat|Left], Acc, Map) ->
{Ts, Map1} = get_safe_underapprox_1(Es, [], Map),
Type = t_tuple(Ts),
get_safe_underapprox_1(Left, [Type|Acc], Map1);
+ map ->
+ %% TODO: Can maybe do something here
+ throw(dont_know);
values ->
Es = cerl:values_es(Pat),
{Ts, Map1} = get_safe_underapprox_1(Es, [], Map),
@@ -1086,7 +1090,7 @@ get_bif_constr({erlang, Op, 2}, Dst, Args = [Arg1, Arg2], _State)
when Op =:= '+'; Op =:= '-'; Op =:= '*' ->
ReturnType = ?mk_fun_var(fun(Map) ->
TmpArgTypes = lookup_type_list(Args, Map),
- erl_bif_types:type(erlang, Op, 2, TmpArgTypes)
+ bif_return(erlang, Op, 2, TmpArgTypes)
end, Args),
ArgFun =
fun(A, Pos) ->
@@ -1128,8 +1132,8 @@ get_bif_constr({erlang, Op, 2}, Dst, [Arg1, Arg2] = Args, _State)
fun(LocalArg1, LocalArg2, LocalOp) ->
fun(Map) ->
DstType = lookup_type(Dst, Map),
- IsTrue = t_is_atom(true, DstType),
- IsFalse = t_is_atom(false, DstType),
+ IsTrue = t_is_any_atom(true, DstType),
+ IsFalse = t_is_any_atom(false, DstType),
case IsTrue orelse IsFalse of
true ->
Arg1Type = lookup_type(LocalArg1, Map),
@@ -1176,7 +1180,7 @@ get_bif_constr({erlang, Op, 2}, Dst, [Arg1, Arg2] = Args, _State)
Arg2Var = ?mk_fun_var(Arg2Fun, DstArgs),
DstVar = ?mk_fun_var(fun(Map) ->
TmpArgTypes = lookup_type_list(Args, Map),
- erl_bif_types:type(erlang, Op, 2, TmpArgTypes)
+ bif_return(erlang, Op, 2, TmpArgTypes)
end, Args),
mk_conj_constraint_list([mk_constraint(Dst, sub, DstVar),
mk_constraint(Arg1, sub, Arg1Var),
@@ -1218,7 +1222,7 @@ get_bif_constr({erlang, '++', 2}, Dst, [Hd, Tl] = Args, _State) ->
ArgTypes = erl_bif_types:arg_types(erlang, '++', 2),
ReturnType = ?mk_fun_var(fun(Map) ->
TmpArgTypes = lookup_type_list(Args, Map),
- erl_bif_types:type(erlang, '++', 2, TmpArgTypes)
+ bif_return(erlang, '++', 2, TmpArgTypes)
end, Args),
Cs = mk_constraints(Args, sub, ArgTypes),
mk_conj_constraint_list([mk_constraint(Dst, sub, ReturnType),
@@ -1240,7 +1244,7 @@ get_bif_constr({erlang, is_function, 1}, Dst, [Arg], State) ->
get_bif_constr({erlang, is_function, 2}, Dst, [Fun, Arity], _State) ->
ArgFun = fun(Map) ->
DstType = lookup_type(Dst, Map),
- case t_is_atom(true, DstType) of
+ case t_is_any_atom(true, DstType) of
true ->
ArityType = lookup_type(Arity, Map),
case t_number_vals(ArityType) of
@@ -1268,7 +1272,7 @@ get_bif_constr({erlang, is_reference, 1}, Dst, [Arg], State) ->
get_bif_test_constr(Dst, Arg, t_reference(), State);
get_bif_constr({erlang, is_record, 2}, Dst, [Var, Tag] = Args, _State) ->
ArgFun = fun(Map) ->
- case t_is_atom(true, lookup_type(Dst, Map)) of
+ case t_is_any_atom(true, lookup_type(Dst, Map)) of
true -> t_tuple();
false -> t_any()
end
@@ -1276,7 +1280,7 @@ get_bif_constr({erlang, is_record, 2}, Dst, [Var, Tag] = Args, _State) ->
ArgV = ?mk_fun_var(ArgFun, [Dst]),
DstFun = fun(Map) ->
TmpArgTypes = lookup_type_list(Args, Map),
- erl_bif_types:type(erlang, is_record, 2, TmpArgTypes)
+ bif_return(erlang, is_record, 2, TmpArgTypes)
end,
DstV = ?mk_fun_var(DstFun, Args),
mk_conj_constraint_list([mk_constraint(Dst, sub, DstV),
@@ -1285,10 +1289,9 @@ get_bif_constr({erlang, is_record, 2}, Dst, [Var, Tag] = Args, _State) ->
get_bif_constr({erlang, is_record, 3}, Dst, [Var, Tag, Arity] = Args, State) ->
%% TODO: Revise this to make it precise for Tag and Arity.
Records = State#state.records,
- AllOpaques = State#state.opaques,
ArgFun =
fun(Map) ->
- case t_is_atom(true, lookup_type(Dst, Map)) of
+ case t_is_any_atom(true, lookup_type(Dst, Map)) of
true ->
ArityType = lookup_type(Arity, Map),
case t_is_integer(ArityType) of
@@ -1304,10 +1307,7 @@ get_bif_constr({erlang, is_record, 3}, Dst, [Var, Tag, Arity] = Args, State) ->
[TagVal] ->
case lookup_record(Records, TagVal, ArityVal - 1) of
{ok, Type} ->
- case t_opaque_match_record(Type, AllOpaques) of
- [Opaque] -> Opaque;
- _ -> Type
- end;
+ Type;
error -> GenRecord
end;
_ -> GenRecord
@@ -1323,38 +1323,9 @@ get_bif_constr({erlang, is_record, 3}, Dst, [Var, Tag, Arity] = Args, State) ->
end,
ArgV = ?mk_fun_var(ArgFun, [Tag, Arity, Dst]),
DstFun = fun(Map) ->
- [TmpVar, TmpTag, TmpArity] = TmpArgTypes = lookup_type_list(Args, Map),
- TmpArgTypes2 =
- case lists:member(TmpVar, AllOpaques) of
- true ->
- case t_is_integer(TmpArity) of
- true ->
- case t_number_vals(TmpArity) of
- [TmpArityVal] ->
- case t_is_atom(TmpTag) of
- true ->
- case t_atom_vals(TmpTag) of
- [TmpTagVal] ->
- case lookup_record(Records, TmpTagVal,
- TmpArityVal - 1) of
- {ok, TmpType} ->
- case t_is_none(t_inf(TmpType, TmpVar, opaque)) of
- true -> TmpArgTypes;
- false -> [TmpType, TmpTag, TmpArity]
- end;
- error -> TmpArgTypes
- end;
- _ -> TmpArgTypes
- end;
- false -> TmpArgTypes
- end;
- _ -> TmpArgTypes
- end;
- false -> TmpArgTypes
- end;
- false -> TmpArgTypes
- end,
- erl_bif_types:type(erlang, is_record, 3, TmpArgTypes2)
+ [TmpVar, TmpTag, TmpArity] = lookup_type_list(Args, Map),
+ TmpArgTypes = [TmpVar,TmpTag,TmpArity],
+ bif_return(erlang, is_record, 3, TmpArgTypes)
end,
DstV = ?mk_fun_var(DstFun, Args),
mk_conj_constraint_list([mk_constraint(Dst, sub, DstV),
@@ -1369,12 +1340,14 @@ get_bif_constr({erlang, 'and', 2}, Dst, [Arg1, Arg2] = Args, _State) ->
ArgFun = fun(Var) ->
fun(Map) ->
DstType = lookup_type(Dst, Map),
- case t_is_atom(true, DstType) of
+ case t_is_any_atom(true, DstType) of
true -> True;
false ->
- case t_is_atom(false, DstType) of
+ case t_is_any_atom(false, DstType) of
true ->
- case t_is_atom(true, lookup_type(Var, Map)) of
+ case
+ t_is_any_atom(true, lookup_type(Var, Map))
+ of
true -> False;
false -> t_boolean()
end;
@@ -1386,15 +1359,15 @@ get_bif_constr({erlang, 'and', 2}, Dst, [Arg1, Arg2] = Args, _State) ->
end,
DstFun = fun(Map) ->
Arg1Type = lookup_type(Arg1, Map),
- case t_is_atom(false, Arg1Type) of
+ case t_is_any_atom(false, Arg1Type) of
true -> False;
false ->
Arg2Type = lookup_type(Arg2, Map),
- case t_is_atom(false, Arg2Type) of
+ case t_is_any_atom(false, Arg2Type) of
true -> False;
false ->
- case (t_is_atom(true, Arg1Type)
- andalso t_is_atom(true, Arg2Type)) of
+ case (t_is_any_atom(true, Arg1Type)
+ andalso t_is_any_atom(true, Arg2Type)) of
true -> True;
false -> t_boolean()
end
@@ -1413,12 +1386,14 @@ get_bif_constr({erlang, 'or', 2}, Dst, [Arg1, Arg2] = Args, _State) ->
ArgFun = fun(Var) ->
fun(Map) ->
DstType = lookup_type(Dst, Map),
- case t_is_atom(false, DstType) of
+ case t_is_any_atom(false, DstType) of
true -> False;
false ->
- case t_is_atom(true, DstType) of
+ case t_is_any_atom(true, DstType) of
true ->
- case t_is_atom(false, lookup_type(Var, Map)) of
+ case
+ t_is_any_atom(false, lookup_type(Var, Map))
+ of
true -> True;
false -> t_boolean()
end;
@@ -1430,15 +1405,15 @@ get_bif_constr({erlang, 'or', 2}, Dst, [Arg1, Arg2] = Args, _State) ->
end,
DstFun = fun(Map) ->
Arg1Type = lookup_type(Arg1, Map),
- case t_is_atom(true, Arg1Type) of
+ case t_is_any_atom(true, Arg1Type) of
true -> True;
false ->
Arg2Type = lookup_type(Arg2, Map),
- case t_is_atom(true, Arg2Type) of
+ case t_is_any_atom(true, Arg2Type) of
true -> True;
false ->
- case (t_is_atom(false, Arg1Type)
- andalso t_is_atom(false, Arg2Type)) of
+ case (t_is_any_atom(false, Arg1Type)
+ andalso t_is_any_atom(false, Arg2Type)) of
true -> False;
false -> t_boolean()
end
@@ -1465,10 +1440,10 @@ get_bif_constr({erlang, 'not', 1}, Dst, [Arg] = Args, _State) ->
Fun = fun(Var) ->
fun(Map) ->
Type = lookup_type(Var, Map),
- case t_is_atom(true, Type) of
+ case t_is_any_atom(true, Type) of
true -> False;
false ->
- case t_is_atom(false, Type) of
+ case t_is_any_atom(false, Type) of
true -> True;
false -> t_boolean()
end
@@ -1485,10 +1460,10 @@ get_bif_constr({erlang, '=:=', 2}, Dst, [Arg1, Arg2] = Args, _State) ->
fun(Map) ->
DstType = lookup_type(Dst, Map),
OtherVarType = lookup_type(OtherVar, Map),
- case t_is_atom(true, DstType) of
+ case t_is_any_atom(true, DstType) of
true -> OtherVarType;
false ->
- case t_is_atom(false, DstType) of
+ case t_is_any_atom(false, DstType) of
true ->
case is_singleton_type(OtherVarType) of
true -> t_subtract(lookup_type(Self, Map), OtherVarType);
@@ -1518,7 +1493,7 @@ get_bif_constr({erlang, '=:=', 2}, Dst, [Arg1, Arg2] = Args, _State) ->
get_bif_constr({erlang, '==', 2}, Dst, [Arg1, Arg2] = Args, _State) ->
DstFun = fun(Map) ->
TmpArgTypes = lookup_type_list(Args, Map),
- erl_bif_types:type(erlang, '==', 2, TmpArgTypes)
+ bif_return(erlang, '==', 2, TmpArgTypes)
end,
ArgFun =
fun(Var, Self) ->
@@ -1527,16 +1502,16 @@ get_bif_constr({erlang, '==', 2}, Dst, [Arg1, Arg2] = Args, _State) ->
DstType = lookup_type(Dst, Map),
case is_singleton_non_number_type(VarType) of
true ->
- case t_is_atom(true, DstType) of
+ case t_is_any_atom(true, DstType) of
true -> VarType;
false ->
- case t_is_atom(false, DstType) of
+ case t_is_any_atom(false, DstType) of
true -> t_subtract(lookup_type(Self, Map), VarType);
false -> t_any()
end
end;
false ->
- case t_is_atom(true, DstType) of
+ case t_is_any_atom(true, DstType) of
true ->
case t_is_number(VarType) of
true -> t_number();
@@ -1560,18 +1535,14 @@ get_bif_constr({erlang, '==', 2}, Dst, [Arg1, Arg2] = Args, _State) ->
mk_constraint(Arg1, sub, ArgV1),
mk_constraint(Arg2, sub, ArgV2)]);
get_bif_constr({erlang, element, 2} = _BIF, Dst, Args,
- #state{cs = Constrs, opaques = Opaques}) ->
+ #state{cs = Constrs}) ->
GenType = erl_bif_types:type(erlang, element, 2),
case t_is_none(GenType) of
true -> ?debug("Bif: ~w failed\n", [_BIF]), throw(error);
false ->
Fun = fun(Map) ->
- [I, T] = ATs = lookup_type_list(Args, Map),
- ATs2 = case lists:member(T, Opaques) of
- true -> [I, erl_types:t_opaque_structure(T)];
- false -> ATs
- end,
- erl_bif_types:type(erlang, element, 2, ATs2)
+ ATs2 = lookup_type_list(Args, Map),
+ bif_return(erlang, element, 2, ATs2)
end,
ReturnType = ?mk_fun_var(Fun, Args),
ArgTypes = erl_bif_types:arg_types(erlang, element, 2),
@@ -1583,22 +1554,14 @@ get_bif_constr({erlang, element, 2} = _BIF, Dst, Args,
end,
mk_conj_constraint_list([mk_constraint(Dst, sub, ReturnType)|NewCs])
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) ->
- TmpArgTypes0 = lookup_type_list(Args, Map),
- TmpArgTypes = [UnopaqueFun(T) || T<- TmpArgTypes0],
- erl_bif_types:type(M, F, A, TmpArgTypes)
+ TmpArgTypes = lookup_type_list(Args, Map),
+ bif_return(M, F, A, TmpArgTypes)
end, Args),
case erl_bif_types:is_known(M, F, A) of
false ->
@@ -1616,12 +1579,12 @@ get_bif_constr({M, F, A} = _BIF, Dst, Args, State) ->
end.
eval_inv_arith('+', _Pos, Dst, Arg) ->
- erl_bif_types:type(erlang, '-', 2, [Dst, Arg]);
+ bif_return(erlang, '-', 2, [Dst, Arg]);
eval_inv_arith('*', _Pos, Dst, Arg) ->
case t_number_vals(Arg) of
[0] -> t_integer();
_ ->
- TmpRet = erl_bif_types:type(erlang, 'div', 2, [Dst, Arg]),
+ TmpRet = bif_return(erlang, 'div', 2, [Dst, Arg]),
Zero = t_from_term(0),
%% If 0 is not part of the result, it cannot be part of the argument.
case t_is_subtype(Zero, Dst) of
@@ -1630,9 +1593,9 @@ eval_inv_arith('*', _Pos, Dst, Arg) ->
end
end;
eval_inv_arith('-', 1, Dst, Arg) ->
- erl_bif_types:type(erlang, '-', 2, [Arg, Dst]);
+ bif_return(erlang, '-', 2, [Arg, Dst]);
eval_inv_arith('-', 2, Dst, Arg) ->
- erl_bif_types:type(erlang, '+', 2, [Arg, Dst]).
+ bif_return(erlang, '+', 2, [Arg, Dst]).
range_inc(neg_inf) -> neg_inf;
range_inc(pos_inf) -> pos_inf;
@@ -1642,33 +1605,20 @@ range_dec(neg_inf) -> neg_inf;
range_dec(pos_inf) -> pos_inf;
range_dec(Int) when is_integer(Int) -> Int - 1.
-get_bif_test_constr(Dst, Arg, Type, State) ->
+get_bif_test_constr(Dst, Arg, Type, _State) ->
ArgFun = fun(Map) ->
DstType = lookup_type(Dst, Map),
- case t_is_atom(true, DstType) of
+ case t_is_any_atom(true, DstType) of
true -> Type;
false -> t_any()
end
end,
ArgV = ?mk_fun_var(ArgFun, [Dst]),
- Opaques = State#state.opaques,
DstFun = fun(Map) ->
ArgType = lookup_type(Arg, Map),
case t_is_none(t_inf(ArgType, Type)) of
true ->
- case lists:member(ArgType, Opaques) of
- true ->
- OpaqueStruct = erl_types:t_opaque_structure(ArgType),
- case t_is_none(t_inf(OpaqueStruct, Type)) of
- true -> t_from_term(false);
- false ->
- case t_is_subtype(ArgType, Type) of
- true -> t_from_term(true);
- false -> t_boolean()
- end
- end;
- false -> t_from_term(false)
- end;
+ t_from_term(false);
false ->
case t_is_subtype(ArgType, Type) of
true -> t_from_term(true);
@@ -1784,7 +1734,6 @@ minimize_state(#state{
fun_arities = FunArities,
self_rec = SelfRec,
prop_types = {d, PropTypes},
- opaques = Opaques,
solvers = Solvers
}) ->
Opts = [{read_concurrency, true}],
@@ -1798,7 +1747,6 @@ minimize_state(#state{
fun_arities = FunArities,
self_rec = SelfRec,
prop_types = {e, ETSPropTypes},
- opaques = Opaques,
solvers = Solvers
}.
@@ -1947,7 +1895,7 @@ sane_maps(Map1, Map2, Keys, _S1, _S2) ->
%% Solver v2
--record(v2_state, {constr_data = dict:new() :: dict(),
+-record(v2_state, {constr_data = dict:new() :: dict:dict(),
state :: #state{}}).
v2_solve_ref(Fun, Map, State) ->
@@ -1956,8 +1904,7 @@ v2_solve_ref(Fun, Map, State) ->
{ok, NewMap}.
v2_solve(#constraint{}=C, Map, V2State) ->
- State = V2State#v2_state.state,
- case solve_one_c(C, Map, State#state.opaques) of
+ case solve_one_c(C, Map) of
error ->
report_failed_constraint(C, Map),
{error, V2State};
@@ -2031,7 +1978,7 @@ v2_solve_self_recursive(Cs, Map, Id, RecType0, V2State0) ->
{ok, NewMap, V2State, U} ->
pp_map("recursive finished", NewMap),
NewRecType = unsafe_lookup_type(Id, NewMap),
- case t_is_equal(NewRecType, RecType0) of
+ case is_equal(NewRecType, RecType0) of
true ->
{NewMap2, U1} = enter_var_type(RecVar, NewRecType, NewMap),
{ok, NewMap2, V2State, lists:umerge(U, U1)};
@@ -2397,7 +2344,7 @@ solve_self_recursive(Cs, Map, MapDict, Id, RecType0, State) ->
{ok, NewMapDict, NewMap} ->
pp_map("NewMap", NewMap),
NewRecType = unsafe_lookup_type(Id, NewMap),
- case t_is_equal(NewRecType, RecType0) of
+ case is_equal(NewRecType, RecType0) of
true ->
{ok, NewMapDict, enter_type(RecVar, NewRecType, NewMap)};
false ->
@@ -2447,7 +2394,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, State#state.opaques) of
+ case solve_one_c(C, Map) of
error ->
report_failed_constraint(C, Map),
{error, MapDict};
@@ -2457,10 +2404,10 @@ 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, Opaques) ->
+solve_one_c(#constraint{lhs = Lhs, rhs = Rhs, op = Op}, Map) ->
LhsType = lookup_type(Lhs, Map),
RhsType = lookup_type(Rhs, Map),
- Inf = t_inf(LhsType, RhsType, opaque),
+ Inf = t_inf(LhsType, RhsType),
?debug("Solving: ~s :: ~s ~w ~s :: ~s\n\tInf: ~s\n",
[format_type(Lhs), format_type(LhsType), Op,
format_type(Rhs), format_type(RhsType), format_type(Inf)]),
@@ -2468,12 +2415,12 @@ solve_one_c(#constraint{lhs = Lhs, rhs = Rhs, op = Op}, Map, Opaques) ->
true -> error;
false ->
case Op of
- sub -> solve_subtype(Lhs, Inf, Map, Opaques);
+ sub -> solve_subtype(Lhs, Inf, Map);
eq ->
- case solve_subtype(Lhs, Inf, Map, Opaques) of
+ case solve_subtype(Lhs, Inf, Map) of
error -> error;
{ok, {Map1, U1}} ->
- case solve_subtype(Rhs, Inf, Map1, Opaques) of
+ case solve_subtype(Rhs, Inf, Map1) of
error -> error;
{ok, {Map2, U2}} -> {ok, {Map2, lists:umerge(U1, U2)}}
end
@@ -2481,7 +2428,7 @@ solve_one_c(#constraint{lhs = Lhs, rhs = Rhs, op = Op}, Map, Opaques) ->
end
end.
-solve_subtype(Type, Inf, Map, Opaques) ->
+solve_subtype(Type, Inf, Map) ->
%% case cerl:is_literal(Type) of
%% true ->
%% case t_is_subtype(t_from_term(cerl:concrete(Type)), Inf) of
@@ -2489,7 +2436,7 @@ solve_subtype(Type, Inf, Map, Opaques) ->
%% false -> error
%% end;
%% false ->
- try t_unify(Type, Inf, Opaques) of
+ try t_unify(Type, Inf) of
{_, List} -> {ok, enter_type_list(List, Map)}
catch
throw:{mismatch, _T1, _T2} ->
@@ -2540,7 +2487,7 @@ join_one_key(Key, [Map|Maps], Type) ->
true -> Type;
false ->
NewType = lookup_type(Key, Map),
- case t_is_equal(NewType, Type) of
+ case is_equal(NewType, Type) of
true -> join_one_key(Key, Maps, Type);
false -> join_one_key(Key, Maps, t_sup(NewType, Type))
end
@@ -2555,7 +2502,7 @@ maps_are_equal(Map1, Map2, Deps) ->
maps_are_equal_1(Map1, Map2, [H|Tail]) ->
T1 = lookup_type(H, Map1),
T2 = lookup_type(H, Map2),
- case t_is_equal(T1, T2) of
+ case is_equal(T1, T2) of
true -> maps_are_equal_1(Map1, Map2, Tail);
false ->
?debug("~w: ~s =/= ~s\n", [H, format_type(T1), format_type(T2)]),
@@ -2587,14 +2534,22 @@ prune_keys(Map1, Map2, Deps) ->
enter_type(Key, Val, Map) when is_integer(Key) ->
?debug("Entering ~s :: ~s\n", [format_type(t_var(Key)), format_type(Val)]),
- case t_is_any(Val) of
+ %% Keep any() in the map if it is opaque:
+ case is_equal(Val, t_any()) of
true ->
erase_type(Key, Map);
false ->
LimitedVal = t_limit(Val, ?INTERNAL_TYPE_LIMIT),
+ case is_equal(LimitedVal, Val) of
+ true -> ok;
+ false -> ?debug("LimitedVal ~s\n", [format_type(LimitedVal)])
+ end,
case dict:find(Key, Map) of
- {ok, LimitedVal} -> Map;
- {ok, _} -> map_store(Key, LimitedVal, Map);
+ {ok, Value} ->
+ case is_equal(Value, LimitedVal) of
+ true -> Map;
+ false -> map_store(Key, LimitedVal, Map)
+ end;
error -> map_store(Key, LimitedVal, Map)
end
end;
@@ -2681,7 +2636,10 @@ updated_vars_only(U, OldMap, NewMap) ->
[V || V <- U, not is_same(V, OldMap, NewMap)].
is_same(Key, Map1, Map2) ->
- t_is_equal(lookup_type(Key, Map1), lookup_type(Key, Map2)).
+ is_equal(lookup_type(Key, Map1), lookup_type(Key, Map2)).
+
+is_equal(Type1, Type2) ->
+ t_is_equal(Type1, Type2).
pp_map(_S, _Map) ->
?debug("\t~s: ~p\n",
@@ -2716,11 +2674,6 @@ new_state(SCC0, NextLabel, CallGraph, Plt, PropTypes, Solvers) ->
state__set_rec_dict(State, RecDict) ->
State#state{records = 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, module = M}.
-
state__set_in_match(State, Bool) ->
State#state{in_match = Bool}.
@@ -2760,7 +2713,8 @@ state__lookup_undef_var(Tree, #state{callgraph = CG, plt = Plt}) ->
{ok, MFA} ->
case dialyzer_plt:lookup(Plt, MFA) of
none -> error;
- {value, {RetType, ArgTypes}} -> {ok, t_fun(ArgTypes, RetType)}
+ {value, {RetType, ArgTypes}} ->
+ {ok, t_fun(ArgTypes, RetType)}
end
end.
@@ -2897,7 +2851,7 @@ state__get_cs(Var, #state{cmap = {d, Dict}}) ->
dict:fetch(Var, Dict).
state__is_self_rec(Fun, #state{self_rec = SelfRec}) ->
- Fun =:= SelfRec.
+ not (SelfRec =:= 'false') andalso is_equal(Fun, SelfRec).
state__store_funs(Vars0, Funs0, #state{fun_map = Map} = State) ->
debug_make_name_map(Vars0, Funs0),
@@ -2923,7 +2877,9 @@ state__finalize(State) ->
%%
%% ============================================================================
--spec mk_constraint(erl_types:erl_type(), constr_op(), fvar_or_type()) -> #constraint{}.
+-spec mk_constraint(erl_types:erl_type(),
+ constr_op(),
+ fvar_or_type()) -> #constraint{}.
mk_constraint(Lhs, Op, Rhs) ->
case t_is_any(Lhs) orelse constraint_opnd_is_any(Rhs) of
@@ -2934,9 +2890,9 @@ mk_constraint(Lhs, Op, Rhs) ->
case Deps =:= [] of
true ->
%% This constraint is constant. Solve it immediately.
- case solve_one_c(C, map_new(), []) of
+ case solve_one_c(C, map_new()) of
error -> throw(error);
- _ ->
+ _R ->
%% This is always true, keep it anyway for logistic reasons
C
end;
@@ -2944,10 +2900,13 @@ mk_constraint(Lhs, Op, Rhs) ->
C
end;
true ->
- C = mk_constraint_1(t_any(), Op, t_any()),
- C#constraint{deps = []}
+ mk_constraint_any(Op)
end.
+mk_constraint_any(Op) ->
+ C = mk_constraint_1(t_any(), Op, t_any()),
+ C#constraint{deps = []}.
+
%% the following function is used so that we do not call
%% erl_types:t_is_any/1 with a term other than an erl_type()
-spec constraint_opnd_is_any(fvar_or_type()) -> boolean().
@@ -3002,7 +2961,8 @@ mk_constraint_1(Lhs, Op, Rhs) ->
#constraint{lhs = Lhs, op = Op, rhs = Rhs}.
mk_constraints([Lhs|LhsTail], Op, [Rhs|RhsTail]) ->
- [mk_constraint(Lhs, Op, Rhs)|mk_constraints(LhsTail, Op, RhsTail)];
+ [mk_constraint(Lhs, Op, Rhs) |
+ mk_constraints(LhsTail, Op, RhsTail)];
mk_constraints([], _Op, []) ->
[].
@@ -3017,7 +2977,7 @@ mk_constraint_list(Type, List) ->
Deps = calculate_deps(List2),
case Deps =:= [] of
true -> #constraint_list{type = conj,
- list = [mk_constraint(t_any(), eq, t_any())],
+ list = [mk_constraint_any(eq)],
deps = []};
false -> #constraint_list{type = Type, list = List2, deps = Deps}
end.
@@ -3236,6 +3196,9 @@ calculate_masks([], _I, L) ->
%%
%% ============================================================================
+bif_return(M, F, A, Xs) ->
+ erl_bif_types:type(M, F, A, Xs).
+
is_singleton_non_number_type(Type) ->
case t_is_number(Type) of
true -> false;
@@ -3265,7 +3228,7 @@ is_singleton_type(Type) ->
find_element(Args, Cs) ->
[Pos, Tuple] = Args,
- case erl_types:t_is_number(Pos) of
+ case t_is_number(Pos) of
true ->
case erl_types:t_number_vals(Pos) of
'unknown' -> 'unknown';
@@ -3301,8 +3264,10 @@ find_constraint(Tuple, [_|Cs]) ->
lookup_record(Records, Tag, Arity) ->
case erl_types:lookup_record(Tag, Arity, Records) of
{ok, Fields} ->
- {ok, t_tuple([t_from_term(Tag)|
- [FieldType || {_FieldName, FieldType} <- Fields]])};
+ RecType =
+ t_tuple([t_from_term(Tag)|
+ [FieldType || {_FieldName, FieldType} <- Fields]]),
+ {ok, RecType};
error ->
error
end.
diff --git a/lib/dialyzer/src/dialyzer_utils.erl b/lib/dialyzer/src/dialyzer_utils.erl
index a4c4d37a0f..21183e3459 100644
--- a/lib/dialyzer/src/dialyzer_utils.erl
+++ b/lib/dialyzer/src/dialyzer_utils.erl
@@ -2,7 +2,7 @@
%%-----------------------------------------------------------------------
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2014. 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
@@ -164,14 +164,14 @@ get_core_from_abstract_code(AbstrCode, Opts) ->
%% ============================================================================
-spec get_record_and_type_info(abstract_code()) ->
- {'ok', dict()} | {'error', string()}.
+ {'ok', dict: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(), module(), dict()) ->
- {'ok', dict()} | {'error', string()}.
+-spec get_record_and_type_info(abstract_code(), module(), dict:dict()) ->
+ {'ok', dict:dict()} | {'error', string()}.
get_record_and_type_info(AbstractCode, Module, RecDict) ->
get_record_and_type_info(AbstractCode, Module, [], RecDict).
@@ -304,7 +304,7 @@ process_record_remote_types(CServer) ->
CServer1 = dialyzer_codeserver:finalize_records(NewRecords, CServer),
dialyzer_codeserver:finalize_exported_types(TempExpTypes, CServer1).
--spec merge_records(dict(), dict()) -> dict().
+-spec merge_records(dict:dict(), dict:dict()) -> dict:dict().
merge_records(NewRecords, OldRecords) ->
dict:merge(fun(_Key, NewVal, _OldVal) -> NewVal end, NewRecords, OldRecords).
@@ -315,10 +315,10 @@ merge_records(NewRecords, OldRecords) ->
%%
%% ============================================================================
--type spec_dict() :: dict().
--type callback_dict() :: dict().
+-type spec_dict() :: dict:dict().
+-type callback_dict() :: dict:dict().
--spec get_spec_info(atom(), abstract_code(), dict()) ->
+-spec get_spec_info(atom(), abstract_code(), dict:dict()) ->
{'ok', spec_dict(), callback_dict()} | {'error', string()}.
get_spec_info(ModName, AbstractCode, RecordsDict) ->
@@ -383,7 +383,7 @@ get_spec_info([], SpecDict, CallbackDict, _RecordsDict, _ModName, _File) ->
%%
%% ============================================================================
--spec sets_filter([module()], set()) -> set().
+-spec sets_filter([module()], sets:set()) -> sets:set().
sets_filter([], ExpTypes) ->
ExpTypes;
@@ -434,7 +434,7 @@ format_errors([]) ->
format_sig(Type) ->
format_sig(Type, dict:new()).
--spec format_sig(erl_types:erl_type(), dict()) -> string().
+-spec format_sig(erl_types:erl_type(), dict:dict()) -> string().
format_sig(Type, RecDict) ->
"fun(" ++ Sig = lists:flatten(erl_types:t_to_string(Type, RecDict)),
@@ -450,11 +450,10 @@ flat_format(Fmt, Lst) ->
%% Created : 5 March 2007
%%-------------------------------------------------------------------
+-spec pp_hook() -> fun((cerl:cerl(), _, _) -> term()).
pp_hook() ->
fun pp_hook/3.
--spec pp_hook() -> fun((cerl:cerl(), _, _) -> term()).
-
pp_hook(Node, Ctxt, Cont) ->
case cerl:type(Node) of
binary ->