thojanssens1
Why is __after_compile__/2 called before __before_compile__/1?
defmodule Bar do
@after_compile __MODULE__
defmacro __before_compile__(_env) do
IO.inspect("#{__MODULE__}.__before_compile__")
end
def __after_compile__(_env, _bytecode) do
IO.inspect("#{__MODULE__}.__after_compile__")
end
end
defmodule Foo do
require Bar
@before_compile Bar
@after_compile __MODULE__
def __after_compile__(_env, _bytecode) do
IO.inspect("#{__MODULE__}.__after_compile__")
end
end
Will print:
“Elixir.Bar.after_compile”
“Elixir.Bar.before_compile”
“Elixir.Foo.after_compile”
[Bar, Foo]
Why is after_compile called before before_compile for the Bar module?
Marked As Solved
NobbZ
-
Baris compiled, you see its output ofBar.__after_compile__ -
Foois going to get compiled, it callsBar.__before_compile__, you see the outputBar.__before_compile__, because__MODULE__isBarin that context. You wan’t to checkenv.modto see the callers module. -
Foogets actuially compiled, its after compile gets called and you see itsFoo.__after_compile__.
Also Liked
ityonemo
Because you are compiling Foo after Bar.
LostKobrakai
Baz is already compiled when used in MyApp. MyApp is not yet compiled. In macros it’s important to be aware if you‘re working in the context of the macro (__MODULE__ is the module with the macro definition) or if you‘re working on the AST of the macros result, which is effectively becoming part of the module calling the macro (__MODULE__ refers to the module calling the macro).
NobbZ
Again, you need to look into the callers environment. Macros get passed __CALLER__ as a magic variable, so you probably want to Module.register_attribute(__CALLER__.mod, :attr, []) or in this case even more appropriate, as you do not make it accumulating or persistent:
defmacro __using__(_opts) do
quote do
@attr []
end
end
Or instead of the empty list use any other default value you prefer.
Beeing aware of the current context you are in is important for macros.
Perhaps give Metaprogramming Elixir by @chrismccord a read? I only skimmed about the first quarter of it, but it helped me to understand macros enough for my usecase.
ityonemo
defmodule A do
defmacro __using__(opts) do
otp_app = opts[:otp_app] # this gets set to :foo
quote do # note 'use' is called by B, so the next two lines live in B.
@otp_app unquote(otp_app) # becomes @otp_app :foo
@before_compile A
end
end
defmacro __before_compile__(_) do
__CALLER__.module
|> IO.inspect(label: "module")
|> Module.get_attribute(:otp_app)
|> IO.inspect(label: "otp app")
quote do end
end
end
defmodule B do
use A, otp_app: :foo
end
compile-time output:
module: B
otp app: :foo
If it’s clearer, this is effectively equivalent:
defmodule A do
defmacro __using__(opts) do
otp_app = opts[:otp_app]
quote do
@otp_app unquote(otp_app)
end
end
defmacro __before_compile__(_) do
__CALLER__.module
|> IO.inspect(label: "module")
|> Module.get_attribute(:otp_app)
|> IO.inspect(label: "otp app")
quote do end
end
end
defmodule B do
use A, otp_app: :foo
@before_compile A
end








