Using module attributes in generating different code

I wanted to define default terminate/2 and terminate/3 implementations for GenServer and :gen_statem by using a __using__ macro. (That part worked.)
What I couldn’t make work is generating different definitions based on the value of a given module’s @behaviour attribute.

What I wanted to do would have looked like this:

defmodule Utils.Terminate do

defmacro __using__(_opts) do
  cond do
    GenServer   in @behaviour -> two_args()
    :gen_statem in @behaviour -> three_args()
    true -> two_args()
            three_args()
  end
end

defmacro two_args do
  quote do
    def terminate(:normal, _),    do: :ok
    def terminate(_reason, _),    do: # notify someome
  end
end

defmacro three_args do
  quote do
    def terminate(:normal, _, _), do: :ok
    def terminate(_reason, _, _), do:  # notify someome
  end
end

end

But I found no way to actually work with the value of another module’s @behaviour variable outside a quote block. (Because I only wanted to have those versions defined in that module that that particular behaviour required.)

Does anybody have a good suggestion how to do this?

I’m not exactly sure anymore, but you need either __CALLER__ or Module.get_attribute (or something like that)

This may help you

2 Likes

I actually discovered my flaw in reasoning due to this solution.

What I tried to do was using the module attribute to only generate the required code. I’m starting to think that’s prevented by how they work.
However, this works:

defmodule Hello do
  defmacro __using__(_) do
    quote do
      cond do
        GenServer   in @behaviour -> def foo, do: :server
        :gen_statem in @behaviour -> def foo, do: :fsm
        true                      -> def foo, do: @behaviour
      end
    end
  end
end

defmodule Test do
  @behaviour :gen_statem
  use Hello
end

Test.foo/0 will indeed be defined accordingly to return :fsm. While this splices some conditional code into the module top-level it does what I want to do.

Thank you!

2 Likes