Dialyzer (Dialyxer) usage in documentation release.ex example

I am trying to learn how dialyzer works. I am using an example from the documentation for ecto migrations found here: https://hexdocs.pm/phoenix/releases.html#ecto-migrations-and-custom-commands

specifically:

defmodule MyApp.Release do
  @app :my_app

  def migrate do
    for repo <- repos() do
      {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
    end
  end

  def rollback(repo, version) do
    {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))
  end

  defp repos do
    Application.load(@app)
    Application.fetch_env!(@app, :ecto_repos)
  end
end

I ran dialyzer and got this output:

lib/my_app/release.ex:17:unmatched_return
The expression produces a value of type:

:ok | {:error, _}

but this value is unmatched.

I am also using ElixirLS in VSCodium (VSCode) and it suggests these types:

defmodule MyApp.Release do
  @app :my_app

  # here
  @spec migrate :: [any]
  def migrate do
    for repo <- repos() do
      {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
    end
  end

  # here
  @spec rollback(atom, any) :: {:ok, any, any}
  def rollback(repo, version) do
    {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))
  end

  # origin of warning
  # no type suggested
  defp repos do
    Application.load(@app)
    Application.fetch_env!(@app, :ecto_repos)
  end
end

I have two questions.

  1. Can the suggested @spec's be improved or are these suggestions ideal? For example, the rollback function takes a version which was typed to any but wouldn’t it be better if I type it to an integer?

  2. The repos function warning can be silenced by using the _ = return pattern on both lines. I’m wondering if there is a type I can apply to that function that would silence it?

Perhaps I should ask, how would you type these functions in order to work with dialyzer?

Thank you

Dialyzer ist just telling you, that you shouldn’t ignore the return value of Application.load/1. If you want to ignore it rather than handle it, you have to tell it to dialyzer by _ = as you already found out.

1 Like

I see. What’s the idiomatic way to handle this?

  defp repos do
    case Application.load(@app) do
      :ok -> Application.fetch_env!(@app, :ecto_repos)
     _ -> IO.puts "ooops"
    end
  end

Like that?

How would you type this method? Sorry, it’s just not clear which pattern to apply and how to type the methods.

Also, why is Dialyzer warning about Application.load and not Application.fetch_env!? I’d imagine I’d have to match on both responses.

Nope. Dialyzer tells that Ecto.Migrator.with_repo/3 might return {:error, _} tuple that is unmatched in the code above.

Doesn’t it just want a match on something/anything?

  defp repos do
    case Application.load(@app) do
      :ok -> Application.fetch_env!(@app, :ecto_repos)
     _ -> IO.puts "ooops"
    end
  end

How would you type this method? How would you match to appease Dialyzer?

As I said, Dialyzer complains about the calls to Ecto.Migrator.with_repo/3. It has nothing to do with Application.load/1. You are free to discard returning values everywhere.

The easiest fix would be to no match at all:

defmodule MyApp.Release do
  @app :my_app

  @spec migrate :: [{:ok, any(), any()} | {:error, any()}]
  def migrate do
    for repo <- repos(),
      do: Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
  end

  @spec rollback(atom(), any()) :: {:ok, any(), any()} | {:error, any()}
  def rollback(repo, version),
    do: Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))

  ...
end

https://hexdocs.pm/elixir/master/typespecs.html#content

But that’s far away from line 17… Or did I hopelessly miscounted the lines?

Anyway the principle remains the same, dialyzer wants you to either deal with returned values or to ignore them explicitly.