Lightweight Static Dispatch
DependencyAtlas does not try to become a whole-program Julia type inferencer. The da_source dispatch layer is intentionally smaller and more explicit than that.
Its job is to improve call resolution for common, local, and explainable cases without turning the base graph into a speculative compiler pass.
Goals
The lightweight dispatch layer exists to answer a narrow question:
- given a callsite already extracted from source
- and a set of local candidate methods already known in the graph
- can we safely narrow that set using source-visible information?
In practice that means it focuses on:
- method signature metadata already present on nodes
- local call-shape facts from syntax/lowering
- owner parameter annotations
- simple type-hierarchy reasoning
- one-step local value propagation
It does not try to perform:
- global dataflow
- interprocedural type inference
- arbitrary evaluation of Julia code
- package-specific dispatch heuristics
Main Components
The dispatch stack is split into three internal layers.
1. Type Hierarchy
type_hierarchy.jl maintains a small compatibility model for:
- project-defined supertype chains
- builtin fallback chains such as
String -> AbstractString - family-tag expansion used by narrowing
This layer answers questions like:
- does
MyDerivedsatisfyDerivedStrategy? - what family tags should be attached to
BitVector? - how should short type refs be normalized in the current owner module?
2. Dispatch Types
dispatch_types.jl converts raw node metadata and call payloads into typed internal records.
This is where DependencyAtlas normalizes:
- signature parameters
- call arguments
- owner parameter environments
- local value environments
- return summaries
This layer keeps the orchestration code from passing around loosely-typed Dict{String,Any} blobs.
3. Dispatch Orchestration
dispatch.jl does only the narrowing workflow:
- arity filtering
- argument-shape scoring
- candidate truncation
- final evidence payload assembly
The orchestrator should stay small. If type reasoning or shape normalization starts growing there, it usually belongs back in dispatch_types.jl or type_hierarchy.jl.
What the Layer Uses
The current narrowing pass can consume several kinds of local information.
Signature Metadata
Method nodes carry normalized dispatch metadata such as:
- signature summary/text
- typed parameter payloads
- arity bounds
- vararg flags
That metadata is attached during lowering/build, then reused during call resolution.
Owner Parameter Annotations
If a call argument is just an identifier like method, the dispatch layer can inherit type information from the enclosing method signature.
This is what lets a call like:
reduce_derived(method::MyDerived, idx) = route_value(method, idx)
narrow toward overloads that accept MyDerived or its supertypes.
Local Value Flow
The layer also uses one-step local value propagation for simple assignments:
expanded = make_pairs(vals)
pick_default(expanded)
This does not try to evaluate make_pairs. Instead it carries a compact shape summary forward so later narrowing is not forced to treat expanded as a completely opaque identifier.
Macro-Generated Runtime Methods
Macro-generated functions are represented as generated runtime method nodes, not as calls attributed back to macro owners.
That means dispatch narrowing runs against the generated method body the same way it would for ordinary source-defined methods.
What “Safe” Means Here
The dispatch layer is intentionally conservative.
Examples of current safe wins:
- unique
Type{T}matches - project subtype-to-supertype narrowing
- builtin family narrowing such as
StringvsAbstractString - constructor-adjacent helper dispatch after one local assignment
- bare external short-name qualification when a single visible
using Moduleexport matches
Examples of cases it deliberately leaves unresolved:
- ambiguous short names across multiple visible modules
- dynamic export generation such as
Core.eval(Expr(:export, ...)) - broad interprocedural type recovery
- macro-definition dependency edges that are not runtime method truth
Why This Layer Exists Separately
Keeping static dispatch lightweight gives DependencyAtlas a few practical advantages:
- the base graph remains fast enough to run on large projects
- evidence remains explainable to humans
- behavior stays portable across codebases instead of becoming Clapeyron-specific
- on-demand JET views can still add richer evidence without fighting hidden heuristics in the base layer
In other words, da_source should resolve the calls it can justify from source structure and local facts, then stop.
Related Pages
- Want the broader pipeline first? See Analysis Pipeline.
- Want the engine layering after reading this? See Engine Integrations.
- Want the runtime payload shape of nodes and edges? See Reference / Protocol.