Intra-module call graph.
For each defmodule in a file, list every callable (def, defp, defmacro,
defmacrop, defguard, defguardp, defdelegate) with which sibling callables
it invokes by name + arity.
External calls (Foo.bar/0, Kernel.+/2) and variable refs are dropped:
an edge is only emitted when the call site's {name, arity} matches a
callable defined in the same module. Pipes are expanded before walking
so x |> foo() is detected as foo/2, and &foo/2 captures count as
edges too.
Default arguments
A def with default args has one graph node — the canonical (highest)
arity. def foo(a, b \\ 1) produces one foo/2 node, not separate
foo/1 + foo/2 nodes. Calls at either arity resolve to foo/2 so
fan-in queries stay accurate; the synthetic foo/1 that Elixir
auto-derives at compile time is not a separate implementation, so
treating it as one would produce phantom self-loops on recursive
default-arg defs.
Summary
Types
@type call() :: {atom(), non_neg_integer()}
@type def_entry() :: %{ name: atom(), arity: non_neg_integer(), kind: atom(), private: boolean(), range: %{start: pos_integer(), end: pos_integer()} | nil, calls: [call()] }