Analyze This!
I’ve found an edge case for dialyzer. Those things are so rare, I had to write about it.


The Dialyzer vs. Proplists Situation
Check out this module…
As you can see, all 3 functions look pretty similar. The difference lies in the Expansions list. While in good/0 tuples have lists as second elements, in bad/0 we have atoms and in wat/0 we have a mixture of both.
If we compile the module and try to use the functions, they actually work…
2> expand:good().
[expanded,expanded,too,c]
3> expand:bad().
[expanded,expanded,c]
4> expand:wat().
[expanded,expanded,too,c]
5>
But if we run dialyzer on it…
src/expand.erl
13: The call proplists:expand(Expansions::[{'a','expanded'} | {'b','expanded'},...], ListIn::['a' | 'b' | 'c',...]) breaks the contract (Expansions, ListIn) -> ListOut when Expansions :: [{Property::property(),Expansion::[term()]}], ListIn :: [term()], ListOut :: [term()]
Dialyzer here is correctly pointing out that the spec for proplists:expand/2 requires all Expansions to be lists. And in our line 13 (that is bad/0) we’re using atoms, which is wrong. But then… why is Dialyzer not complaining about line 18 (in wat/0) where we’re using atoms as well?
What’s going on here?
I’m not 100% sure of this one (hopefully someone with more knowledge will correct me if I’m wrong), but my best guess is this one:
To check if the calls to proplists:expand/2 are valid, Dialyzer is inferring the type of Expansions in each function and then matching it against proplists:expand/2 specification (i.e. [{property(),[term()]}]).
- In good/0, Dialyzer determines that Expansions :: [{atom(), [atom(),…]}] and since atom() is a subtype of property() and [atom(),…] is a subtype of [term()] we’re all good :)
- In bad/0, Dialyzer determines that Expansions :: [{atom(), atom()}] and since atom() is a subtype of property() but atom() is not a subtype of [term()] we’re not good :(
- Finally, in wat/0, Dialyzer determines that Expansions :: [{atom(), atom()|[atom(),…]}]. Here atom() is a subtype of property() and there are perfectly valid values of type atom()|[atom(),…] that are [term()] (namely, all of the [atom(),…] ones), Dialyzer can’t tell (based on the type information alone) that we’re using one instance that doesn’t work well. So, it doesn’t warn us.
Dialyzer is an amazing tool, but it’s not almighty 🙏
But, on the other hand…
Of course, there is something else to notice here: proplists:expand/2 actually works despite of us using the wrong expansion types, right? So, I’m hereby proposing a change to proplists:expand/2 specification to:
-spec expand(Expansions, ListIn) -> ListOut when
Expansions ::
[{Property :: property(), Expansion :: term() | [term()]}],
ListIn :: [term()],
ListOut :: [term()].
Erlang & Elixir Factory Lite Buenos Aires 2017
Just like last time, I would like to finish this article with a reminder…
The first South American Erlang & Elixir conference (The Erlang & Elixir Factory Lite @ Buenos Aires) is getting closer and closer! As usual, I would like to invite you all to it.


The programme is already online and among other great speakers, you’ll be able to watch Simon Thompson talking about the power of functional programming!
So, all my south-american readers: come join us! It will be awesome!