Here is some code with an error which I think Dialyzer should be able to spot:
-module(myapp_thing).
-spec exists(pos_integer()) -> yes | no.
exists(Id) ->
myapp_mnesia:thing_exists(Id).
-module(myapp_mnesia).
thing_exists(Id) ->
Exists = fun() ->
case mnesia:read({thing, Id}) of
[] -> false;
_ -> true
end
end,
mnesia:activity(transaction, Exists).
myapp_thing:exists/1
is specified as returning yes | no
, but the return type will actually be true | false
(i.e., boolean()
), which is what is returned from myapp_mnesia:thing_exists/1
.
However, running Dialyzer on myapp passes it without warnings.
If I change myapp_mnesia:thing_exists/1
to just return true
I get an appropriate warning; similarly if I add the right spec:
-spec session_exists(pos_integer()) -> boolean().
But it looks like Dialyzer can't look inside the mnesia transaction function Exists, or for some other reason can't infer a return type for thing_exists.
So, are mnesia transaction functions a barrier for Dialyzer, or is there a more general barrier to Dialyzer's return type inference?
In mnesia_tm:execute_transaction
, the provided fun is being called inside a catch
, which means that the return type collapses into term()
as far as Dialyzer is concerned. Thus Dialyzer is unable to conclude that the return type of mnesia:activity/2
is the same as that of the provided function, and thus needs an explicit type spec to that effect.
Besides, I believe that Dialyzer generally never infers a return value type based on the return value of a function provided as an argument. For example, with this module:
-module(foo).
-export([foo/1]).
foo(F) ->
F(42).
typer shows the following:
$ typer /tmp/foo.erl
%% File: "/tmp/foo.erl"
%% --------------------
-spec foo(fun((_) -> any())) -> any().
though if I add the explicit type spec -spec foo(fun((_) -> X)) -> X.
, then typer accepts it.
(I'm sure the Dialyzer developers will have a more complete and insightful answer to this.)