aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMagnus Lång <[email protected]>2016-07-04 14:12:07 +0200
committerMagnus Lång <[email protected]>2016-07-04 15:35:05 +0200
commit784f845e2d1ec66b9505339b4b0e43d55c2d49a9 (patch)
tree40ee492ba9ecc4efdbfdca696b46c759c714e01f
parent1697cdea341e7d54e754e5f85e091c8bb68fb6c7 (diff)
downloadotp-784f845e2d1ec66b9505339b4b0e43d55c2d49a9.tar.gz
otp-784f845e2d1ec66b9505339b4b0e43d55c2d49a9.tar.bz2
otp-784f845e2d1ec66b9505339b4b0e43d55c2d49a9.zip
dialyzer: Suppress warns on generated case stmts
Warnings about clauses that cannot match and are also compiler generated are suppressed unless none of the clauses return. This feature is useful for non-Erlang BEAM languages (such as Elixir) that compile to Erlang and expand certain language constructs into case statements. In that case, as long as the language construct can succeed, these warnings are undesired and appear spurious to users that do not check the Erlang code that their program expands into. Thanks to José Valim for the test (slightly modified).
-rw-r--r--lib/dialyzer/src/dialyzer_dataflow.erl62
-rw-r--r--lib/dialyzer/test/Makefile1
-rw-r--r--lib/dialyzer/test/abstract_SUITE.erl102
-rw-r--r--lib/dialyzer/test/dialyzer_common.erl11
4 files changed, 147 insertions, 29 deletions
diff --git a/lib/dialyzer/src/dialyzer_dataflow.erl b/lib/dialyzer/src/dialyzer_dataflow.erl
index 9399789464..963c953447 100644
--- a/lib/dialyzer/src/dialyzer_dataflow.erl
+++ b/lib/dialyzer/src/dialyzer_dataflow.erl
@@ -978,12 +978,21 @@ handle_case(Tree, Map, State) ->
false -> State1
end,
Map2 = join_maps_begin(Map1),
- {MapList, State3, Type} =
+ {MapList, State3, Type, Warns} =
handle_clauses(Clauses, Arg, ArgType, ArgType, State2,
- [], Map2, [], []),
+ [], Map2, [], [], []),
+ %% Non-Erlang BEAM languages, such as Elixir, expand language constructs
+ %% into case statements. In that case, we do not want to warn on
+ %% individual clauses not matching unless none of them can.
+ SupressForced = is_compiler_generated(cerl:get_ann(Tree))
+ andalso not (t_is_none(Type)),
+ State4 = lists:foldl(fun({T,R,M,F}, S) ->
+ state__add_warning(
+ S,T,R,M,F andalso (not SupressForced))
+ end, State3, Warns),
Map3 = join_maps_end(MapList, Map2),
debug_pp_map(Map3),
- {State3, Map3, Type}
+ {State4, Map3, Type}
end.
%%----------------------------------------
@@ -1082,22 +1091,24 @@ handle_receive(Tree, Map, State) ->
RaceListSize + 1, State);
false -> State
end,
- {MapList, State2, ReceiveType} =
+ {MapList, State2, ReceiveType, Warns} =
handle_clauses(Clauses, ?no_arg, t_any(), t_any(), State1, [], Map,
- [], []),
+ [], [], []),
+ State3 = lists:foldl(fun({T,R,M,F}, S) -> state__add_warning(S,T,R,M,F) end,
+ State2, Warns),
Map1 = join_maps(MapList, Map),
- {State3, Map2, TimeoutType} = traverse(Timeout, Map1, State2),
- Opaques = State3#state.opaques,
+ {State4, Map2, TimeoutType} = traverse(Timeout, Map1, State3),
+ Opaques = State4#state.opaques,
case (t_is_atom(TimeoutType, Opaques) andalso
(t_atom_vals(TimeoutType, Opaques) =:= ['infinity'])) of
true ->
- {State3, Map2, ReceiveType};
+ {State4, Map2, ReceiveType};
false ->
Action = cerl:receive_action(Tree),
- {State4, Map3, ActionType} = traverse(Action, Map, State3),
+ {State5, Map3, ActionType} = traverse(Action, Map, State4),
Map4 = join_maps([Map3, Map1], Map),
Type = t_sup(ReceiveType, ActionType),
- {State4, Map4, Type}
+ {State5, Map4, Type}
end.
%%----------------------------------------
@@ -1245,7 +1256,7 @@ handle_tuple(Tree, Map, State) ->
%% Clauses
%%
handle_clauses([C|Left], Arg, ArgType, OrigArgType, State, CaseTypes, MapIn,
- Acc, ClauseAcc) ->
+ Acc, ClauseAcc, WarnAcc0) ->
IsRaceAnalysisEnabled = is_race_analysis_enabled(State),
State1 =
case IsRaceAnalysisEnabled of
@@ -1258,8 +1269,8 @@ handle_clauses([C|Left], Arg, ArgType, OrigArgType, State, CaseTypes, MapIn,
State);
false -> State
end,
- {State2, ClauseMap, BodyType, NewArgType} =
- do_clause(C, Arg, ArgType, OrigArgType, MapIn, State1),
+ {State2, ClauseMap, BodyType, NewArgType, WarnAcc} =
+ do_clause(C, Arg, ArgType, OrigArgType, MapIn, State1, WarnAcc0),
{NewClauseAcc, State3} =
case IsRaceAnalysisEnabled of
true ->
@@ -1277,9 +1288,9 @@ handle_clauses([C|Left], Arg, ArgType, OrigArgType, State, CaseTypes, MapIn,
false -> {[BodyType|CaseTypes], [ClauseMap|Acc]}
end,
handle_clauses(Left, Arg, NewArgType, OrigArgType, State3,
- NewCaseTypes, MapIn, NewAcc, NewClauseAcc);
+ NewCaseTypes, MapIn, NewAcc, NewClauseAcc, WarnAcc);
handle_clauses([], _Arg, _ArgType, _OrigArgType, State, CaseTypes, _MapIn, Acc,
- ClauseAcc) ->
+ ClauseAcc, WarnAcc) ->
State1 =
case is_race_analysis_enabled(State) of
true ->
@@ -1289,9 +1300,9 @@ handle_clauses([], _Arg, _ArgType, _OrigArgType, State, CaseTypes, _MapIn, Acc,
RaceListSize + 1, State);
false -> State
end,
- {lists:reverse(Acc), State1, t_sup(CaseTypes)}.
+ {lists:reverse(Acc), State1, t_sup(CaseTypes), WarnAcc}.
-do_clause(C, Arg, ArgType0, OrigArgType, Map, State) ->
+do_clause(C, Arg, ArgType0, OrigArgType, Map, State, Warns) ->
Pats = cerl:clause_pats(C),
Guard = cerl:clause_guard(C),
Body = cerl:clause_body(C),
@@ -1323,7 +1334,7 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, State) ->
[cerl_prettypr:format(C), format_type(ArgType0, State1)]),
case state__warning_mode(State1) of
false ->
- {State1, Map, t_none(), ArgType0};
+ {State1, Map, t_none(), ArgType0, Warns};
true ->
{Msg, Force} =
case t_is_none(ArgType0) of
@@ -1403,8 +1414,7 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, State) ->
{record_match, _} -> ?WARN_MATCHING;
{pattern_match_cov, _} -> ?WARN_MATCHING
end,
- {state__add_warning(State1, WarnType, C, Msg, Force),
- Map, t_none(), ArgType0}
+ {State1, Map, t_none(), ArgType0, [{WarnType, C, Msg, Force}|Warns]}
end;
{Map2, PatTypes} ->
Map3 =
@@ -1437,9 +1447,9 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, State) ->
false ->
{guard_fail_pat, [PatString, format_type(ArgType0, State1)]}
end,
- State2 =
+ Warn =
case Reason of
- none -> state__add_warning(State1, ?WARN_MATCHING, C, DefaultMsg);
+ none -> {?WARN_MATCHING, C, DefaultMsg, false};
{FailGuard, Msg} ->
case is_compiler_generated(cerl:get_ann(FailGuard)) of
false ->
@@ -1448,15 +1458,15 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, State) ->
{neg_guard_fail, _} -> ?WARN_MATCHING;
{opaque_guard, _} -> ?WARN_OPAQUE
end,
- state__add_warning(State1, WarnType, FailGuard, Msg);
+ {WarnType, FailGuard, Msg, false};
true ->
- state__add_warning(State1, ?WARN_MATCHING, C, Msg)
+ {?WARN_MATCHING, C, Msg, false}
end
end,
- {State2, Map, t_none(), NewArgType};
+ {State1, Map, t_none(), NewArgType, [Warn|Warns]};
Map4 ->
{RetState, RetMap, BodyType} = traverse(Body, Map4, State1),
- {RetState, RetMap, BodyType, NewArgType}
+ {RetState, RetMap, BodyType, NewArgType, Warns}
end
end.
diff --git a/lib/dialyzer/test/Makefile b/lib/dialyzer/test/Makefile
index f43e04dd59..0d8fba438c 100644
--- a/lib/dialyzer/test/Makefile
+++ b/lib/dialyzer/test/Makefile
@@ -12,6 +12,7 @@ AUXILIARY_FILES=\
dialyzer_common.erl\
file_utils.erl\
dialyzer_SUITE.erl\
+ abstract_SUITE.erl\
plt_SUITE.erl
# ----------------------------------------------------
diff --git a/lib/dialyzer/test/abstract_SUITE.erl b/lib/dialyzer/test/abstract_SUITE.erl
new file mode 100644
index 0000000000..269db3e836
--- /dev/null
+++ b/lib/dialyzer/test/abstract_SUITE.erl
@@ -0,0 +1,102 @@
+%% This suite contains cases that cannot be written
+%% in Erlang itself and must be done via the abstract
+%% format.
+
+-module(abstract_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+-include("dialyzer_test_constants.hrl").
+
+-export([suite/0, all/0, init_per_suite/0, init_per_suite/1]).
+-export([generated_case/1]).
+
+suite() ->
+ [{timetrap, {minutes, 1}}].
+all() ->
+ [generated_case].
+
+init_per_suite() ->
+ [{timetrap, ?plt_timeout}].
+init_per_suite(Config) ->
+ OutDir = ?config(priv_dir, Config),
+ case dialyzer_common:check_plt(OutDir) of
+ fail -> {skip, "Plt creation/check failed."};
+ ok -> [{dialyzer_options, []}|Config]
+ end.
+
+generated_case(Config) when is_list(Config) ->
+ %% Equivalent to:
+ %%
+ %% -module(foo).
+ %% -export(bar).
+ %% bar() ->
+ %% Arg = sample,
+ %% case Arg of
+ %% #{} -> map;
+ %% _ -> Arg:fn()
+ %% end.
+ %%
+ %% Except the case statement and its clauses are marked as autogenerated.
+ [] =
+ test([{attribute,1,module,foo},
+ {attribute,2,export,[{bar,0}]},
+ {function,3,bar,0,
+ [{clause,3,[],[],
+ [{match,4,{var,4,'Arg'},{atom,4,sample}},
+ {'case',[{location,5},{generated,true}],{var,5,'Arg'},
+ [{clause,[{location,6},{generated,true}],[{map,6,[]}],[],
+ [{atom,6,map}]},
+ {clause,[{location,7},{generated,true}],[{var,7,'_'}],[],
+ [{call,7,{remote,7,{var,7,'Arg'},{atom,7,fn}},[]}]}]}]}]}],
+ Config, [], []),
+ %% With the first clause not auto-generated
+ [{warn_matching,{_,6},_}] =
+ test([{attribute,1,module,foo},
+ {attribute,2,export,[{bar,0}]},
+ {function,3,bar,0,
+ [{clause,3,[],[],
+ [{match,4,{var,4,'Arg'},{atom,4,sample}},
+ {'case',[{location,5},{generated,true}],{var,5,'Arg'},
+ [{clause,6,[{map,6,[]}],[],
+ [{atom,6,map}]},
+ {clause,[{location,7},{generated,true}],[{var,7,'_'}],[],
+ [{call,7,{remote,7,{var,7,'Arg'},{atom,7,fn}},[]}]}]}]}]}],
+ Config, [], []),
+ %% With Arg set to [] so neither clause matches
+ [{warn_return_no_exit,{_,3},_},
+ {warn_matching,{_,6},_},
+ {warn_failing_call,{_,7},_}] =
+ test([{attribute,1,module,foo},
+ {attribute,2,export,[{bar,0}]},
+ {function,3,bar,0,
+ [{clause,3,[],[],
+ [{match,4,{var,4,'Arg'},{nil,4}},
+ {'case',[{location,5},{generated,true}],{var,5,'Arg'},
+ [{clause,[{location,6},{generated,true}],[{map,6,[]}],[],
+ [{atom,6,map}]},
+ {clause,[{location,7},{generated,true}],[{var,7,'_'}],[],
+ [{call,7,{remote,7,{var,7,'Arg'},{atom,7,fn}},[]}]}]}]}]}],
+ Config, [], []),
+ ok.
+
+test(Prog, Config, COpts, DOpts) ->
+ {ok, BeamFile} = compile(Config, Prog, COpts),
+ run_dialyzer(Config, succ_typings, [BeamFile], DOpts).
+
+compile(Config, Prog, CompileOpts) ->
+ OutDir = ?config(priv_dir, Config),
+ Opts = [{outdir, OutDir}, debug_info, return_errors | CompileOpts],
+ {ok, Module, Source} = compile:forms(Prog, Opts),
+ BeamFile = filename:join([OutDir, lists:concat([Module, ".beam"])]),
+ ok = file:write_file(BeamFile, Source),
+ {ok, BeamFile}.
+
+run_dialyzer(Config, Analysis, Files, Opts) ->
+ OutDir = ?config(priv_dir, Config),
+ PltFilename = dialyzer_common:plt_file(OutDir),
+ dialyzer:run([{analysis_type, Analysis},
+ {files, Files},
+ {init_plt, PltFilename},
+ {check_plt, false},
+ {from, byte_code} |
+ Opts]).
diff --git a/lib/dialyzer/test/dialyzer_common.erl b/lib/dialyzer/test/dialyzer_common.erl
index d2b1026c06..48083a2731 100644
--- a/lib/dialyzer/test/dialyzer_common.erl
+++ b/lib/dialyzer/test/dialyzer_common.erl
@@ -7,7 +7,7 @@
-module(dialyzer_common).
--export([check_plt/1, check/4, create_all_suites/0, new_tests/2]).
+-export([check_plt/1, check/4, create_all_suites/0, new_tests/2, plt_file/1]).
-include_lib("kernel/include/file.hrl").
@@ -39,7 +39,7 @@
check_plt(OutDir) ->
io:format("Checking plt:"),
- PltFilename = filename:join(OutDir, ?plt_filename),
+ PltFilename = plt_file(OutDir),
case file:read_file_info(PltFilename) of
{ok, _} -> dialyzer_check_plt(PltFilename);
{error, _ } ->
@@ -63,6 +63,11 @@ check_plt(OutDir) ->
end
end.
+-spec plt_file(string()) -> string().
+
+plt_file(OutDir) ->
+ filename:join(OutDir, ?plt_filename).
+
dialyzer_check_plt(PltFilename) ->
try dialyzer:run([{analysis_type, plt_check},
{init_plt, PltFilename}]) of
@@ -119,7 +124,7 @@ build_plt(PltFilename) ->
'same' | {differ, [term()]}.
check(TestCase, Opts, Dir, OutDir) ->
- PltFilename = filename:join(OutDir, ?plt_filename),
+ PltFilename = plt_file(OutDir),
SrcDir = filename:join(Dir, ?input_files_directory),
ResDir = filename:join(Dir, ?result_files_directory),
Filename = filename:join(SrcDir, atom_to_list(TestCase)),