I've noticed that Dialyzer possibly simplifies long union types, at least when it comes to atoms.
For example, if I have:
defmodule Foo do
@type name() :: :one | :two | :three
@spec bar(name()) :: String.t()
def bar(name), do: to_string(name)
end
and then I call the function with
Foo.bar(:hello)
Dialyzer will catch the error saying that :hello
is a wrong type for this function.
But then, if I change the type declaration to make the union consist of more than 13 (my observation) items (pseudo code):
@type name() :: :one | :two | :three ... :fourteen
Dialyzer seems to convert it into a simple atom()
type under the hood and would not complain about calling Foo.bar/1
with any atom, even if it's not included into the union.
So I wonder if that's expected behaviour and whether there's a way to prevent that?
I couldn't find a mention of it in the documentation, but this behavior is mentioned in the Success Typing paper explaining dialyzer:
We allow for any disjoint union, including unions of singleton types such as 1 ∪ 2. Since these unions can become large or even infinite, in our analysis we impose a fixed size limit after which the union is widened to a supertype. For example, if the union limit is three the union type 1 ∪ 2 ∪ 3 ∪ 4 will be widened to integer ().
Unfortunately, there doesn't seem to be any option to change this limit as of today.