Dialyzer says "The pattern false can never match the type true"

I’ve gotten my (10 KLOC) project down to a single Dialyzer error, but I can’t figure this one out. Suggestions?

See this gist for the code.

2 Likes

What happens if You comment those lines?

      not(is_map(payload))  ->
        IO.puts "\nIgnored: " <> trim_path
        IO.puts "Because: Payload is not a Map."
      %{}

You do mention this…

The input tuple is emitted by Toml.decode/2

So it seems payload will always be a map (or an error, but that is handled before in your condition) from Toml.decode spec

decode(bin, opts \\ [])
decode(binary(), opts()) :: {:ok, map()} | error()

not(is_map(payload)) might never get triggered?

As it happens, I had tried that. The result was that it complained about the last clause, instead of the second clause. FYI, the project is available at https://github.com/RichMorin/PA_all.

On a vaguely related note, I just realized that functions defined in my test files (eg, foo_test.exs) aren’t being checked by Dialyzer. Is there a Best Practice for getting this to happen?

Test files are just plain elixir scripts, which are executed on demand by the test runner. The only thing different to arbitrary elixir scripts is their folder + naming schema (*_test.exs). They‘re not part of your compiled codebase even when running with MIX_ENV=test, which is why dialyzer doesn‘t act on them.

1 Like

An addition. If you want to validate code you’re using just in tests modify :elixirrc_paths in your mix.exs (e.g. like phoenix does it) and put the to be checked code in .ex files of test/support. Running dialyzer with the proper mix env should result in that code being checked.

Can confirm this is the blessed way to dialyze your test logic. It unfortunately needs to be in .ex files but most of the time that is sufficient in my experience.

My test support function contains assert\1 calls. How should I make these available to it?

You would move actual test helper logic to the .ex files and the asserts etc would remain in the .exs files.

The support function is a sequence of assert/1 calls. Owell.

In case anyone has a clue to offer; I still need one for the original Dialyzer issue.

-r

From what I’ve got, the only place payload is not a map is when the first element of the tuple is :error (e.g. {:error, "File string contains annoying codepoints."}).

Then dialyzer is telling that not(is_map(payload)) will never match, as Toml.decode/1 returns {:ok, map}.

As noted above, I tried commenting out the not(is_map(payload)) clause and it just fails on the Enum.empty?(payload) clause. So something else is borked…

I rewrote it as this:

  @spec filter({atom, any}, String.t()) :: map
  defp filter({:error, payload}, trim_path) do
    IO.puts("\nIgnored: " <> trim_path)
    IO.puts("Because: " <> inspect(payload))
    %{}
  end

  defp filter({_status, payload}, trim_path) do
    payload
  end

And dialyzer still failed:

apps/info_toml/lib/info_toml/parser.ex:86:pattern_match_cov
The pattern
{__status, _payload}, _trim_path

can never match since previous clauses completely cover the type
{:error, binary()}, binary()

This means dialyzer considers it will always return {:error, "..."}, so never succeed with an {:ok, map}. :face_with_monocle:

1 Like

Then it’s probably a bug in the toml library. If someone runs dialyzer there is it without warnings or errors?

In an effort to follow your advice, I extracted the module into a separate Mix project and ran Dialyzer on it. This reproduced the problem, so I started trimming out various parts of my code.

Although I haven’t found the bug yet, it appears to be in my own code, rather than the Toml library. I’ll report back once I know more…

After trying a few versions, I discovered bugs in a couple of @spec lines, further down in my code. For example:

@spec parse_h1(s, atom) :: map|{atom, s}
  when s: String.t

should have been:

@spec parse_h1(s, atom) :: {atom, map|s}
  when s: String.t

I think the main lessons from this are that

  • Dialyzer errors can be quite misleading about specifics.
  • Small code bases simplify testing and reduce confusion.
  • Divide and conquer is (as always) a powerful technique.

Anyway, thanks to everyone for your help!

3 Likes