Following Flows Where Static Calls Disappear

CodeGraph’s graph can be rich and still stop short of the flow a user cares about, because many applications do not write every connection as a direct call. This chapter diagnoses that gap and explains how CodeGraph chooses among name resolution, synthesized relationships, and an explicit boundary. The practical judgment is not whether the graph contains an arrow, but whether the arrow has a defensible mechanism behind it.

Why a direct call is not the whole flow

A syntax tree is good at recording an explicit call such as one function invoking another by name. Real applications use more indirect forms. A callback is registered in one place and invoked later from a collection. An event channel connects a publisher to subscribers through a string key. An interface call reaches an implementation selected by dispatch. A framework route connects an external path to a handler through a convention or configuration shape. A mobile application can cross from JavaScript to native code through a bridge that is not expressed as an ordinary call in either language.

These patterns create a specific diagnostic symptom: the graph contains the caller and the eventual handler as separate objects, but a flow query breaks between them. The missing relationship is not necessarily a parser failure. The source may contain no direct reference for a basic extractor to follow. CodeGraph’s response is to classify the missing hop before trying to repair it.

The first class is a named reference. If the source provides an attribute, imported name, handler declaration, route target, or other recognizable identifier, a resolver can use project context to connect it to a definition. Imports, qualified names, enclosing types, language rules, and framework conventions can narrow the candidates. The resolver’s job is to answer, “Which known project object does this named reference denote?”

The second class is registration and dispatch. A callback may be appended to a collection, then invoked by a loop that knows only the collection field. An observer may register a handler through an onUpdate-style operation, then call it later through a dispatcher. There may be no single named reference from the dispatcher to the callback. A synthesizer has to correlate two source facts: where the handler was wired and where the dispatcher invokes the registered value.

The distinction matters because the evidence is different. A resolver links a reference that already names something. A synthesizer creates a relationship from a recognized interaction pattern. CodeGraph records synthesized edges as heuristic and can retain the wiring site that justified them. The result can make a flow useful without pretending that the source contained a direct call.