[erlang-questions] Dialyzer underspecs and overspecs options

Jesper Louis Andersen jesper.louis.andersen@REDACTED
Tue Mar 28 17:46:00 CEST 2017

On Fri, Mar 24, 2017 at 11:37 PM Dmitry Kakurin <dima_kakurin@REDACTED>

> Two of them that seem especially useful are *underspecs *and *overspecs*.
> If you think about it, an overspecification is not really a problem with
the code. The dialyzer uses inference analysis and makes a best effort at
determining the type of a given function. Your contract specifies your
usage of the function. When a overspec happens, it is because your contract
is more restrictive than the inference. This is usually not a problem. A
small change in your function body might change the inferred type entirely.
But unless you want to go change your contract all the time, it may be
better to keep it more restrictive. Lots of functions are more general than
their intended use. Another problem is when the dialyzer fails to make a
precise inference, so falls back to type any(). Enabling overspecs will
make the system complain all the time in this situation, but it may not be
a problem.

Underspecs, on the other hand, could spell trouble. Here the inference is
more restrictive, and thus there are inputs to the function which is within
the contract, but won't make the code terminate as expected according to
the inference scheme. If your code never makes use of the extended
contract, there is no problem, but as soon as some user tries to do so,
things will go bad. The dialyzer will usually catch such a use quickly, but
it is nicer to be strict in the contract if possible.

There are, however, situations where underspecs isn't that desirable as a
property either. Suppose you are returning a map, such as #{ a => 37 }. The
dialyzer can infer its success type to be #{ a := pos_integer() }[0]. But
you may have written a specification where you say a map() is returned.
This is more general than the inferred type, and as such it is an underspec
violation. Your contract isn't wrong, but on the other hand, it isn't
precise either. A common problem that also hits a lot are when you are
declaring a binary(), but the dialyzer is able to figure out a more precise
type scheme, for instance <<_:64, _:_*24>>. You will perhaps be more happy
with just saying "this is a binary".

My usual recommendation is to start with the default set of warnings.
Remove every error there, because the slogan is

   The dialyzer is never wrong!

and this is absolutely true. Once you have a dialyzer clean piece of code
with the default warnings, you start turning on underspecs, no_return, and
so on. But often this is more for understanding if there is anything that
sticks out as being too general.

[0] This is strictly not true. If you *always* return 37, then it figures
out the type is exactly the same response every time and tags its type as
#{ a := 37 }. But for this exposition, I'm assuming your code returns "some
positive integer" where 37 is an example.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20170328/ff7d4b87/attachment.htm>

More information about the erlang-questions mailing list