Fetch all callbacks from behaviour module

Hi everybody!

I have tried to propose PR for the ‘credo’ project (Credo is a static code analysis tool for the Elixir language). The aim of this PR was mainly in comparison of the arities of the callback functions (functions which are exists in module to implement injected behaviour) with the arities of the behaviour module functions. If they don’t match - issue should be raised.

My appoach to this problem was the following:

  • fetch path pointing to behaviour from module_info[:compile][:source] (for example GenServer.module_info[:compile][:source] …)
  • and then find all callbacks from the module existing at that path using AST traversion

Unfortunately this approach can not be applied to Credo, because module_info requires module to be compiled and Credo is supposed to be a static analysis tool that works without compiling the source code which it analyses.

My question is the following:

Here is the module:

defmodule Example
  @behaviour MyCustomBehaviour

  def handle_call(:x, state) do
    # Notice the missing "from" parameter
    {:reply, :y, state}
  end
end

How can I fetch the list of callbacks available for behaviour module without compilation of the source code? Actually I need to find out the path to the behaviour module without source compilation.

Probably get the list of the files from Application.app_dir(:elixir) or Application.app_dir(:stdlib) and then try to find the behaviour source file (.beam or .erl would be better?). Any other ideas?

Many thanks in advance for your suggestions!

1 Like

All behaviour modules include a special behaviour_info/1 function that allows for some reflection. You can pass atom :callbacks as the argument to get a list of callbacks (that’s actually the only supported option right now).

iex(2)> GenServer.behaviour_info(:callbacks)
[handle_call: 3, terminate: 2, init: 1, code_change: 3, handle_cast: 2,
 format_status: 2, handle_info: 2]
2 Likes

Thank you, I didn’t know about this function. I will try to apply it for my task!

There is one essential thing I’m interested to know: should GenServer in your example be loaded to call this function?
Because in my case I have no ability to ensure that module loaded (please see aforementioned comments attached to the PR on github)

Without actually compiling I am not sure how possible it would be to test this? Macro’s, use’s, etc… can easily add callbacks, behaviours, etc… You could make a macro that asks Random if it should add it. I just do not see how such a situation is resolvable without compiling as this sounds like a variant of the Halting problem.

1 Like

Due to reasons mentioned already in the thread, you can’t do this without compiling the module in question, as well as modules which the module under test does require.

But as far as I understand your problem, dialyizer does already check if callback functions are implemented with the correct arrity and spec. It does require you to compile the code though.

1 Like

Thank you for all your assistance!

I felt the same about this issue, but I wanted to try all possible ways to resolve this problem without compilation.

Best regards,
Mikhail