Module is not available warning when configuring project in config.exs

I have a project that expects to be configured with a module from another project like so:

defmodule HelperCore.Server do
  @command Application.get_env(:helper_core, :command)

  def dispatch(context) do
    @command.handle_command(context)
  end
end

With an expectation that the module set in :command implement the following behavior:

defmodule HelperCore.Command do
  @callback handle_command(HelperCore.Context.t) :: HelperCore.Context.t
end

Then, in a project that depends on HelperCore, I have:

defmodule Helper do
  @behaviour HelperCore.Command

  def handle_command(context) do
    ...
  end
end

and in its helper/config/config.exs:

config :helper_core, command: Helper

At runtime, this works fine and Helper.handle_command/1 is called when dispatch is called, but at compile time, I get the warning:

warning: function Helper.handle_command/1 is undefined (module Helper is not available)
  lib/helper_core/server.ex:36

Is this a problem? How should I specify this configuration so that it doesn’t raise a warning? I don’t want HelperCore to depend on Helper because it should be usable by any project to dispatch to a module that adopts the HelperCore.Command behavior.

1 Like

i would simply move the Application.get_env(:helper_core, :command) to runtime.

calling Application.get_env is inexpensive enough and storing configuration in module attributes seems discouraged anyway.

4 Likes

Manukall is correct here. You don’t want to set this at compile time.

Is there a way I can enforce the behavior?

My goal of having it at compile time was so it could fail to compile if the module in Application.get_env(:helper_core, :command) didn’t adopt the HelperCore.Command behavior.

You can disable the warning by adding an exception to xref that is generating the warning: mix xref — Mix v1.16.0.

This can not be enforced by the compiler even it it’s compile time.

EDIT: To clarify, the compiler cannot enforce that a given value is a module and that it implements a specific behaviour.

If I create a module in the same project as HelperCore.Server, it does the check:

defmodule HelperCore.Server do
  @command Application.get_env(:helper_core, :command)

  def dispatch(context) do
    @command.handle_command(context)
  end
end
defmodule HelperCore.LocalCommand do
end

warning: function HelperCore.LocalCommand.handle_command/1 is undefined or private

In this case the compiler isn’t checking if the module is implementing a behaviour, it can’t because you are never actually declaring that you want this specific behaviour.

The warning you are seeing is coming from xref which verifies that the function you are calling exists.