Dialyzer detects false errors with Supervisor.on_start

Background

I am trying to add specs to all my public functions, but dialyzer gives me warnings on Supervisor.start_link/2 that according to the docs should not happen.

Code and Error

  @spec start(any, any) :: Supervisor.on_start
  def start(_type, _args) do
    children = [ ]

    opts = [strategy: :one_for_one, name: IslandsEngine.Supervisor]
    Supervisor.start_link(children, opts)
  end

lib/islands_engine/application.ex:6: The return type ‘ignore’ in the specification of start/2 is not a subtype of {‘error’,} | {‘ok’,pid()} | {‘ok’,pid(),}, which is the expected return type for the callback of the ‘Elixir.Application’ behaviour

Documentation

The documentation for Supervisor.start_link/2 clearly specifies there is an :ignore type:

https://hexdocs.pm/elixir/Supervisor.html#t:on_start/0

Question

What am I doing wrong?

Function start/2 in Application do not return Supervisor.on_start/0 type.

This links to the function Application.start/2 not the callback: https://hexdocs.pm/elixir/Application.html#c:start/2

@Fl4m3Ph03n1x
Supervisor.on_start includes :ignore, which is not allowed for the callback of the Application behaviour.

Could you elaborate?
In Elixir the return of a function is the return of its last statement. Since Supervisor.start_link returns what I have shown above, it stands to reason that start will do it to.

Unless you are telling me that my start applications should not terminate with Supervisor.start_link, I don’t know how else to solve this.

Supervisor.start_link can return :ignore, but a supervisor never does so without you implementing it to do that. So unless you’re purposefully starting a root supervisor, which can return :ignore in its init/1 callback this is totally fine. If you really want to you can wrap it in a pattern match, which only allows {:ok, pid} | {:error, term} to actually return. This will crash the Application just as much as currently if Supervisor.start_link returns :ignore, but you crash because of the pattern match and not because of an invalid return value.

Supervisor.start_link can return :ignore , but a supervisor never does so without you implementing it to do that.

So, the documentation is wrong?
Should I create a ticket or something to fix it?

It’s not really wrong. There are cases where the function can return :ignore, but they don’t happen randomly. If it returns :ignore that means a developer set the supervisor up to do so by making the init/1 callback return :ignore.

From a quick glance at the code it seems Supervisor.start_link/2 actually really cannot return :ignore at the moment as there seems to be no path to bring a :ignore to the supervisor module it’s using behind the scenes. (There’s not much reason for it to be honest as you simply can just not call start_link/2 as well at this point.)

Supervisor.start_link/3 on the other hand can return :ignore for sure, as this one works with custom supervisor modules the user implements.

To bring this back to your case. Unless you use a supervisor which some developer setup to be able to return :ignore this will match up with the expected return values of the Application.start/2 callback. If a developer does so this will fail as well, as you’re not starting a proper root supervisor for the app to start. A app, which cannot start brings down the whole vm anyways.

1 Like

So I can’t get rid of the dialyzer error, correct?
The only way I see is to remove the @spec so it wont complain.

At this point I have the idea my code is correct and doesn’t go against any community guidelines, but I still have a tool that complains about it, which is distressing.

So you know Supervisor.start_link won’t return :ignore in this specific case. So the return value is only {:ok, pid()} | {:error, {:already_started, pid()} | {:shutdown, term()} | term()}. Using those should make make dialyzer happy, as it aligns with what Application.start expects.

3 Likes