Pattern can never match the type

Getting the following error:

lib/teiserver_web/controllers/api/spads_controller.ex:221:pattern_match
The pattern can never match the type.

Pattern:
nil

Type:
[any()] | integer()

My code that is throwing the error looks like this:

  defp get_member_lobby(nil), do: nil

  @spec get_member_lobby(non_neg_integer()) :: T.lobby() | nil
  defp get_member_lobby(userid) do
    case Account.get_client_by_id(userid) do
      nil ->
        nil

      client ->
        Battle.get_lobby(client.lobby_id)
    end
  end

Is there something wrong with it?

If I delete this line, the error goes away:

  defp get_member_lobby(nil), do: nil

The dialyzer message is saying that the userid parameter can never be nil when it’s passed to get_member_lobby/1. And by looking at the spec you have:

@spec get_member_lobby(non_neg_integer()) :: T.lobby() | nil

This spec is also saying that the parameter is always non_neg_integer, which is never nil.

Two other small notes:

  1. @spec always applies to the overall function/arity, so the spec you have applies to both function clauses.
  2. Dialyzer deduces the parameter types, it doesn’t rely upon specs. But in this case, your own spec agrees with dialyzer.
1 Like

I tried this

  @spec get_member_lobby(non_neg_integer() | nil) :: T.lobby() | nil
  defp get_member_lobby(nil), do: nil

  defp get_member_lobby(userid) do
    case Account.get_client_by_id(userid) do
      nil ->
        nil

      client ->
        Battle.get_lobby(client.lobby_id)
    end
  end

And still get the same error

The lexical ordering doesn’t matter so I would expect the same message. Dialyzer cannot find any situation where userid can be nil. Dialzyer doesn’t rely upon specs, it deduces internally a type contracts.

Basically, it is saying that the clause:

defp get_member_lobby(nil), do: nil

will never be called.

Interesting. So if I add this line of code

get_member_lobby(nil)

to another function, then the error goes away.

Yes, I would expect so - although I didn’t try it.

It took me (and not only me it seems) a long time to get my head to think like dialyzer. Sometimes I think specs aren’t worth it. But they, at minimum, provide documentation for developers and maintainers that is useful. And sometimes they help surface bugs - but not as often as I originally expected many years ago.

1 Like

So in this situation the correct move would be to delete this line of code?

defp get_member_lobby(nil), do: nil

I think the thing to realise is that the code is the truth! The compiler completely ignores the typing and specs when compiling so you can quite happily define nonsense types and specs. But don’t :wink: Dialyzer does the same. It checks the functions to try and work what types of data are input and returned then checks the callers of the function to see if they do the right thing. It is based on this that it complains. It can use defined types and specs when reporting type errors it finds and sometimes when complaining they don’t match the code. But the code is the truth.

The reason for this is that typing, specs and dialyzer came long after Erlang had been defined and had been in use so a hard requirement was that all old code should still work as before.

3 Likes

In this situation nothing is calling this code:

defp get_member_lobby(nil), do: nil

and so dialyzer is complaining (it’s part of our CI). So I assume the correct call is to just delete it?

If in the future someone does this:

result = get_member_lobby(nil)

Since I deleted the function that handles nil then this is the only one left:

  @spec get_member_lobby(non_neg_integer()) :: T.lobby() | nil
  defp get_member_lobby(userid) do

And so dialyzer will complain again and alert me that I need to handle the nil case. So I’m assuming deleting the unused function is the correct call?

Yes, if you want to keep your code clean and the CI happy.

1 Like

I would leave the nil clause and either ignore the dialyzer warning or comment out the clause to show that you are at least prepared to handle it if necessary. IIRC there is a way to make dialyzer keep quite about things it finds in specific functions.

1 Like