Accessing module attributes inside __using__

Suppose I have a module defining a __using__ macro. It registers a module attribute attr. I want every module that uses it to automatically have a attrs/0 function that exposes the value of that attribute. However it does not work. Here’s my minimal example:

defmodule Test do
  defmacro __using__(_options) do
    quote do
      Module.register_attribute(__MODULE__, :attr, accumulate: true, persist: true)
      import unquote(__MODULE__), only: [attr: 1]

      def attrs_not_working, do: @attr
    end
  end

  defmacro attr(opts) do
    quote bind_quoted: [attr: Macro.escape(opts)] do
      @attr attr
    end
  end
end
defmodule UseTest do
  use Test

  attr(:hello_world)

  def attrs_working, do: @attr
end
iex(36)> UseTest.attrs_working()
[:hello_world]
iex(37)> UseTest.attrs_not_working()
[]

Guess it has to do it @attr meaning different things in different contexts. How would I do that?

define attr_not_working using @before_compile module attribute:

defmodule Test do
  defmacro __using__(_options) do
    quote do
      Module.register_attribute(__MODULE__, :attr, accumulate: true, persist: true)
      import unquote(__MODULE__), only: [attr: 1]

      @before_compile unquote(__MODULE__)
    end
  end

  defmacro __before_compile__(_) do
    quote do
      def attrs_not_working, do: @attr
    end
  end

  defmacro attr(opts) do
    quote bind_quoted: [attr: Macro.escape(opts)] do
      @attr attr
    end
  end
end
2 Likes

Indeed it works! Thanks! Will study more about these compilation triggers.

1 Like

Just remember to think of defmodule as a script to compile the module… If you put two things out of order there is no magical way the first thing can know about the second (this is not the case for all compilers, some do a pre-parsing step to figure out all the features, then you can use them later)

3 Likes