aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/dialyzer/src/dialyzer_dataflow.erl8
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/common_eunit2
-rw-r--r--lib/dialyzer/test/small_SUITE_data/src/common_eunit.erl121
3 files changed, 130 insertions, 1 deletions
diff --git a/lib/dialyzer/src/dialyzer_dataflow.erl b/lib/dialyzer/src/dialyzer_dataflow.erl
index 2b70c372de..df411342cb 100644
--- a/lib/dialyzer/src/dialyzer_dataflow.erl
+++ b/lib/dialyzer/src/dialyzer_dataflow.erl
@@ -554,7 +554,13 @@ handle_apply(Tree, Map, State) ->
{State3, enter_type(Op, OpType1, Map2), t_none()};
false ->
Map3 = enter_type_lists(Args, NewArgs, Map2),
- {State2, enter_type(Op, OpType1, Map3), t_fun_range(OpType1)}
+ Range0 = t_fun_range(OpType1),
+ Range =
+ case t_is_unit(Range0) of
+ true -> t_none();
+ false -> Range0
+ end,
+ {State2, enter_type(Op, OpType1, Map3), Range}
end
end;
true ->
diff --git a/lib/dialyzer/test/small_SUITE_data/results/common_eunit b/lib/dialyzer/test/small_SUITE_data/results/common_eunit
new file mode 100644
index 0000000000..bb5fd1c9ac
--- /dev/null
+++ b/lib/dialyzer/test/small_SUITE_data/results/common_eunit
@@ -0,0 +1,2 @@
+
+common_eunit.erl:57: The created fun has no local return
diff --git a/lib/dialyzer/test/small_SUITE_data/src/common_eunit.erl b/lib/dialyzer/test/small_SUITE_data/src/common_eunit.erl
new file mode 100644
index 0000000000..bca390068e
--- /dev/null
+++ b/lib/dialyzer/test/small_SUITE_data/src/common_eunit.erl
@@ -0,0 +1,121 @@
+%%=====================================================================
+%% Program with an erroneous type declaration that caused dialyzer to
+%% go into an infinite loop. There are some comments that explain the
+%% symptoms and the culprit: the return of test_fun() is erroneous and
+%% its type should read
+%% fun((config()) -> test_rep() | [test_rep()])
+%% instead. But this should not throw dialyzer into an infinite loop.
+%% This concerned dialyzer in R14B02 (and probably prior).
+%%=====================================================================
+-module(common_eunit).
+
+-export([expand_cases/2]).
+
+-type test_name() :: atom() | {'group', atom()}.
+
+-type test_rep() :: {{atom(), atom(), arity()}, fun()}
+ | {'setup', fun(), fun()}
+ | {'setup', fun(), fun(), fun()}
+ | {atom(), test_rep()}
+ | {atom(), term(), test_rep()}.
+
+-type config() :: [proplists:property()].
+
+-type control() :: tuple() | atom().
+
+%% The combination of the following type and the (erroneous) spec for
+%% expand_cases/2 is the reason for the infinite loop in dialyzer.
+-type test_fun() :: fun((config()) -> test_rep()).
+
+%% If one comments out this spec the infinite loop disappears.
+-spec expand_cases(atom(), test_name() | [test_name()]) -> test_fun().
+expand_cases(Module, Cases) ->
+ if is_list(Cases) ->
+ TestFuns = [expand_case(Module, Case) || Case <- Cases],
+ fun(Config) -> [F(Config) || F <- TestFuns] end;
+ is_atom(Cases); is_tuple(Cases) ->
+ expand_cases(Module, [Cases])
+ end.
+
+-spec expand_case(atom(), test_name()) -> test_fun().
+expand_case(Module, CaseName) when is_atom(CaseName) ->
+ TestFun = fun(Config) ->
+ {{Module, CaseName, 1},
+ fun() -> apply(Module, CaseName, [Config]) end}
+ end,
+ setup_wrapper(Module, TestFun, {init_per_testcase, [CaseName]},
+ {end_per_testcase, [CaseName]});
+expand_case(Module, {group, GroupName}) ->
+ {Control, Cases} = group_specification(Module, GroupName),
+ TestFun = control_wrapper(Control, expand_cases(Module, Cases)),
+ setup_wrapper(Module, TestFun, {init_per_group, [GroupName]},
+ {end_per_group, [GroupName]}).
+
+-spec control_wrapper([control()], test_fun()) -> test_fun().
+control_wrapper([Control|T], TestFun0) ->
+ TestFun1 = control_wrapper(T, TestFun0),
+ fun(Config) ->
+ case Control of
+ parallel ->
+ {inparallel, TestFun1(Config)};
+ sequence ->
+ {inorder, TestFun1(Config)};
+ {timetrap, Time} ->
+ Seconds = case Time of
+ {hours, Hs} -> Hs * 60 * 60;
+ {minutes, Ms} -> Ms * 60;
+ {seconds, Ss} -> Ss;
+ MSs -> MSs / 1000
+ end,
+ {timeout, Seconds, TestFun1(Config)};
+ C when is_atom(C) ->
+ {C, TestFun1(Config)};
+ {C, Arg} ->
+ {C, Arg, TestFun1(Config)}
+ end
+ end;
+control_wrapper([], TestFun) ->
+ TestFun.
+
+-spec setup_wrapper(atom(), test_fun(), Callback, Callback) -> test_fun()
+ when Callback :: {atom(), list()}.
+setup_wrapper(Module, TestFun, {Setup, SA}, {Cleanup, CA}) ->
+ case erlang:function_exported(Module, Setup, length(SA) + 1) of
+ true ->
+ case erlang:function_exported(Module, Cleanup, length(CA) + 1) of
+ true ->
+ fun(Config0) ->
+ {setup,
+ fun() ->
+ apply(Module, Setup, SA ++ [Config0])
+ end,
+ fun(Config1) ->
+ apply(Module, Cleanup, CA ++ [Config1])
+ end,
+ TestFun}
+ end;
+ false ->
+ fun(Config) ->
+ {setup,
+ fun() ->
+ apply(Module, Setup, SA ++ [Config])
+ end,
+ TestFun}
+ end
+ end;
+ false ->
+ TestFun
+ end.
+
+-spec group_specification(atom(), atom()) -> {[control()], [test_name()]}.
+group_specification(Module, GroupName) ->
+ case lists:keyfind(GroupName, 1, Module:groups()) of
+ {_, Control, Cases} when is_list(Control), is_list(Cases) ->
+ {Control, Cases};
+ {_, Cases} when is_list(Cases) ->
+ {[], Cases};
+ false ->
+ exit({missing_group, GroupName});
+ _ ->
+ exit({bad_group_spec, GroupName})
+ end.