Syntax of types and specs in eqWAlizer
Like Dialyzer, eqWAlizer consumes specs and type aliases written using the Erlang syntax of types and specs.
Types written according to this spec are fully accepted by eqWAlizer. However, since this documentation does not fully describe the semantics of types and specs, some design choices had to be made regarding the interpretation of types during the development of eqWAlizer.
These choices, summarized in this page, are mostly guided by practicality, both from a usability and programming perspective. In short, eqWAlizer tends to focus on clear, non-ambiguous, and intuitive signal. Rarely used, ambiguous, or difficult to implement features are often not supported.
Numeric types
EqWAlizer does not distinguish between the different numeric types.
All numeric types (integer(), float(), integer ranges, byte(), etc)
are all converted into type number() before type-checking.
Maps
Map types in eqWAlizer contain two parts: a set of associations whose keys are
statically-defined singleton types, and a default association. Both are optional:
a map type can contain one or the other, both, or none (in which case it will just be #{}).
The first part is composed of keys that are statically-defined atoms or nested tuples
of such atoms. For example, the atom a, and the tuple {a, {b, c}} are such keys.
Corresponding associations can be marked as optional => or mandatory :=, for example,
#{a => atom(), {a, {b, c}} := binary()}.
The second part is the so-called "default association", which is necessarily marked as
optional, and can contain any key/value combination, e.g. #{atom() => binary()}.
For complexity reasons and to preserve good signal, only one such association is allowed
per map type. The default association will have the least precedence when understanding
a map type. For example, the type #{a => integer(), atom() => binary()} stands for
maps that optionally associate the atom a to an integer, and any other atom to a
binary.
Note that the Erlang syntax allows for some ambiguous map types to be defined.
For example, it is possible to define maps with mandatory associations such as
#{atom() := number()}, in which case it is not clear whether such a map
must contain a mapping for every atomic key, or at least one mapping with
an atomic key. Similarly, one can define maps with multiple overlapping default
associations #{term() => binary(), atom() => atom()}, in which case the precedence
of associations is unclear. To avoid such questions, eqWAlizer
interprets all such maps as the map type #{dynamic() => dynamic()}, issuing
an error in the process.
List types
Much like numeric types, the various kind of list types are only supported in a
limited form by eqWAlizer. Non-empty lists are subsumed to standard lists in
eqWAlizer. That is, nonempty_list(integer()) is equivalent to list(integer()).
Similarly, improper lists are not supported and are simply considered as proper
lists: maybe_improper_list(T1, T2) is the same as list(T1) for eqWAlizer.
Moreover, trying to construct an improper list will, in most contexts, produce
a type error.
However, eqWAlizer still distinguishes the empty list from other lists. The type
[] will be understood by eqWAlizer as being the type of empty lists.
It is, in particular, a subtype of any list type list(T).