A macro I was writing required some default values for a using module file. The apparent way of doing this is to pass options to use that get turned into module attributes. I encountered some problems while trying to make this work and thought this might help explain.

The few things of most importance are:

  • Module attributes are a compile time only thing
  • Macros are evaluated at compile time, producing code which is then compiled
  • Passing a value to a macro is not the same as passing a module attribute



On that last point, passing a literal @att to a macro will provide the macro with an AST that is a call to Module.get_attribute, which is typically not what you want if you're interested in the compile time value of that attribute.

The attribute in my case is the formation of a contract between the __using__ set up and the macro's running. It's better for the world outside the macro module to not see or know about this attribute.

The code:

defmodule ResultWrapper do

  defmacro result_wrapper( wrapper_name, component_keyword_list ) do

    cx = component_keyword_list ++
      Module.get_attribute( __CALLER__.module, :wrapper_common )
    
    fields =
      Enum.map( cx, fn { k, t } -> quote do
          field unquote( k ), unquote( t )
        end
      end )

    quote location: :keep do

      object unquote( wrapper_name ) do
        unquote( fields )
      end
    end
  end

  defmacro __using__( wrapper_fields ) do
    Module.put_attribute( __CALLER__.module,
      :wrapper_common, wrapper_fields )

    quote do
      import ResultWrapper
    end
  end
end

The attribute wrapper_fields is created during __using__ and then used in the result_wrapper macro to make a complete list.

The non-working and discussion of this problem can be found on ElixirForum