# A.ex
defmodule A do
use B
@description "function1/0 returns :ok"
def function1() do
:ok
end
@description "function2/0 returns :not_ok"
def function2() do
:not_ok
end
end
# B.ex
defmodule B do
defmacro __using__(_) do
quote do
import B
Module.register_attribute(__MODULE__, :description, accumulate: true, persist: true)
Module.register_attribute(__MODULE__, :test, accumulate: true, persist: true)
@on_definition B
@after_compile B
@before_compile B
IO.puts("B started in #{IO.inspect(__MODULE__)}")
end
end
def __on_definition__(env, _access, name, args, _guards, _body) do
IO.puts("Reached on_definition")
desc = Module.get_attribute(env.module, :description) |> hd()
Module.delete_attribute(env.module, :description)
Module.put_attribute(env.module, :test, {name, length(args), desc})
end
def __before_compile__(_env) do
IO.puts("Reached before_compile")
end
def __after_compile__(_env, bytecode) do
IO.puts("Reached after_compile")
# Gets debug_info chunk from BEAM file
chunks =
case :beam_lib.chunks(bytecode, [:debug_info]) do
{:ok, {_mod, chunks}} -> chunks
{:error, _, error} -> throw("Error: #{inspect(error)}")
end
# Gets the (extended) Elixir abstract syntax tree from debug_info chunk
dbgi_map =
case chunks[:debug_info] do
{:debug_info_v1, :elixir_erl, metadata} ->
case metadata do
{:elixir_v1, map, _} ->
# Erlang extended AST available
map
end
x ->
throw("Error: #{inspect(x)}")
end
dbgi_map[:attributes]
|> IO.inspect
end
end
Considering the two modules shown above, module B is inserted in module A using the use
macro. They produce this output:
B started in Elixir.A
Reached on_definition
Reached on_definition
Reached before_compile
Reached after_compile
[
test: {:function1, 0, "function1/0 returns :ok"},
test: {:function2, 0, "function2/0 returns :not_ok"}
]
From the docs, it states that @on_definition is “invoked when each function or macro in the current module is defined”.
A function can only be defined after it is compiled, so how is @on_definition reached before @before_compile and @after_compile?
Why is the order of execution: @on_definition → @before_compile → @after_compile, and not @before_compile → @after_compile → @on_definition?
Another question, I used put_attribute in the @on_definition callback (to set @description "..."
). These new attributes were read successfully in the @after_compile callback as shown in the output.
My question is, if attributes can only be set at compile time, how is @after_compile able to read the new changes that were done after the functions were already defined (in @on_definition)?
Appreciate any feedback. Thanks.