/** * Class Hierarchy Analysis (CHA) — type-hierarchy-resolved polymorphic dispatch. * (spec: add-type-hierarchy-resolved-dispatch) * * Closes the polymorphic-dispatch blind spot deterministically, from the * `ClassNode` / `InheritanceEdge` hierarchy the call graph already extracts plus * the existing type-inference engine — no LLM, no points-to solve. Classic CHA * (Dean, Grove, Chambers, ECOOP 1995): resolve a virtual call `recv.m()` to the * implementations of `m` reachable in the type subtree of `recv`'s type. * * Two additive, provenance-labeled edge kinds, both `confidence: 'synthesized'`: * * - **Virtual-dispatch** (`kind: 'calls'`): from an unpinned `recv.m()` call site * to each implementation of `m` in `recv`'s subtree. Labeled `cha-declared-type` * when the receiver type was statically recovered (so the target set is narrowed * to that type's subtree) and `cha-name-only` when it was not (a deliberately * weaker over-approximation: every implementation of `m` by method NAME across the * whole hierarchy — the call site's argument count is not recovered, so no arity * narrowing is applied here). * - **Override** (`kind: 'overrides'`): from a base method `B.m` to every overriding * `D.m` where `D <: B` and both declare `m` with compatible arity. The precise, * consistent replacement for the legacy class-level N×M adjacency cross-product. * * Bias is false-negatives over false-positives: a virtual-dispatch edge is emitted * only when `m` resolves to ≥1 implementation in the user-defined hierarchy (external * / stdlib types are not in it and never resolve); per-call-site fan-out is capped and * over-cap call sites are dropped (not partially wired) and logged. * * The name-only fan-out is additionally REFUSED when the receiver is statically known not to * be a value of an in-repo type (change: suppress-false-cha-name-only). See * {@link receiverOutsideRepository}: `json.Unmarshal(...)` is not an unpinned virtual call * whose type happens to be unrecovered — the source says `json` is a package outside this * repository, so fanning it onto every in-repo `Unmarshal` method contradicts a fact already * computed. A refused site is DISCLOSED as an {@link ChaRefusedSite} (so the graph records an * unbound call rather than silently losing one), never emitted as edges. */ import type { FunctionNode, ClassNode, InheritanceEdge, CallEdge } from './call-graph.js'; import { type ImportMap } from './import-resolver-bridge.js'; /** Per-call-site / per-base-method target fan-out cap — mirrors the dynamic-dispatch bound. */ export declare const CHA_FANOUT_CAP = 8; /** A receiver-based method call recovered from the raw edges: `recv.method(...)`. */ export interface RawMethodCall { callerId: string; /** Receiver variable/expression name (`recv` in `recv.m()`); `self`/`this`/`cls` allowed. */ recv: string; /** Method name `m`. */ method: string; line: number; offset?: number; } /** * A `recv.m()` site where CHA REFUSED the name-only fan-out because the receiver is statically * known not to be a value of an in-repo type (change: suppress-false-cha-name-only). * * Disclosed rather than dropped silently: the call site is real and unbound, so a consumer that * reads the graph for liveness or blast radius must treat it as an unresolved call, not as * absence. `refusedCandidateIds` are the in-repo methods the pass declined to wire, so the * existing `find_dead_code` ambiguity cap keeps naming them — removing a false edge must not * promote a symbol to confidently dead. */ export interface ChaRefusedSite { callerId: string; recv: string; method: string; line: number; /** Why the receiver cannot be an in-repo value. */ reason: 'package-qualifier' | 'declared-type-outside-repo'; /** The non-repo package/module the receiver (or its declared type) belongs to. */ qualifier: string; refusedCandidateIds: string[]; } /** * Extract a parameter count from a function declaration signature. Returns * `undefined` when no parameter list is recoverable, in which case arity is * treated as compatible with anything (we never *drop* a real override for an * unparseable signature — false-negative bias). */ export declare function arityFromSignature(signature: string | undefined): number | undefined; /** * Run the CHA synthesis pass: override edges + virtual-dispatch edges. Additive — * returns only new edges, never modifies the directly-resolved graph. Per-rule and * order-independent; a failure in one rule must not abort the other or the build. * * `refusedSites` are the `recv.m()` sites whose name-only fan-out was refused because the * receiver is statically outside the repository; the caller discloses them as unresolved call * sites (change: suppress-false-cha-name-only). */ export declare function synthesizeTypeHierarchyEdges(input: { nodes: Map; classes: ClassNode[]; inheritanceEdges: InheritanceEdge[]; rawMethodCalls: RawMethodCall[]; fileContents: Map; directCalleeIdsByCaller: Map>; /** Per-file import map, so a receiver type NAME shared across files resolves to the * class the caller actually imports (precise dispatch) instead of every same-named class. */ importMap?: ImportMap; }): { edges: CallEdge[]; refusedSites: ChaRefusedSite[]; }; //# sourceMappingURL=cha.d.ts.map