Dialyzer warning on default implementation raising (when not overriden)

The following is a snippet depicting my use case:

defmodule Abstract do
  @callback foo() :: binary()

  defmacro __using__( _) do
    quote do
      @behavior Abstract
 
      @impl true
      def foo() do
        raise RuntimeError, "shouldn't have been invoked!"
      end

      defoverridable Abstract
    end
  end
end

defmodule SpecificA do
  use Abstract
end

defmodule SpecificB do
  use Abstract

  @impl true
  def foo() do
    "foo"
  end
end

The problem is dialyzer keeps on showing this warning that foo/0 default implementation in SpecificA “has no local return”. Even if I change the callback return value to binary() | no_return(), it still gives the same warning.

Any ideas on how to circumvent the dialyzer warning without providing a dummy implementation of the foo/0 in SpecificA?

Thanks

I believe this specific situation is the same as what motivates this code in GenServer:

2 Likes

In your case, it is better to not provide a default implantation and either let the compile warn or mark the callback as optional. :slight_smile:

1 Like

@optional_callbacks it is.

Thanks!

The basic reason is that raise generates an error so the function foo never returns. Dialyzer recognises this.

1 Like

What if the foo function has a more serious default implementation? Such as those that exist in ‘GenServer’ for default callbacks implementations(e.g. handle-*)

If the original @DaAnalyst’s question’s code modified as such:

defmodule Abstract do
  @callback foo() :: {:ok, binary()} | {:error, binary()}
  # EDIT: Even by marking it as optional, Dialyzer complains
  @optional_callbacks foo: 0

  defmacro __using__(_) do
    quote do
      @behaviour Abstract

      @impl true
      def foo() do
        {:ok, "Evrything is good:~"}
      end

      def bar() do
        case foo() do
          {:ok, message} -> IO.puts(message)
          {:error, message} -> IO.puts(message)
        end
      end

      defoverridable foo: 0
    end
  end
end

defmodule SpecificA do
  use Abstract
end

Dialyzer complains:

ElixirLS Dialyzer: The pattern 
          {'error', _@2} can never match the type 
          {'ok', <<_:152>>}

How to say/trick Dialyzer to not complain?

Applying the trick mentioned by @al2o3cr,

Dialyzer does not complain anymore:

  defmodule Abstract do
    @callback foo() :: {:ok, binary()} | {:error, binary()}

    defmacro __using__(_) do
      quote do
        @behaviour Abstract

        @impl true
        def foo() do
          case :erlang.phash2(1, 1) do
            0 ->
              {:ok, "Evrything is good:~"}

            1 ->
              {:error, "This is not possible to execute"}
          end
        end

        def bar() do
          case foo() do
            {:ok, message} -> IO.puts(message)
            {:error, message} -> IO.puts(message)
          end
        end

        defoverridable foo: 0
      end
    end
  end

  defmodule SpecificA do
    use Abstract
  end

Thank you