How to avoid a “can never match” error from Dialyzer in a `with` statement that has a match all `else`?
Asked Answered
C

2

17

I have the following code:

@spec test_pass(String.t) :: (:failed | {:ok, map()})
def test_pass(pass) do
  db_user = %{password_hash: @hash_for_foo}
  with {:ok, ^db_user} <- Comeonin.Argon2.check_pass(db_user, pass) do
    {:ok, db_user}
  else
    _ -> :failed
  end
end

And Dyalizer is giving me the "can never match error":

⟨my_file⟩.ex:25: The pattern {'ok', _} can never match the type {'error',<<_:64,_:_*8>>}

My Question is, why? I know it can't match, and I actually don't care, that's why I'm using with in the first place. All of the non-matching cases are handled in the else.

How can I change my code that dialyzer will be satisfied?

I'm not looking for @dialyzer {:nowarn_function, …}. I already tried it with a {:error, _} -> … expression in the else body but no avail.

Erlang/Elixir version (elixir -v):

Erlang/OTP 20 [erts-9.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:10] [hipe] [kernel-poll:false]

Elixir 1.6.1 (compiled with OTP 19)

Argon.check_pass/2 is external, from Comeonein.

I checked comeonin out, and ran mix dialixier on it and it reported several no local return errors.

Cohin answered 4/4, 2018 at 21:45 Comment(2)
What version of elixir are you using?Assay
updated elixir version outputCohin
S
11

Dialyzer is very user unfriendly. Nevertheless, if it reports an error it means that based on the various type specification you have annotated your functions with, accordingly it is seeing a contradiction with your actual usage.

When Dialyzer is complaining about {'error',<<_:64,_:_*8>>}, it means that Argon2.check_pass has some contradictory type specifications to it, either itself, or either potentially something deeper down. Dialyzer is not very friendly in its ability to point you out exactly where and why contradictions are happening.

As I do not have full access to your code, in order to resolve the issue, at most I can point you out to following a few steps:

  1. If Argon2.check_pass has an explicit @spec annotation, then comment that out and see if Dialyzer still complains.

  2. If the complaint is gone, then modify various parts of @spec annotation with any until the problem is gone for identification purposes. Accordingly the issue will resolve there or either you need to dig deeper in other functions that Argon2.check_pass relies on that may be the cause of the issue.

  3. In the case that 1. fails, then copy paste the function defined Argon2.check_pass as a private function: tmp_check_pass and see how that changes the issue.

4.If need be, you may need to introduce more of these tmp_... functions that Argon2.check_pass is relying on in order to isolate the root cause of the complaint. Before doing that, first try commenting out all the @spec annotations of any of the supporting functions that Argon2.check_pass leverages and apply point 1 accordingly.

Eventually, you will arrive at a particular point in your code, where according to the specifications that you have provided to Dialyzer, that some usage of your code violates it being: of type {'error',<<_:64,_:_*8>>}

The key thought here is to try to isolate the root cause of the complaint, which Dialyzer regretfully is not too precise at pointing out for you from time to time.

Solstice answered 4/4, 2018 at 22:4 Comment(6)
Ah I forgot to put the check_pass function in the question, but a very nice answer. It's actually not mine, it's from external and does not have @spec annotations.Cohin
1. Are you able to reproduce the issue with a minimal repo that loads the external library? 2. If you git clone the library itself and do dialyzer on it, does it still report the same issue?Solstice
…I'm letting it run right now, but generating the plts takes some timeCohin
I'll update the question with the info you requested.Cohin
a minimal gist with the code from aboveCohin
Okay, mabe I misunderstood something that you wrote, I moved the Argon2.check_pass/2 call to a private function which did the same destructuring and gave it an appropriate typing my_check_pass (User.t, String.t) :: ({:ok, User.t} | {:error, String.t}) and but now I got two "never match" errors, one for the private function and one for the public one… It really seems to me like the error is exactly in the with-clause. I also rewrote it into a case… but the same errors occur exactly in the match. Does dialyzer not see that there is another clause for that case?Cohin
D
0

As another question "Dialyzer is usually never wrong", but I can't figure out how my @spec is incorrect said, Dialyzer detected your Comeonin.Argon2.check_pass() call will never return OK, so ...

I've no solution yet, just want to clarify the cause.

Dunstable answered 3/9, 2019 at 3:12 Comment(1)
No it did not the error was somewhere else… Updated the link to point to the function definition I was actually working with at that time (which you also could have checked, by the way the change to only return :error occured in february '19 and my qestion is from '18)Cohin

© 2022 - 2024 — McMap. All rights reserved.