[erlang-questions] Dialyzer questions
Kostis Sagonas
kostis@REDACTED
Wed May 4 01:01:50 CEST 2011
Prashanth Mundkur wrote:
> We've recently been getting the upcoming release of Disco to go through
> the Dialyzer wringer, and it has been a very useful exercise. Thanks to
> Kostis' helpful wielding of the cluebat at the SF Erlang Factory!
>
> We are trying to understand the following output (I hope the inline code
> pasting below comes out readable):
You already figured out some answers to your questions, but let me also
reply to them for completeness and hopefully also for more explanations.
> $ cat dial.erl
> -module(dial).
> -export([abort/2, extract/2,
> diskspace/1, monitor_diskspace/2, refresh_tags/1,
> monitor_launch/0]).
>
> % We have helper functions like this:
> abort(Msg, Code) ->
> error_logger:warning_report(Msg),
> exit(Code).
> % which give rise to the following dialyzer message:
> %
> % Function abort/2 only terminates with explicit exception
> % How do we interpret this message?
First of all note that you do not get this warning by default. You only
get this warning because you explicitly enabled the -Werror_handling
option. I quote from the manual:
-Werror_handling ***
Include warnings for functions
that only return by means of an exception.
You can suppress this warning by adding a spec of the form:
-spec abort(some_type(), some_other_type()) -> no_return().
for this function, which will notify dialyzer that it is indeed your
intention that this function will never return a value
> % Even with a plt that includes stdlib, there is no warning for the
> % following code:
> -spec extract(binary(), nonempty_string()) -> 'ok'.
> extract(Pack, Dir) ->
> case zip:extract(Pack, [{cwd, Dir}]) of
> {ok, Files} ->
> Files;
> {error, Reason} ->
> exit({error, Reason})
> end.
This happens because module 'zip' does not have -spec information about
its functions. Without such information, dialyzer's analysis infers
that zip:extract/2 will either return something of the form {ok, term()}
or {error, term()}. Note that the only thing that dialyzer can infer
given this information is that Files will be some term(). Your spec
promises is that this term is 'ok'. In such cases, where dialyzer
cannot find any reason not to trust you, it keeps its mouth shut about
it. (Unless you use option -Woverspecs in this case.)
Now, it's actually the case that I have a spec-ed version of zip.erl
which has not made it into OTP yet and in that version I get the
following warning in your code:
Invalid type specification for function dial:extract/2.
The success typing is (binary() | string(),binary() | string()) ->
[binary() | string() | {binary() | string(),binary()}]
I will see whether this version can be included as in OTP or not...
> % Here, the spawn_link of monitor_diskspace in monitor_launch() gives:
> % The created fun has no local return
> % whereas the spawn_link of refresh_tags doesn't. If the spawn_link
> % of monitor_diskspace() function is commented out, there is no
> % warning. It's not clear how to interpret the warning or the
> % inconsistency.
> %
> % Converting the nested list comprehension in monitor_diskspace into a
> % list:map made the warning go away, at the expense of readability and
> % perhaps even efficiency.
You are right. There is some inconsistency in the behavior of the two
cases. But note that the funs that are created have no return -- they
both call functions that go into an infinite loop and actually they are
explicitly spec-ed as no returning. So the warning you get is correct.
Still, you would like the two cases to be consistent and ideally also
not get this warning, if possible. (So that you do not have to resort to
calls outside this module as e.g. using lists:map/2.)
We spend some time with Stavros today looking into this and Stavros has
come up with a way of making sure the treatment of these two cases is
consistent and the one you want. When such specs are in place for
functions that go into infinite loops, the warnings for uses of them in
funs are suppressed.
Stay tuned and Stavros' patch will soon find its place to an OTP version
near you!
Hope this explains all issues,
Kostis
> -type diskinfo() :: {non_neg_integer(), non_neg_integer()}.
> -spec diskspace(nonempty_string()) -> {'ok', diskinfo()} | {'error',
> term()}.
> diskspace(Path) ->
> case Path of
> "a" -> {ok, {0,0}};
> _ -> {error, error}
> end.
>
> -spec monitor_diskspace(nonempty_string(),
> [{diskinfo(), nonempty_string()}]) ->
> no_return().
> monitor_diskspace(Root, Vols) ->
> Df = fun(VolName) ->
> diskspace(filename:join([Root, VolName]))
> end,
> NewVols = [{Space, VolName}
> || {VolName, {ok, Space}}
> <- [{VolName, Df(VolName)}
> || {_OldSpace, VolName} <- Vols]],
> monitor_diskspace(Root, NewVols).
>
> -spec refresh_tags(nonempty_string()) ->
> no_return().
> refresh_tags(Root) ->
> {ok, _} = diskspace(Root),
> refresh_tags(Root).
>
> monitor_launch() ->
> spawn_link(fun() -> refresh_tags("abc") end),
> spawn_link(fun() -> monitor_diskspace("root", [{{0,0}, "a"}]) end).
>
> $ dialyzer --get_warnings -Wunmatched_returns -Werror_handling dial.erl
> Checking whether the PLT /home/mundkur/.dialyzer_plt is up-to-date... yes
> Proceeding with analysis...
> dial.erl:6: Function abort/2 only terminates with explicit exception
> dial.erl:66: The created fun has no local return
More information about the erlang-questions
mailing list