diff --git a/gitnexus/src/core/ingestion/utils/callable-flow-captures.ts b/gitnexus/src/core/ingestion/utils/callable-flow-captures.ts index 45fb41d87..eb18a0f8b 100644 --- a/gitnexus/src/core/ingestion/utils/callable-flow-captures.ts +++ b/gitnexus/src/core/ingestion/utils/callable-flow-captures.ts @@ -6,6 +6,53 @@ * callable signatures, protocol invocation names). The central extractor * never sees a parser node and shared ingestion code never branches on a * language name. + * + * ## The cell/site model + * + * A *cell* is a named storage location that may hold a callable — a variable, + * parameter, field or pointer, canonicalized to a binding key by the scope + * tree. A *site* is one observed fact about cells, emitted here as a capture + * match and consumed by `passes/callable-value-flow.ts`, which runs them to a + * fixpoint. The site kinds (`CallableFlowSite`) are: + * + * - `seed` — a cell acquires a named callable: `f = target`. Carries + * `@callable-flow.target-name`, the name the pass resolves against. + * - `copy` / `alias` — a cell takes another cell's contents, so targets flow + * between them. + * - `address` / `store` / `load` — indirection through a pointer cell. + * - `formal` — a parameter cell of a known function, by index. + * - `argument` — a callable passed at a call site, binding to that `formal`. + * - `invoke` — a call THROUGH a cell (`f()`), the site that ultimately becomes + * a `CALLS` edge once the cell's target set is known. + * + * ## The anonymous-callable convention + * + * A closure literal has no name to resolve against, so a `seed` whose source + * is an anonymous callable takes its **destination's** name as + * `@callable-flow.target-name` — `val f = { }` seeds "the cell `f` holds the + * callable named `f`". That is deliberately self-referential and only resolves + * because the binding itself is a callable target: `buildGraphTargetIndex` + * admits it on the label of the graph node it resolves to, which #2687 makes a + * `Function` for exactly this construct. Languages whose closure binding does + * NOT emit a callable graph node get no resolution from the convention alone + * (#2693). + * + * ## Adding a language + * + * Supply `CallableFlowCaptureOptions` from `/captures.ts` and call + * `synthesizeCallableFlowCaptures`. Two recurring traps: + * + * - A **fieldless** assignment/binding node decomposes to nothing under the + * shared `left`/`name`/`value` fallback in `assignmentParts`. Supply + * `extractAssignment` (Kotlin's `assignment`, Dart's + * `initialized_identifier`). Returning `undefined` falls back to the shared + * path, so one callback can handle the odd node and leave the rest alone. + * - A binding needs a `SymbolDefinition` for the pass to attach to. Captures + * alone are not enough: without a `@declaration.*` for the bound name, the + * seed has no cell to key on. + * + * `c/captures.ts` is the fullest worked example (pointers, signatures, + * overload selection); `dart/captures.ts` the smallest interesting one. */ import type { CaptureMatch, ParameterTypeClass } from 'gitnexus-shared'; diff --git a/gitnexus/src/storage/parse-cache.ts b/gitnexus/src/storage/parse-cache.ts index 8f8c7cc22..2b98b8618 100644 --- a/gitnexus/src/storage/parse-cache.ts +++ b/gitnexus/src/storage/parse-cache.ts @@ -63,6 +63,8 @@ import type { ParseWorkerResult } from '../core/ingestion/workers/parse-worker.j // method injection sites plus bean-name and @Primary provider metadata. // v20: Java/Kotlin capture side-channels persist package and class-annotation // facts for shared Spring Bean resolution. +// v23: Dart closure bindings emit Function nodes for function-local closures +// and flow captures for top-level ones (#2693). // v21: Java local class/enum/record/interface captures use javac-compatible, // source-type-relative JLS 13.1 identities and declaration-to-block scopes // (#2562). @@ -70,7 +72,7 @@ import type { ParseWorkerResult } from '../core/ingestion/workers/parse-worker.j // JLS 13.1 immediate-host chains (#2555). // v18: Worker$N anonymous bodies. v17: callable-value-flow operand identity. // v16: direct callee identity. -const SCHEMA_BUMP = 22; +const SCHEMA_BUMP = 23; const GITNEXUS_PKG_VERSION = (() => { try { // package.json sits at gitnexus/package.json — two levels up from diff --git a/gitnexus/src/storage/repo-manager.ts b/gitnexus/src/storage/repo-manager.ts index faca22eff..d4f00a9e7 100644 --- a/gitnexus/src/storage/repo-manager.ts +++ b/gitnexus/src/storage/repo-manager.ts @@ -467,13 +467,19 @@ export interface RepoMeta { * instance owner is outside the caller's enclosing class/MRO (#2563). The * incremental write set would otherwise retain those stale CALLS edges on * every unchanged C# and Kotlin file; force a full re-analyze instead. + * v16: calls through a closure-valued binding (`val f = { }; f()`) now resolve + * in Kotlin, Swift and Dart (#2693). These are NEW `CALLS` edges, and Dart also + * gains `Function` nodes for function-local closures. The incremental write set + * only covers changed files, so unchanged files would keep reporting a zero + * blast radius for those symbols; force a full re-analyze instead. + * * v15: `const X = ` no longer emits an edgeless * `Const::X` twin beside its `Function` node (#2687). The incremental * write set only covers changed files, so every unchanged TS/JS file would * keep its twin and `impact`/`context` would stay ambiguous on those names; * force a full re-analyze instead. */ -export const INCREMENTAL_SCHEMA_VERSION = 15; +export const INCREMENTAL_SCHEMA_VERSION = 16; export interface IndexedRepo { repoPath: string; diff --git a/gitnexus/test/unit/call-summary-schema-version.test.ts b/gitnexus/test/unit/call-summary-schema-version.test.ts index db16663a5..24df56b66 100644 --- a/gitnexus/test/unit/call-summary-schema-version.test.ts +++ b/gitnexus/test/unit/call-summary-schema-version.test.ts @@ -73,8 +73,8 @@ describe('CALL_SUMMARY relation-type exclusion (U-C1)', () => { }); describe('CALL_SUMMARY incremental reuse gate (U-C5)', () => { - it('INCREMENTAL_SCHEMA_VERSION is bumped to 15 (const-arrow twin removal, #2687)', () => { - expect(INCREMENTAL_SCHEMA_VERSION).toBe(15); + it('INCREMENTAL_SCHEMA_VERSION is bumped to 16 (closure-binding call resolution, #2693)', () => { + expect(INCREMENTAL_SCHEMA_VERSION).toBe(16); }); it('a pre-current stamp fails the `=== INCREMENTAL_SCHEMA_VERSION` reuse gate → forces full re-analyze', () => {