4

When Dialyzer encounters a record literal where a required field is not initialized, it thinks control flow stops at the line with the record literal.

Example:

-module(sample).
-export([foo/0]).
-record(boo, {a :: number()}).
foo() ->
    erlang:display(#boo{}).

Errors:

13> dialyzer:run([{files, ["/Users/mheiber/sample.erl"]}, {from, src_code}]).
[{warn_return_no_exit,
     {"/Users/mheiber/sample.erl",11},
     {no_return,[only_normal,foo,0]}},
 {warn_matching,
     {"/Users/mheiber/sample.erl",12},
     {record_constr,
         ["#boo{a::'undefined'}","a::number()"]}}]

Is this a bug? The runtime semantics of Erlang do not match how Dialyzer is modeling them: ERTS (for better or worse!) chugs along, happily assigning the atom 'undefined' to to any unitialized fields.

Clarification: what I mean here is that it's preferable, where feasible, for the static checking to reflect how Erlang works at run time.

So is this a Dialyzer bug?

The way Dialyzer handles these incorrectly-initialized records is pernicious, because it can trigger a cascade of spurious warnings–when Dialyzer thinks a line of function foo is unreachable, any functions that are reachable only from foo are also considered dead.

Max Heiber
  • 14,346
  • 12
  • 59
  • 97

1 Answers1

5

No, it's not a bug.

I'd say that it's a limitation caused by the fact that Erlang is dynamically typed and that the -type directives are not used in runtime.

Dialyzer is built on top of ERTS, not the other way around.

The problem in this case is that dialyzer does not know how to continue its execution: Should it use the type defined in the record definition or the actual record initialization? It reports an error and the actual fix is left to the programmer.

José M
  • 3,294
  • 1
  • 14
  • 16
  • 1
    Wouldn't it be more useful and in accordance with the "success typings" approach to report the error, then continue with the union of the declared types and discovered types? – Max Heiber Dec 23 '20 at 17:00
  • 2
    @MaxHeiber that approach has some issues too: All the analysis up to that point was based on the original type: should it be thrown out and restarted? How can dyalizer know if the error was due to the missing `| undefined` in the definition (which would be fixed by the union) or the missing `= X` in the initialization (which the union could only hide other errors)? IMHO once you have an error, anything further down that path is to be taken with a grain of salt, so I don't think implementing this kind of union is worth it – José M Dec 26 '20 at 16:31