lurodrigo
Help understanding compile-time dependencies when referencing aliases in module attributes
I’m refactoring some parts of a codebase that do some compile-time magic, in order to reduce the number of compile-time dependencies between modules. One thing I don’t fully get is when nominal references to other modules/aliases in module attributes create a compile-time dependency.
I have a minimal example here (compiling with elixir 1.15.7-otp-26)
If I have these two modules:
defmodule Trace.Submodules.Submodule do
end
and
defmodule Trace do
alias Trace.Submodules
@attribute Submodules.Submodule
@attribute Submodules.DoesNotExist
@attribute [Submodules.Submodule]
@attribute [Submodules.DoesNotExist]
@attribute to_string(Submodules.Submodule)
@attribute to_string(Submodules.DoesNotExist)
@attribute to_string(Submodules.Submodule) <> to_string(Submodules.DoesNotExist)
@attribute (& &1).(Submodules.Submodule)
def attribute, do: @attribute
end
When I run mix xref trace lib/trace.ex --label compile it shows Trace three places where there are compile-time dependencies on Trace.Submodules.Submodule → precisely where there are function calls wrapping Subodules.Submodule, but not a direct reference or just wrapping in a list.
lib/trace.ex:8: alias Trace.Submodules.Submodule (compile)
lib/trace.ex:10: alias Trace.Submodules.Submodule (compile)
lib/trace.ex:11: alias Trace.Submodules.Submodule (compile)
It’s weirder because I do the exact same operations for Submodules.DoesNotExist, which doesn’t exist, and there’s no problem. Is it the case the compiler assumes the alias can be used as a module by the function at compile-time, so it places a compile-time dependency on it?
Asking bc the reason behing the behaviour impacts how I’ll approach refactoring. Any help appreciated!
Marked As Solved
xpg
That explanation makes sense to me. Good analysis!
Applying your way of thinking (and a bit looking and xref source code) also turned out to explain and solve my situation: Macro.expand_literals/2 will indeed add compile-time dependencies if the provided environment (__CALLER__ in my case) represents the body of a module. But it will only add a runtime dependency, if the environment represents the body of a function.
The documentation of Macro.expand_literals/2 hints that you can just modify the environment passed in, such that the code “thinks” you are in a function body. Unfortunately, I didn’t quite understand the hint for quite some time ![]()







