I’m confronted with a case where mix xref graph --label compile-connected --fail-above 0 would force me to refactor something which seems unnecessary. I’ll give a simplified example, starting with an expected case and then showing my case.
Expected case
Here is a clear transitive compile-time dependency, where A depends on B which depends on C.
(Note: pretend that each of these modules is in its own file.)
- If
C.hello/0changes, that meansBneeds to recompile andB.hello/0changes - If
B.hello/0changes, that meansAneeds to recompile andA.hello/0changes
defmodule A do
@hello B.hello()
def hello, do: @hello
end
defmodule B do
@hello C.hello()
def hello, do: @hello
end
defmodule C do
def hello, do: "hello"
end
We can find this out like this:
$ mix xref graph --label compile-connected --fail-above 0
lib/a.ex
└── lib/b.ex (compile)
** (Mix) Too many references (found: 1, permitted: 0)
If we want to know “why is it bad that A depends on B? What does B depend on?”, we can check:
$ mix xref graph --source lib/b.ex
lib/b.ex
└── lib/c.ex (compile)
(Tangential point: it would be nice if the first command would show this automatically; its output implies “A shouldn’t depend on B” when maybe the truth is that B shouldn’t depend on C.)
A More Confusing Case
Here is a more confusing case, like the one I’m facing:
- If
C.world/0changes, that meansBneeds to recompile andB.world/0changes - If
B.world/0changes, I’m pretty sure that A isn’t going to compile any differently, but because theAdepends at compile time onBforB.hello/0, it will get (needlessly?) recompiled.
defmodule A do
@hello B.hello()
def hello, do: @hello
end
defmodule B do
@hello "hello"
def hello, do: @hello
def world, do: C.world()
end
defmodule C do
def world, do: "world"
end
The mix xref commands above see these two situations as the same, even though to me they seem quite different. This seems like a shortcoming of mix xref and/or the compile process.
Am I wrong?






















