[erlang-questions] Temporarily violating record type constraints annoys dialyzer

Dániel Szoboszlay dszoboszlay@REDACTED
Fri Dec 7 12:34:52 CET 2018


Hi,

I'm a bit late to this party, but I have a suggestion I haven't seen coming
up in the thread. So maybe it could be still useful for you or someone else:

Consider moving type specs out from your record definition completely! Use
a custom type instead:

-record(widget, {id, name, size}).
-type widget() :: #widget{id :: binary(), name :: binary(), size ::
binary()}.

This way you can explicitly tell in the function specs whether you're
dealing with a properly typed widget() or not. You may even define a
different type for the parser, and have Dialyzer check against that:

-type partially_parsed_widget() :: #widget{id :: binary()|undefined, name
:: binary()|undefined, size :: binary()|undefined}.
-spec parse_widget(proplists:proplist()) -> widget().
-spec parse_widget(proplists:proplist(), partially_parsed_widget()) ->
widget().

There are lots of cases when you want to use a record's structure with
different type constraints than the "usual" use of that record. Generating
property based tests and negative tests were already mentioned. But using
records in match specifications is also very common. And in case of some
simple records you may also use tuples that are not meant to be a record,
but look like one (consider someone defining -record(error, {err_no ::
integer(), msg :: string()}). for example).

An additional benefit of separating record definitions from type specs is
that Dialyzer would actually generate much more useful error messages on
type violations. With types in records you may get something like this:

foo.erl:10: Function test/0 has no local return
foo.erl:11: Record construction #foo{bar::0} violates the declared type of
field bar::pos_integer()

The problem is that the "has no local return" error is poisonous: it will
propagate to all other functions calling foo:test/0, and from there to
their callers etc. You'll get a ton of error messages and it will be very
hard to figure out the root cause.

On the other hand when using a separate foo() type the error message would
become:

foo.erl:7: Invalid type specification for function foo:test/0. The success
typing is () -> #foo{bar::0}

This error will not propagate and will be easy to debug.

Just my two cents. Cheers,

Daniel

On Mon, 12 Nov 2018 at 11:58 Roger Lipscombe <roger@REDACTED> wrote:

> I've got a record defined as follows (e.g., and very simplified):
>
> -record widget {
>     id :: binary(),
>     name :: binary(),
>     size :: integer()
> }.
>
> I parse that from (e.g.) a proplist:
>
> parse_widget(Props) ->
>     parse_widget(Props, #widget{}).
>
> parse_widget([{name, Name} | Rest], Acc) ->
>     parse_widget(Rest, Acc#widget { name = Name });
> % etc.
>
> Dialyzer isn't happy that my fields are initially set to 'undefined',
> even though this only occurs during the parsing step, and isn't a big
> deal.
>
> What can I do to deal with this? Either re-structuring my code or
> persuading dialyzer that it's OK would both be acceptable.
> _______________________________________________
> erlang-questions mailing list
> erlang-questions@REDACTED
> http://erlang.org/mailman/listinfo/erlang-questions
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20181207/97575022/attachment.htm>


More information about the erlang-questions mailing list