TIL: Understanding dialyzer’s “The pattern can never match the type.”

Lasse Ebert
Sep 3 · 3 min read
Dialyzer warning

I like dialyzer and typespecs. They are a natural part of my TDD flow:

  • Write a failing test
  • Write docs for function
  • Write typespecs for function
  • Write the function
  • See that test is now green
  • Refactor
  • See that test is still green

Having written tests, docs and a spec for a function, makes it much more clear what the function should accept as arguments and what it should return. I also tend to write more reusable functions with this flow.

The typespecs will also function as documentation when reading the function.

But dialyzer has a downside. Debugging dialyzer warnings can be a real pain and it is often not clear where to look for a wrong typespec or whatever made dialyzer vomit all over my terminal with less-than-obvious messages.

I spend a good three hours today debugging a dialyzer warning in my Elixir app, so I’m naturally obligated to share my insights with the world :)

The setup

I got an error in a with statement much like this one in function c/0:

The dializer warning says:

This is a pretty light-weight warning compared to dialyzer standards. Normally I need to copy-paste the message into an editor to format it and be able to make a little sense out of it.

So apparently :error does not match :another_error, which is obvious, but why complain about it? When I wrote the code, I called b/0 in a way that shouldn’t make it return anything else than :ok, so I would like a runtime exception if anything else is returned from that function.

My mistake was that I considered the above example the same as this one:

Notice that we only match on :ok and :error, but not on :another_error. This code does not produce a dialyzer warning.

What the hell is going on?

After a long time of debugging and trying to find a wrong typespec, I realized how dialyzer handles with: For each statement in the with (all the lines with a <- b), we can think of it as “b must either match a or must match at least one clause in else”.

I tried digging deeper in the elixir source code to get a confirmation of this, but with is a special form and implemented in dark magic, so I got no further.

Solution?

Yes, there is an easy solution. If you don’t want the statement in the with to match anything in else, don’t include the statement in the with. You can include it before, after or inside do, whateever fits the use case.

In my small example from the top, I would simply rewrite, so that the call to b/0 was moved inside the do and matched with the expected return value:

And since I now only have a single statement in the with, I would probably refactor to a case:

Again, dialyzer was right. It does not make sense to have a statement inside a with if I’m never going to match a clause in else with it. My code is now easier to read, since it is obvious to the reader that b/0 _must_ return :ok.

Lasse Ebert

Written by

Developer, Elixir

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade