1%%---------------------------------------------------------------------------
2%% From: Nicolas Tranger <ikke@nicolast.be>
3%% Date: 10/11/2110
4%%
5%% After adding spec statements to my module, Dialyzer failed on execution
6%% though. I've been trying to create a minimal reproducible case and
7%% reduced the code to something similar of about 80 LOC.  As noted in the
8%% comments, commenting out some lines makes Dialyzer parse and analyze
9%% the file correctly.  The code executes correctly and as expected.
10%%
11%% I'm not sure what causes the issue. parse_result is polymorphic in its
12%% return type, but statically typed based on the type of the 3th argument
13%% (well, that's how I see things from a Haskell background).
14%%---------------------------------------------------------------------------
15%% This was a bug in erl_types:t_subtract/2 which was not handling the case
16%% of free variables in prameterized types. Fixed 15/10/2010.
17%%---------------------------------------------------------------------------
18-module(param_types_crash).
19
20-export([test/0]).
21
22-type socket_error() :: 'connection_closed' | 'timeout'.
23-type app_error() :: 'no_magic' | 'unknown_failure'.
24
25-type resulthandler_result(T) :: {'ok', T} | socket_error() | app_error().
26-type resulthandler(T) :: fun((binary()) -> resulthandler_result(T)).
27
28test() ->
29  Data = <<0:32/little-unsigned, 1:8/little, 1:8/little-unsigned>>,
30  case parse_result(Data, get_option(fun get_bool/1)) of
31	%% Removing the next 2 lines makes
32	%% dialyzer param_types_crash.erl -Wunmatched_returns -Wunderspecs
33	%% succeed. With these lines, it fails.
34	{ok, none} -> none;
35	{ok, {some, Value}} -> Value;
36	Reply -> {error, Reply}
37  end.
38
39-spec parse_result(binary(), resulthandler(T)) -> resulthandler_result(T).
40parse_result(<<ResultCode:32/little-unsigned, Rest/binary>>, ResultHandler) ->
41  case ResultCode of
42	0 -> ResultHandler(Rest);
43	1 -> no_magic;
44	2 -> unknown_failure
45  end.
46
47-spec get_bool(binary()) -> {'ok', boolean()} | socket_error().
48get_bool(Data) ->
49  case get_data(Data, 1, size(Data)) of
50    {<<Value:8/little-unsigned>>, <<>>} -> {ok, (Value =:= 1)};
51    Other -> Other
52  end.
53
54-spec get_option(resulthandler(T)) -> resulthandler('none' | {'some', T}).
55get_option(Fun) ->
56  fun(Data) ->
57      case get_data(Data, 1, size(Data)) of
58	{<<HasValue:8/little>>, Rest} ->
59	  case HasValue of
60	    0 -> {ok, none};
61	    1 -> {ok, Value} = Fun(Rest),
62		 {ok, {some, Value}}
63	   end;
64	Other -> Other
65      end
66    end.
67
68-spec get_data(binary(), non_neg_integer(), non_neg_integer()) ->
69	{binary(), binary()} | socket_error().
70get_data(Data, Length, Received) when Length > Received ->
71    case Data of
72      <<>> -> connection_closed;
73      _ -> timeout
74    end;
75get_data(Data, Length, _) ->
76    <<Bin:Length/binary, Rest/binary>> = Data,
77    {Bin, Rest}.
78