"Dialyzer is usually never wrong", but I can't figure out how my @spec is incorrect

loc*_*red 1 elixir dialyzer

I have some code that is failing dialyzer and I cannot understand why. No matter what I put into the @spec at the top of the function, calls to that function return a puzzling dialyzer error. Here is a simplification of the function. As far as I can tell, I have spec'd the function correctly.

@spec balances(uuid :: String.t(), retries :: non_neg_integer) ::
        {:ok, list()}
        | {:internal_server_error, String.t(), String.t()}
        | {:internal_server_error, map | list, String.t()}
def balances(uuid, retries \\ 0) do
  url = "/url/for/balances" |> process_url

  case HTTPoison.get(
         url,
         [, {"Content-Type", "application/json"}],
         []
       ) do
    {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
      response = Poison.decode!(body, as: %{"message" => [%Currency{}]})

      cond response["message"] do
        length(bal) > 0 ->
          {:ok, bal}

        retries >= 1 ->
          {:ok, []}

        true ->
          init(uuid)
          balances(uuid, retries + 1)
      end

    {:error, %HTTPoison.Error{reason: reason}} ->
      Notifier.notify(url, reason, Helpers.line_info(__ENV__))
      {:internal_server_error, reason, url}

    {_, %HTTPoison.Response{body: body} = res} ->
      response = Poison.decode!(body)
      Notifier.notify(url, response, Helpers.line_info(__ENV__))

      {:internal_server_error, response, url}
  end
end
Run Code Online (Sandbox Code Playgroud)

My issue is that every call across the codebase to this function is failing if I expect to get anything other than {:ok, balances}:

  user_balances =
    case balances(uuid) do
      {:ok, user_balances} -> user_balances
      _ -> [] # Dialyzer error here
    end
Run Code Online (Sandbox Code Playgroud)

Dialyzer warns that The variable _ can never match since previous clauses completely covered the type {'ok',[map()]}. I read this to mean that any call to balances will always return {:ok, balances}, but that can't be true as the case statement for HTTPoison.get is the last thing evaluated in the function, and it appears to have only three possible results:

  • {:ok, list}
  • {:internal_server_error, String.t(), String.t()}
  • {:internal_server_error, map | list, String.t()}.

I understand that I am likely missing something very obvious but I can't figure out what it is. Any help would be greatly appreciated. Thank You!

loc*_*red 5

感谢@legoscia 的评论,我调查了对 的调用Notifier.notify,果然在那个函数中也有一个透析器警告(我有 PR 到一个开源项目来修复导致通知函数使透析器失败的规范)。如果我修改通知功能,以便不发生警告,那么调用肯定balances不再产生透析器警告。

tl;dr 如果dialyzer 向您发出警告,指出某个函数似乎未正确指定,请开始检查函数内的函数调用以查找下游透析器错误。