How to expose (or use) a module attribute that is built using macros

Thanks, @kip, I think this is really helpful, but I still have some questions, for example:

defmodule Greeter do
  defmacro greeting(name) do
    quote do
      Module.put_attribute(__MODULE__, :greeting, unquote(name))
    end 
   end
   
   defmacro __using__(opts) do
    quote do
      import Greeter
      Module.register_attribute(__MODULE__, :greeting, accumulate: false)
      
      def greet_inside() do
        @greeting
      end      
    end
   end 
end
defmodule Gentlemen do
  use Greeter
  
  greeting "Hello Sir"
  
  def greet, do: @greeting  
end

So, let’s see if I understand it right, the previous example will yield:

Gentlemen.greet
#==> `"Hello Sir"`
Gentlemen.greet_inside
#==> nil

Considering that a macro is expanded when it’s called, so Greeter.__using__ is expanded first, adding the @greeting attribute to Gentlemen and defining the greet_inside() function, which outputs the @greeting attribute. So, greet_inside() outputs nil when its called because at compilation time the attribute was registered but no value was assigned to it yet. Then, the greeting macro is expanded and it adds "Hello Sir" to the @greeting attribute, and that’s why greet() outputs "Hello Sir". Is my understanding right? (does the code expands top to bottom sequentially like this?).

Considering I’m right, for this to work, greet_inside() has to be a macro otherwise @greeting would be evaluated to nil at that point. So I was expecting this to work:

defmacro greet_inside() do
  quote do
    @greeting
  end
end      
import Gentlemen
Gentlemen.greet_inside()

** (ArgumentError) cannot invoke @/1 outside module
    (elixir 1.13.1) lib/kernel.ex:6111: Kernel.assert_module_scope/3
    (elixir 1.13.1) expanding macro: Kernel.@/1
    iex:12: (file)
    expanding macro: Gentlemen.greet_inside/0
    iex:12: (file)

But it won’t, because when greet_inside() is imported and called (and expanded), I’m outside of a module (I’m testing it on iex) and I can’t access attributes outside of modules. I imagine that __CALLER__.module in that context won’t return anything either, so what are my other options?

I think I understood what you mean, but I’m having a hard time applying it for this scenario. I think it’s a little different because we are setting the attribute value after the macro that registered it? (unless macros are expanded all at once, so I wouldn’t have the reference to the attribute yet!?)