mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-09 03:17:54 +00:00
docs(scope-resolution): document the callable-flow capture contract (#2693)
The module is 1200+ lines behind a nine-line docblock, and the only worked
example was C. Both root causes fixed in this series were "the contract was
discoverable only by reading the emitter":
- the anonymous-callable convention (a seed whose source is a closure takes
its DESTINATION's name) is what makes closure bindings resolvable at all,
and is the reason the widened target gate is correct;
- a fieldless binding node silently decomposes to nothing under the shared
assignment fallback, which cost Kotlin one debugging cycle in #2522 and
Dart another here;
- captures alone are never enough — the bound name also needs a
`@declaration.*` or there is no cell to key the seed on.
Records the cell/site model, both traps, and points at the fullest and
smallest worked examples.
Bumps INCREMENTAL_SCHEMA_VERSION 15 → 16 and the parse-cache SCHEMA_BUMP
22 → 23: this series emits NEW CALLS edges and new Dart Function nodes, and
the incremental write set only covers changed files, so an existing index
would keep reporting a zero blast radius for exactly the symbols the fix is
about.
This commit is contained in:
parent
18757c048d
commit
5fee2016d2
4 changed files with 59 additions and 4 deletions
|
|
@ -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 `<lang>/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';
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 = <arrow | function-expression>` no longer emits an edgeless
|
||||
* `Const:<file>: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;
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue