https://www.erlang.org/doc/reference_manual/typespec.html
making a PLT for all of OTP
The shipped applications are in versioned subdirectories of code:lib_dir():
$ dialyzer --build_plt --apps $(erl -noshell -eval '{ok, A0} = file:list_dir(code:lib_dir()), A1 = lists:map(fun (App) -> hd(string:split(App, "-")) end, A0), lists:foreach(fun (App) -> io:fwrite("~ts\n", [App]) end, A1), halt().')
using types from other modules
Prefix the type name, similarly to a function call. E.g., calendar:date()
export a function to keep dialyzer from narrowing its type to its actual usage
Consider:
-module(e5).
-export([main/0]).
-record(day, {
date :: calendar:date() | undefined,
goal :: number() | undefined,
score :: number()
}).
initialized(#day{date = undefined}) -> false;
initialized(#day{goal = undefined}) -> false;
initialized(_) -> true.
main() ->
erlang:display(initialized(#day{score = 2})).
Dialyzer will complain that the second pattern of initialized/1 can never match:
e5.erl:10:1: The pattern
{'day', _, 'undefined', _} can never match since previous clauses completely covered the type
#day{score :: 2}
e5.erl:11:1: The variable _ can never match since previous clauses completely covered the type
#day{score :: 2}
But note the type, #day{score :: 2}. Dialyzer has narrowed the type to its single use. When this function is exported dialyzer has no more complaints.
Of course, it's reasonable to not want to pollute a module's interface for such a reason. An easy alternative to add tests:
-include_lib("eunit/include/eunit.hrl").
initialized_test() ->
false = initialized(#day{score = 0}),
false = initialized(#day{date = {2024, 5, 16}, score = 0}),
true = initialized(#day{date = {2024, 5, 16}, goal = 10, score = 0}).