Dialyzer cachex unable to solve types

Hello all,

I’m triing to integrate dialyzer/dialyxer into one project that is using cachex, but dialyzer keeps reporting some veird error with Cachex.start_link/2 function. I managed to destille minimal example:

defmodule Test do
  def hello do
    {:ok, _pid} = Cachex.start_link(__MODULE__, stats: true)
  end
end

Dialyzer is complaining that

lib/test.ex:3:pattern_match
The pattern can never match the type.

Pattern:
{:ok, __pid}

Type:
:error | :ok | {:error, atom() | {:already_started, pid() | {_, _}}}

But when I tri to run function in iex, it returns {:ok, pid()}, also when I look at the code, typescpecs seems right and code also, so, how did dialyzer come up with return type :error | :ok | {:error, atom() | {:already_started, pid() | {_, _}}}? The code for Cachex.start_link/2 looks like this:

  @spec start_link(atom | Keyword.t) :: { atom, pid }
  def start_link(options) when is_list(options) do
    with { :ok,  name } <- Keyword.fetch(options, :name),
         { :ok,  true } <- ensure_started(),
         { :ok,  true } <- ensure_unused(name),
         { :ok, cache } <- setup_env(name, options),
         { :ok,   pid }  = Supervisor.start_link(__MODULE__, cache, [ name: name ]),
         { :ok,  link }  = Informant.link(cache),
                ^link   <- Overseer.update(name, link),
     do: { :ok,   pid }
  end
  def start_link(name) when not is_atom(name),
   do: error(:invalid_name)
  def start_link(name),
    do: start_link(name: name)

  @doc false
  @spec start_link(atom(), Keyword.t) :: { atom(), pid() }
  def start_link(name, options),
    do: start_link([name: name] ++ options)

Anybody have experience with similar problem and how to solve it?

if Cachex.start_link/1 returns anything besides {:ok, pid}, (for example :error, from one of the with block fallthroughs), then your hello function will crash due to match error, dialyzer is trying to tell you this.

This seems to be a bug in specs in the sleeplocks library and should be fixed by https://github.com/whitfin/sleeplocks/pull/5

2 Likes

Which is fine, dialyzer assumes that it is exactly what the programer wants, in spirit of let-it-crash. It also is not what dialyzer complaints about here.

The error message of dialyzer says, that there will never be a {:ok, _} value returned, as dialyzer only knows about the possibilities :ok, :error, and an :error-tuple`

Thank you, when I override sleeplock dependenci in mix.exs, it started to work

It’s possible that Cachex also has wrong specs, at least I think in many modules types referenced from specs won’t be found by Dialyzer, e.g. here: https://github.com/whitfin/cachex/blob/master/lib/cachex/services/overseer.ex#L62 Spec is used but Supervisor.Spec is aliased, not Cachex.Spec. But I may be wrong, and in that case Dialyzer is probably smart enough to ignore the spec.

Overall this is a common problem through the Elixir ecosystem, almost none of the libraries are running Dialyzer on the CI so specs just get outdated or invalid. I try to maintain Elixir itself working somehow with Dialyzer by running a check on it every day: https://github.com/michallepicki/elixir-lang-dialyzer-runs but it won’t catch some issues because they may pop up when a function is called, not defined, and I am not analyzing Elixir test modules since they are .exs files.