Dynamic code generation: def as an exception in Macro expansion prioritisation. Understanding Macro.escape(var, unquote: true) Injection vs Transfer

Thanks for that clarification.

Just to really confirm, def is expanded at the same phase as other macros, but with def the expanded AST is stored and used later. Which would mean that the below is exemplifying the order of expansion for all intents and purposes, due to actual exposure of the def AST’s effects via emittance of the module byte code?

Order of expansion

As you’d expect, the module-level code (the code that isn’t a part of any function) is evaluated in the expansion phase. Somewhat surprisingly, this will happen after all macros (save for def) have been expanded. It’s easy to prove this:


defmodule MyMacro do
          defmacro my_macro do
            IO.puts "my_macro called"
            nil
          end
        end

defmodule Test do
          import MyMacro

          IO.puts "module-level expression"
          my_macro
        end

# Output:
my_macro called
module-level expression

See from the output how mymacro is called before IO.puts even though the corresponding IO.puts call precedes the macro call. This proves that compiler first resolves all “standard” macros. Then the module generation starts, and it is in this phase where module-level code, together with calls to def is being evaluated.

excerpt from Understanding Macros Part 6