How to get the value of an @attr passed to a macro

If I pass @attr to a macro, the macro receives an AST of Module.get_attribute. How can the macro get the actual value?

The macro intends to combine the list value of an attr with another argument to the macro and use the resulting list in an Enum.map to create quoted code.

Thanks!

Since you’re operating on a module attribute you don’t really need to pass it in to the macro. Something as simple as this might help demonstrate:

defmodule M do
  @attr [1,2,3]

  defmacro attr do
    quote do
      Enum.each @attr, fn x ->
        IO.puts x
      end
    end
  end

  def test_attr do
    attr()
  end
end

Although it would be somewhat more expressive to the following:

defmodule M do
  @attr [1,2,3]

  defmacro attr do
    quote bind_quoted: [attr: @attr] do
      Enum.each attr, fn x ->
        IO.puts x
      end
    end
  end

  def test_attr do
    attr()
  end
end
1 Like

If you want to use a module attribute from another module that is being compiled then something like this might help:

defmodule M do
  @attr [1,2,3]

  defmacro attr do
    # In this case get the attribute from the calling module
    attribute =
      Module.get_attribute(__CALLER__.module, :attr)

      quote bind_quoted: [attr: attribute] do
        attr
      end
  end

  def test_attr do
    IO.inspect attr()
  end
end
2 Likes

To be more explicit:

defmodule ResultWrapper do

  defmacro result_wrapper( wrapper_name, component_keyword_list, common ) do

    cx = component_keyword_list ++ [ do something to common to make it a value ]
    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
    quote do
      import ResultWrapper
      @wrapper_common unquote( wrapper_fields )
    end
  end
end

If that makes any sense.

I had a few subtle things wrong. It was always my intention for a using call to set up a module attr that later macro calls would use. I needed to put the Module.put_attribute in the unquoted section of the using and not try to declare the @ in the quoted section (the value was not appearing otherwise). Then in the macro, call get_attribute. The other key bit is using CALLER.module (thank you @kip), I wasn’t sure where the module was hiding.

This works:

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

Thank you!

Yes, that’s the key - basically keeping it clear in your head what is happening at compile time and what is happening at runtime.

Module attributes are compile time, So setting it outside the quote block makes sense.