fix(scope-resolution): resolve callable reference flows (#2437) (#2522)

* docs(plans): add provider-hook value-refs plan (#2437)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(plans): deepen #2437 plan to USES + property-dispatch design

Design revised after prior-art research (Kythe ref vs ref/call, Joern
METHOD_REF, Feldthaus field-based call graphs, CodeQL impliedReceiverStep):
registration sites emit reference-class USES, invocation is recovered by a
field-based property-dispatch pass synthesizing CALLS at member-call sites.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(scope-resolution): model provider-hook value references (#2437)

Functions referenced as object-literal property values (provider hooks like
emitScopeCaptures: emitCppScopeCaptures) previously produced no edge at all,
so impact/context reported a false-safe 0 upstream dependents.

Two coordinated halves, per prior art (Kythe ref vs ref/call, Joern
METHOD_REF, Feldthaus ICSE'13 field-based call graphs, CodeQL
impliedReceiverStep):

- Registration -> USES: new ReferenceKind 'value-ref'; TS/JS queries capture
  pair values and shorthand properties (with @reference.property-key);
  emitted as a reference-class USES edge, reason 'scope-resolution:
  value-ref'. Resolution is callable-gated so plain values emit nothing.
- Dispatch -> CALLS: new shared pass emitPropertyDispatchCalls synthesizes
  CALLS (reason 'property-dispatch', confidence 0.7, per-key fan-out cap 32
  calibrated on this repo's 16-provider hook tables) from member-call sites
  to every function registered under the same property key.

Deviation from plan: the pass owns value-ref resolution entirely via the
post-finalize findCallableBindingInScope walker — the shared registries only
see pre-finalize local bindings, so imported hooks (the c-cpp.ts case) were
unresolvable through lookupForSite; Reference.propertyKey passthrough
dropped as unnecessary.

SCHEMA_BUMP 13 -> 14: ParsedFile gains value-ref sites + propertyKey.

Verified end-to-end: impact(emitCppScopeCaptures, upstream) now reports 8
impacted / HIGH with extractParsedFile (true dispatch caller) at d=1 via
property-dispatch and the c-cpp.ts registration via USES.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(scope-resolution): cover value-ref registration and property dispatch (#2437)

Integration: same-file/cross-file/aliased/shorthand registrations emit USES;
non-callable and destructuring values emit nothing; dispatch sites gain
property-dispatch CALLS (incl. JS twins and per-language partitioning);
fan-out-capped keys are dropped entirely; factory-call values unchanged.
Unit: capture-shape pins for @reference.value-ref + @reference.property-key.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(scope-resolution): surface dropped property-dispatch keys in stats (#2437)

Review finding: skippedKeys was returned but discarded — a hook table
larger than the fan-out cap silently reopened the #2437 gap for those
keys. Log dropped keys and fold value-ref USES + dispatch CALLS into
referenceEdgesEmitted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(plans): add callable reference-flow implementation plan

* fix(scope-resolution): close property-dispatch review gaps

* feat(scope-resolution): add callable flow facts

* feat(scope-resolution): resolve callable value flow

* feat(scope-resolution): resolve callable references across providers

* fix: harden callable reference flow resolution

* fix(scope-resolution): preserve callable binding semantics

* docs(plans): add pr-2522-review-fixes plan

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(storage): bump INCREMENTAL_SCHEMA_VERSION for callable-value-flow edges

Callable-value-flow CALLS/USES edges (#2437) can connect two files whose
content did not change, but the incremental write set only covers changed
files — a top-up against a pre-v7 index would silently omit the new edges
for every unchanged file pair, indefinitely. Force the one-time full
re-analyze (review finding 1, #2522).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(storage): sanitize callable-flow sites per-site at load, log drops

The load-time validator rejected the WHOLE ParsedFile when one site was
malformed or over-bound, with no logging — and C++ legitimately emits
empty-string parameterTypes entries ('' = unknown, the
ReferenceSite.argumentTypes convention) for cv-only/ERROR-recovered types,
so real repos fell into a permanent, silent warm-cache-miss reparse loop
through the #1983-sensitive main-thread path (review finding 7, #2522).

Now: '' entries are valid in type arrays; a malformed/over-bound site drops
only itself (counted, warned once per load); only non-array garbage —
evidence the serialization itself is untrustworthy — rejects the file.
Deviation from plan §6 wording: validator-side tolerance replaces emit-side
clamps — smaller diff, same asymmetry closed at the single chokepoint.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(scope-resolution): keep declarations in the union for reassigned callable cells

The binding-lookup suppression for fact-constrained cells was wholesale:
reassigning a declared function through its own name (greet = other;
greet()) deferred the call to the solver, which then refused the lexical
lookup that resolves the declaration — an unresolvable RHS yielded zero
CALLS for a call that resolved pre-flow (review finding 8, #2522).

Suppression now applies only to cells bound by FORMAL facts — its actual
purpose (a parameter whose grammar emits no declaration binding must not
adopt a same-named outer function). Copy/alias/store/load destinations keep
their declaration as an inclusion seed (Andersen-style union).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(scope-resolution): count forfeited deferred sites in the budget-bailout warning

On work-budget exhaustion the deferred invoke sites end the run with zero
CALLS — free-call fallback and reference emission already skipped them —
but the warning said 'ordinary graph emission remains untouched', which is
false for exactly those sites. The warning context now carries the
unresolved deferred-site count and the comment states the real cost
(review finding: budget-bailout honesty, #2522).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(scope-resolution): surface dropped property-dispatch keys in stats and warn payload

The over-cap warning carried only a count; the dropped key NAMES were
discarded and RunScopeResolutionStats had no field, so the PR-body claim
'includes them in resolver statistics' was unimplemented (review finding,
#2522; reviewer ask on the fan-out cap). The warn payload now names up to
20 dropped keys and the stats carry propertyDispatchSkippedKeys.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(scope-resolution): drop producer-less ownerQualifiedName from formal sites

No capture emitter anywhere produces @callable-flow.owner-qualified-name —
the solver branch consuming it was unreachable in production, yet the field
was typed, parsed, validated, and unit-tested with hand-built input (review
finding 16, #2522; YAGNI). Re-add with a real producer if C++ qualified
member declarators ever need it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(scope-resolution): drop dead callable-flow knobs

CallableFlowPassingMode 'callable-object' had no producer and no consumer
distinguishing it, and CallableFlowCaptureOptions.extractCallArguments had
no language providing it (unlike its live sibling extractCallCallee) —
review finding 17, #2522 (YAGNI). The invocation-kind 'callable-object'
is a different, live concept and stays.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ingestion): bind subscripted callable cells to the container, not the index

terminalIdentifier iterates children in reverse, so tbl[i] = handler seeded
the INDEX variable's cell (polluting a same-named formal) and tbl[i](7)
looked up the callee under i in a different scope — no join, no CALLS edge
for the classic function-pointer-array dispatch (review finding 12, #2522).
Subscript nodes now recurse into their container field only, in both
bindingIdentifier and terminalIdentifier, across the fielded grammars
(C/C++/JS/TS/Python/Go/Java).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ingestion): make cross-function file-scope callable bindings resolvable

Two stacked gaps killed the canonical C callback-registration pattern
(fp assigned in init(), called in run()) — the exact #2437 false-safe this
PR exists to fix (review finding H1, #2522):

1. isVisibleValueBinding only consulted assignment regions and formals, so
   a call in a function OTHER than the assigning one emitted no invoke
   fact. A declared callable-typed binding is now a value binding wherever
   its declaration is visible (visibleCallableSignature).
2. The C scope query had no @declaration.variable pattern for function-
   pointer declarators — void (*fp)(int); created no scope-tree binding,
   so the seed (init) and invoke (run) cells canonicalized to different
   keys and never joined. Both bare and initialized forms now bind.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(c): detect variadic parameters via the named variadic_parameter node

tree-sitter-c materializes '...' as a named variadic_parameter node; the
anonymous-token checks never matched, so variadic function-pointer
signatures were emitted with a wrong fixed arity and no '...' sentinel
(review finding, #2522). C++ is unaffected ('...' stays an anonymous token
there); the token checks remain for such grammars.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ingestion): emit invoke facts for field-stored callable member calls

The C ops-vtable pattern (o->run = handler; o->run(1)) captured the store
but never the call — the member path in emitCallFacts bailed for languages
without protocol methods, and the value-binding index recorded the member
store under the OBJECT's name ('o'), not the member's ('run') (review
finding 11/M3, #2522). Member destinations now also record their terminal
member name, and a member call whose name-cell has a visible store emits an
indirect invoke — gated on the store so plain accessor calls (map.get)
stay inert.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(cpp): disambiguate (obj->*ptr)() ERROR recovery by token order

tree-sitter-cpp groups the recovered '->*' two ways depending on
error-recovery cost (identifier lengths): [identifier, ERROR '->*m'] or
[ERROR 'obj->*', identifier]. The recovery assumed the first shape, so the
second silently swapped receiver/member and dropped the call site — the
committed test passed only by name luck (review finding H2, #2522). The
identifier's position relative to '->*' inside the ERROR now decides roles.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(cpp): class members are never file-local in hasFileLocalCallableLinkage

The name-keyed file-local set is populated from every static declaration,
so an in-class 'static void make();' (external linkage — in-class static
means no-instance) and any member sharing a name with a static free
function were over-marked, refusing legitimate cross-file
declaration/definition joins (review finding 13/M2, #2522). Method and
Constructor defs now bypass the name-set, per the hook's own linkage-only
contract.

Deviation from plan step 13: the regression is a unit-level contract pin
rather than an end-to-end join test — C++ merges out-of-line member
definitions onto the member node by qualified identity, so the graph shape
cannot discriminate the join refusal for members.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(cpp): classify parameter passing mode from the declarator chain only

A whole-subtree scan for reference_declarator inverted copy vs alias:
void reg(void (*cb)(int& out)) marked the by-value pointer cb as
'reference' because of the NESTED parameter's int&, making the solver
back-propagate formal targets into every caller's argument cell — alias
semantics for a copy (review finding 14/M5, #2522). The chain walk never
descends into nested parameter lists; a reference anywhere ON the chain
(int& x, void (*&cb)(int)) still aliases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ruby): bare identifiers are calls, not callable references

Ruby parses a receiver-less zero-arg method call identically to a variable
read, so 'action = process' — which CALLS process and stores its return —
seeded action with the callable and minted a wrong CALLS edge from any
dispatch through it, confirmed end-to-end (review finding 15/HIGH, #2522).
New provider knob bareNamesAreCalls: a bare name that is not a provably
local value binding and not an explicit reference form (method(:x),
lambda/proc) emits no flow fact, on both the assignment and argument paths.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(go): pair multi-value := positionally instead of cross-wiring

The shared field fallback took the FIRST LHS identifier and the LAST RHS
identifier of Go's expression_list pair, cross-wiring 'a, b := f, g' and
synthesizing a garbage comma-joined qualified name — the real relationships
were silently dropped (review finding 16, #2522). extractAssignment may now
return multiple pairs; Go pairs list entries positionally and emits nothing
for a length mismatch (multi-return call RHS).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(java): drop get/test from callableProtocolMethods

'get' and 'test' collide with ubiquitous non-functional-interface APIs
(Map/List/Optional/Future.get), so every ordinary container access emitted
a spurious callable-object invoke fact — high-volume misleading graph facts
with a cross-wiring risk on receiver-name reuse (review finding 17, #2522).
Supplier.get/Predicate.test dispatch is deliberately traded away until the
check can gate on the receiver's declared type.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(rust): pin the qualified-name no-degrade guard as a hard invariant

Rust's scoped_identifier callable-reference capture over-includes unit enum
variants and associated constants (Shape::Square seeds as if callable);
they stay edge-free only because resolveSeedCandidates refuses to degrade
an unresolved qualified name to a simple-name lookup (review finding 18,
#2522). Capture-side type filtering would false-negative on tuple-variant
constructors, so the guard IS the contract: documented as a hard invariant
(Go's mis-shaped multi-value forms also rely on it) and pinned end-to-end.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(php): remove nonexistent optional_parameter node type

tree-sitter-php has no 'optional_parameter' — defaults ride on
simple_parameter — so the entry was dead weight the #1920 literal gate
does not cover for capture-option Sets (review finding 19, #2522).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(cobol): detect procedure pointers on fixed-format sources

Two stacked defects made the feature a no-op on classic sequence-numbered
fixed format (review finding 20/H3, #2522):
1. parseDataItemClauses' USAGE alternation knew POINTER but not
   PROCEDURE-POINTER/FUNCTION-POINTER, so the dataItems filter was dead.
2. The raw-line fallback scanned UNCLEANED text, where the sequence number
   satisfied the leading digits and the LEVEL NUMBER got captured as the
   pointer name. It now scans preprocessed lines and requires a letter-
   initial name (COBOL data names must contain a letter).
161 COBOL preprocessor/copy-expander tests stay green; free-format matrix
case unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(cobol): skip comment lines in SET seed/copy scans

A commented-out SET (indicator-column '*'/'/' or free-format '*>')
produced a live seed and a false CALLS edge from dead code (review
finding 21/M1, #2522). The scan now skips indicator-column comment lines
and strips inline '*>' tails before matching.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(architecture): document callable-flow-only mode and skipped-key reporting

The Callable-value flow section omitted scopeResolutionEdgeMode:
'callable-flow-only' — a real emit-pipeline branch that suppresses all
ordinary emission for standalone providers (review finding 22, #2522) —
and predated the skipped-key names/stats surfacing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(scope-resolution): correct value-ref resolution attribution and stale pdg-gating comments

The value-ref contract comment claimed MethodRegistry resolution — the
mechanism is the post-finalize findCallableBindingInScope walker owned by
emitPropertyDispatchCalls (resolveReferenceSites skips these sites). Three
'only under --pdg' calleeIdSink comments were falsified by the #2437 gating
change (callee-id-sink.ts's header was updated; these copies were missed).
Review finding 23, #2522.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(ingestion): direct unit coverage for synthesizeCallableFlowCaptures

The 1,100-line shared synthesizer had no test naming it — only downstream
consumers were covered (review finding 24, #2522). Pins seed/invoke/
formal/argument emission, subscript container binding, store-gated member
invokes, produced-value guards, and the bareNamesAreCalls knob over a
minimal options object so assertions target the synthesizer's own
semantics.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(resolvers): deepen shallow-language coverage; fix Kotlin/Swift reassignment gaps it exposed

Adds the COBOL SET x TO y copy-branch scenario and conditional-assignment
scenarios for Kotlin, C#, Swift, and Dart (10 languages previously had one
generic case each — review finding 25, #2522). The new scenarios exposed
two real capture gaps, fixed here:
- tree-sitter-kotlin's 'assignment' node is fieldless, so nested
  reassignments (chosen = ::target inside a block) produced no flow facts;
  Kotlin's extractAssignment now decomposes it positionally.
- tree-sitter-swift fields its assignment as target:/result:, neither in
  the shared fallback's field lists; both added.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(infra): literal-validation gate for callable-capture option Sets

The #1920 gate validates query literals and exported configs but not the
module-private *_CALLABLE_CAPTURE_OPTIONS Sets consumed by the shared
synthesizer — a typo'd node type silently captures nothing (PHP shipped a
dead 'optional_parameter'; review finding 26, #2522). Every <key>NodeTypes
Set literal is now validated against its language's grammar; name-carrying
sets (callableProtocolMethods, memberPointerOperators) are deliberately
outside the contract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(storage): centralize corrupt-fixture casts into makeStoreEntry

The callable-flow store tests scattered 'as unknown as' double-casts per
fixture (review finding 27, #2522; standing no-as-any rule). One typed
helper now owns the single controlled escape hatch for building malformed
serialization-boundary payloads.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore(bench): refresh capture fingerprints after review fixes

python-scope: the committed baseline (8d5c3699) never matched this
branch's code — CI's benchmarks arm was red on the PR head (review
finding 2/HIGH, #2522); regenerated (a99e69ab), scaling 1.04 in budget.
scope-capture: ruby/cpp/swift/java/kotlin drifted from the review-fix
commits (bare-name suppression, passing modes + ->* recovery, assignment
fields, protocol narrowing, positional assignment); all 14 languages
re-verified PASS with ratios <= 1.18 against the 1.5 budget.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore(docs): untrack docs/plans working documents

docs/ is gitignored (local working docs); the plan files were force-added
past the ignore. Untracked from the index only — they stay on disk.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(golden): regenerate captures goldens after callable-flow review fixes

The per-language digest guards (csharp/go/php/python/ruby/rust/swift)
locked the pre-fix capture output; the review-fix series intentionally
changed it — store-gated member invokes, subscript container binding,
Ruby bare-name suppression, Swift assignment fields, positional pairing.
Regenerated with UPDATE_GOLDEN=1; clean verification run 59/59; all other
parity/golden guards (pipeline-graph, spring-route, python parity) pass
untouched at 33/33.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ingestion): prototypes are callees, not callable value cells

The cross-function visibility fix indexed EVERY signature-bearing
declaration as a value binding — including plain function/method
prototypes (void f(int);). Every call to a declared function then became
an indirect invoke, and with emitCanonicalInvokeReference (C/C++) minted a
free-call reference that resolved through the registry, bypassing the
precise passes' two-phase/ambiguity/subobject suppression — eight phantom
CALLS edges in the cpp resolver suite on CI.

Only declarations whose binding identifier sits under a pointer/
parenthesized declarator (callable-typed variables like void (*fp)(int);)
create value cells now. cpp resolver suite 331/331; callable-value-flow +
C/C++ suites 181/181 (the cross-function fp regression still passes); cpp
fingerprint rebaselined, both bench gates PASS.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Gergő Magyar 2026-07-17 17:20:02 +01:00 • committed by GitHub
parent faef5a0b49
commit 8fc4d4d663
75 changed files with 8264 additions and 1265 deletions

View file

@ -201,7 +201,9 @@ Language-agnostic scope-resolution resolver. This is the resolution path for eve
ReferenceIndex
│ emitReceiverBoundCalls ── FIRST
│ emitFreeCallFallback ── THEN
│ emitReferencesViaLookup ── LAST (uses handledSites)
│ emitReferencesViaLookup ── uses handledSites + deferred-site skip set
│ emitPropertyDispatchCalls ── registration USES + conservative CALLS
│ emitCallableValueFlow ── assigned/passed callable invocation CALLS
│ emitImportEdges
▼
KnowledgeGraph (IMPORTS / CALLS / ACCESSES / INHERITS / USES)
@ -210,6 +212,18 @@ Language-agnostic scope-resolution resolver. This is the resolution path for eve
Orchestrator: `runScopeResolution(input, provider)` in `scope-resolution/pipeline/run.ts`.
Pipeline phase: `scopeResolutionPhase` in `scope-resolution/pipeline/phase.ts` — iterates the registered `SCOPE_RESOLVERS` over the worker-serialized `ParsedFile`s. (Per-language `emitScopeCaptures` hooks may reuse a cached Tree via the orchestrator's `treeCache`, but in worker-pool runs that cache is empty — Trees can't cross MessageChannels — so they consume the pre-extracted `ParsedFile` instead; § Performance notes.)
### Callable-value flow
First-class callable values use a language-neutral inclusion analysis in `passes/callable-value-flow.ts`. Providers recognize their own syntax and emit JSON-safe `CallableFlowSite` facts (`seed`, `copy`, `alias`, `address`, `load`, `store`, `formal`, `argument`, and `invoke`) into `ParsedFile`; shared ingestion never branches on a language name. These always-on facts cross workers and the durable parse store, whose schema is bumped whenever their semantic shape changes.
The emit stage defers only invocation sites proven to reference a flow cell. Ordinary receiver/free/reference passes still resolve direct callees first and record exact callee IDs by file/line/column. Property dispatch then runs before callable flow because a property-dispatched wrapper call can seed actual-to-formal propagation. The callable solver consumes those direct targets, propagates callable sets through lexical cells and formals, and emits `CALLS` at the real indirect invocation site with reason `callable-value-flow` (confidence 0.8 for a singleton, 0.7 for a bounded multi-target set).
The solver is flow-insensitive but bounded: dependency-indexed work items rerun only when a cell they read changes; target/address sets cap at 32; a hostile fact graph has a finite work budget; overflow or budget exhaustion emits no partial `CALLS` and produces a structured warning. Lexical shadowing is function/block aware, invocation/constructor results are not reinterpreted as callable designators, and overload selection uses provider-supplied signature metadata. C/C++ additionally associate visible prototypes with unique definitions so actual-to-formal flow crosses translation units; the provider-owned `hasFileLocalCallableLinkage` hook prevents `static` declarations or definitions from leaking across files. C++ member-function pointers preserve parameter/cv shape, keep non-virtual targets exact, and expand virtual targets through `MethodDispatchIndex`/MRO.
Property-key dispatch remains a separate conservative fallback. Its per-key fan-out cap is 32; capped keys synthesize no partial calls and are reported at warning level with language, skipped-key count, dropped key names (bounded), and cap; the count also travels in `RunScopeResolutionStats.propertyDispatchSkippedKeys`.
Standalone (regex-based) providers such as COBOL participate via `ScopeResolver.scopeResolutionEdgeMode: 'callable-flow-only'`: `runScopeResolution` runs for them, but every ordinary emission path — heritage, interface implementations, receiver-bound, free-call fallback, reference/import edges, post-resolution hooks — is gated off, so their legacy phase (e.g. `cobolPhase`) remains the sole owner of structural edges and the callable solver's `CALLS` are purely additive. A callable-flow-only provider whose files emitted no callable facts exits early, before finalize, keeping the opt-in proportional to source scanning.
### Optional CFG/PDG emission (`--pdg`, #2081–#2086)
On a `--pdg` run the parse worker builds a per-function control-flow graph from the tree-sitter AST (`LanguageProvider.cfgVisitor`; TypeScript/JavaScript today) and serializes it onto `ParsedFile.cfgSideChannel` as plain data. Scope-resolution then emits the program-dependence layers from that side-channel **inside Phase 4 of `runScopeResolution`, while the disk-backed ParsedFile store is still live** — the only window where the worker-built CFGs are loaded (the store is cleared right after the phase returns). A standalone post-`mro` phase would read an empty store, so the emit deliberately lives in-phase, mirroring the `applyCaptureSideChannel` pattern. The opt-in is off by default (graph byte-identical), folded into the parse-cache key (a pdg-off warm cache is never reused on a `--pdg` run), and each layer is bounded by a per-function edge cap that logs any dropped edges. All layers are `BasicBlock → BasicBlock` edges in the single `CodeRelation` table, keyed by `type`; there is **no** `Function → BasicBlock` edge — the symbol↔block join is reconstructed from the BasicBlock id prefix + line span. The layers build on each other:
@ -242,6 +256,7 @@ Single interface a language implements to plug into the pipeline. Contract fully
| `collapseMemberCallsByCallerTarget?` | One CALLS edge per (caller, target) instead of per-site — default off |
| `populateNamespaceSiblings?` | Cross-file implicit visibility (compiler-implicit namespace sharing) — default off; ctx carries `treeCache` |
| `hoistTypeBindingsToModule?` | Walk up to Module scope when looking up a method's return-type typeBinding — default off; enable only when bindings are stored at module level |
| `hasFileLocalCallableLinkage?` | Precise internal-linkage predicate used only when joining callable declarations/prototypes to cross-file definitions; C/C++ use it for `static` free functions |
### Per-language registration
@ -273,6 +288,7 @@ CI auto-discovers the set via `tsx`. No workflow edit required.
- **Cross-phase Tree cache**: the orchestrator's `treeCache` (`RunScopeResolutionInput.treeCache`) lets a scope-resolution per-language hook (`emitScopeCaptures`) reuse a tree instead of re-parsing. Workers leave it empty — Trees can't cross MessageChannels — so in normal (worker-pool) runs scope-resolution does NOT rely on it: workers serialize each file's `ParsedFile` (+ capture side-channel) and stream them in, so scope-resolution consumes the pre-extracted artifact rather than re-parsing on the main thread (§ Chunked parse-and-resolve). `PROF_SCOPE_RESOLUTION=1` emits hit/miss counters and a worker-engaged warning.
- **Typed relationship iteration**: heritage + MRO walk only the EXTENDS / IMPLEMENTS / HAS_METHOD edges via `iterRelationshipsByType`, not the full relationship map.
- **Workspace-resolution-index**: O(1) `findOwnedMember` / `findExportedDef` / `classScopeByDefId` built once per run.
- **Callable-value worklist**: dependency-indexed inclusion propagation is linear in a reverse-ordered copy-chain fixture; target/address sets cap at 32 and the whole worklist has a finite budget with no partial edge emission on exhaustion.
- **SCC-ordered cross-file return-type propagation** (PR #1050): `propagateImportedReturnTypes` walks `indexes.sccs` in reverse-topological order (leaves first), so multi-hop alias chains like `models.User → service.user → app.user` collapse to the terminal class in a single linear pass. Within each importer, the source module's `typeBindings` is chain-followed BEFORE mirroring (so we mirror terminal types, not intermediate refs), and the importer's own `typeBindings` is chain-followed AFTER mirroring (so local `const x = importedFn()` resolves before downstream importers run). Cyclic SCCs reach a partial fixpoint within a single pass without iterating to convergence — see the `ts-circular` cross-file-binding fixture which only asserts pipeline-no-throw. PROF output (`PROF_SCOPE_RESOLUTION=1`) splits `finalize` from `propagate` so quadratic regressions in the chain-follow surface independently.
---

View file

@ -1,207 +0,0 @@
---
title: Claude Skill Discovery Paths - Plan
type: fix
date: 2026-07-11
artifact_contract: ce-unified-plan/v1
artifact_readiness: implementation-ready
product_contract_source: ce-plan-bootstrap
execution: code
---
# Claude Skill Discovery Paths - Plan
## Goal Capsule
- **Objective:** Make every Claude Code skill written by `gitnexus analyze` discoverable from the project skill root while preserving skip flags, repeat-run stability, and unrelated user skills.
- **Authority:** GitHub issue #2433 and Claude Code's documented project-skill layout are the behavioral contract; repository guardrails and existing CLI conventions govern implementation.
- **Execution profile:** Standard, test-first bug fix in `gitnexus/`; no dependency, schema, or public MCP changes.
- **Stop conditions:** Stop if the fix requires deleting unrecognized user-owned skill directories, changes `--skip-skills` semantics, or impact analysis reports HIGH/CRITICAL risk without maintainer approval.
- **Tail ownership:** LFG owns simplification, review, commits, PR creation, and CI follow-through after the implementation units pass verification.
---
## Product Contract
### Summary
Install standard and repo-generated Claude Code skills as direct children of `.claude/skills/`, update all generated references and CLI messages to those paths, and migrate known legacy GitNexus outputs without touching unrelated project skills.
### Problem Frame
`gitnexus analyze` currently writes standard skills below `.claude/skills/gitnexus/` and community skills below `.claude/skills/generated/`.
Claude Code treats `.claude/skills/<skill-name>/SKILL.md` as the project-skill shape; nested `.claude/skills/` directories elsewhere in a monorepo are separate discovery roots, not grouping directories inside a skill root.
The current installer therefore reports success and writes managed instructions that point to files, but the skills are not registered for invocation.
### Requirements
**Standard skills**
- R1. Each bundled `gitnexus-*` standard skill is written to `.claude/skills/<skill-name>/SKILL.md`.
- R2. Generated AGENTS.md and CLAUDE.md routing rows reference the same direct standard-skill paths.
**Community skills**
- R3. Each `--skills` community skill is written directly below `.claude/skills/` with a GitNexus-owned name that cannot collide with the six standard skills or ordinary unprefixed project skills.
- R4. Community skill frontmatter, returned metadata, console output, and generated routing rows use one consistent discoverable name and path.
**Migration and compatibility**
- R5. A repeat analyze removes or replaces only legacy directories GitNexus can identify as its own output and preserves unrelated `.claude/skills/` entries.
- R6. `--skip-skills` continues to suppress only the six standard skills, while `--skills` community generation remains independent; `--index-only` continues to suppress all context-file injection.
- R7. CLI help and localized help text describe the corrected paths without changing flag behavior.
- R8. This repository's checked-in copies of the six standard skills and its managed AGENTS.md/CLAUDE.md routing rows use the corrected direct layout when the fix lands.
### Acceptance Examples
- AE1. Given a clean repository, a normal analyze creates `.claude/skills/gitnexus-exploring/SKILL.md`, does not create `.claude/skills/gitnexus/gitnexus-exploring/SKILL.md`, and emits the direct path in AGENTS.md and CLAUDE.md.
- AE2. Given `analyze --skills`, each generated community skill has a direct, namespaced directory below `.claude/skills/`, and the context-file table points to that exact file.
- AE3. Given existing unrelated project skills plus legacy GitNexus grouping directories, rerunning analyze preserves the unrelated skills, produces the direct GitNexus skills, and leaves no managed reference pointing at a legacy grouped path.
- AE4. Given `--skip-skills`, no standard `gitnexus-*` skill is installed or referenced, while generated community skill behavior remains available when `--skills` is also requested.
### Success Criteria
- All standard and generated skill files use Claude Code's documented direct-child layout.
- Generated context, return messages, help text, and tests contain no active references to `.claude/skills/gitnexus/` or `.claude/skills/generated/`.
- The canonical repository no longer ships the six standard skills or managed routing rows in the broken grouped layout.
- Repeated runs are deterministic and do not delete unrelated user skills.
### Scope Boundaries
- **In scope:** project-local Claude Code skill installation performed by `analyze`, repo-generated community skills, managed context paths, the repository's checked-in copies of the six standard skills, CLI/help copy, migration of known legacy outputs, and regression coverage.
- **Out of scope:** global `gitnexus setup` targets, plugin skill layouts, changing the six bundled skill bodies, or changing Claude Code itself.
- **Deferred to follow-up work:** relocating this repository's three extra hand-maintained nested `.claude/skills/gitnexus/` skills that are not installed by `analyze`; those are workspace configuration rather than the issue's six standard installer outputs.
### Sources
- GitHub issue #2433: `https://github.com/abhigyanpatwari/GitNexus/issues/2433`
- Claude Code skills documentation: `https://code.claude.com/docs/en/slash-commands`
- Related path-contract regressions: GitHub issues #1098 and #1381.
---
## Planning Contract
### Key Technical Decisions
- KTD1. Treat `.claude/skills/` as the installation root and make each skill directory its direct child. This matches the official project-skill contract and avoids relying on recursive discovery that Claude Code does not document.
- KTD2. Keep the six standard names unchanged because they are already `gitnexus-*` namespaced. This preserves their intended invocation names while correcting only the filesystem layout.
- KTD3. Reserve a separate GitNexus-owned prefix for generated community skill names before writing them flat. This prevents a community such as `Cli` from overwriting `gitnexus-cli` and prevents common labels such as `auth` from replacing user skills.
- KTD4. Replace grouped-directory cleanup with ownership-bounded cleanup. Remove known standard legacy children and generated legacy output, or direct generated directories carrying the reserved prefix, but never recursively clear `.claude/skills/` itself.
- KTD5. Keep generation and documentation derived from the same `GeneratedSkillInfo.name` value so disk paths, frontmatter names, managed routing rows, and repeat-run cleanup cannot drift.
### Assumptions
- Issue #2433's request to check community skills includes fixing them in this PR rather than filing a separate follow-up.
- Legacy `.claude/skills/generated/` is GitNexus-owned because current code already deletes and recreates it on every `--skills` run; unknown siblings under `.claude/skills/` remain user-owned.
- Standard legacy cleanup is limited to the six bundled names under `.claude/skills/gitnexus/`; unknown children in that grouping directory are preserved.
- The exact generated-skill prefix may be refined during implementation, but it must be stable, GitNexus-owned, direct-child compatible, and non-conflicting with standard skill names.
### Existing Patterns to Follow
- `gitnexus/src/cli/setup.ts` installs globally scoped Claude skills directly under the target skill root and provides a path-contract precedent.
- `gitnexus/src/cli/ai-context.ts` already centralizes standard skill definitions, context table generation, skip semantics, and best-effort filesystem handling.
- `gitnexus/src/cli/skill-gen.ts` already owns community-name normalization, deterministic collision suffixes, output cleanup, frontmatter rendering, and returned path metadata.
- `gitnexus/test/unit/ai-context.test.ts` uses temporary repositories to prove file layout and skip-mode behavior.
- `gitnexus/test/unit/skill-gen.test.ts` and `gitnexus/test/integration/skills-e2e.test.ts` cover generated skill metadata, file contents, idempotency, and end-to-end context references.
### System-Wide Impact
The change affects the user-visible filesystem contract of `gitnexus analyze`, generated AGENTS.md/CLAUDE.md content, CLI help output, and the invocation names of previously inert community skills.
It does not alter indexing, graph storage, MCP APIs, global setup targets, or runtime analysis behavior.
### Risks and Mitigations
- **Accidental user-skill deletion:** Scope cleanup to known standard names, the prior generated output directory, and the new reserved prefix; add preservation tests with unrelated directories.
- **Standard/community collision:** Use distinct namespaces and assert representative `Cli`/common-label cases.
- **Path drift across surfaces:** Derive context rows from returned generated names and assert exact disk-to-doc parity.
- **Skip-mode regression:** Retain focused tests for normal, `--skip-skills`, `--skills`, and `--index-only` combinations.
---
## Implementation Units
### U1. Flatten standard skill installation and managed references
- **Goal:** Install the six bundled skills as direct project skills and migrate only their known legacy copies.
- **Requirements:** R1, R2, R5, R6; AE1, AE3, AE4.
- **Dependencies:** None.
- **Files:** `gitnexus/src/cli/ai-context.ts`, `gitnexus/test/unit/ai-context.test.ts`.
- **Approach:** Change the standard install root and routing-table templates together; preserve `skipSkills` behavior and result reporting; add bounded cleanup for the six known legacy child directories while preserving unknown siblings and unrelated direct skills.
- **Execution note:** Start with failing temporary-repository assertions for the direct path, absence of the legacy path, preservation of unrelated skills, and repeated-run behavior.
- **Patterns to follow:** Existing `installSkills`, `generateGitNexusContent`, and temporary-directory tests in `ai-context.test.ts`.
- **Test scenarios:**
- Covers AE1. A default run writes all six direct skill files and emits the same direct paths in both context files.
- Covers AE3. A run with an unrelated direct skill and an unknown legacy-group child preserves both while replacing known legacy standard children.
- Covers AE4. `skipSkills` writes no standard direct skill, emits no standard routing row, and reports the corrected skipped location.
- A second default run produces the same six skills without duplicate directories or context rows.
- **Verification:** Focused AI-context tests prove the filesystem, managed-document, migration, and skip contracts.
### U2. Flatten and namespace generated community skills
- **Goal:** Make `--skills` outputs discoverable without colliding with standard or user-authored skills.
- **Requirements:** R3, R4, R5, R6; AE2, AE3, AE4.
- **Dependencies:** U1 establishes the shared direct-root convention.
- **Files:** `gitnexus/src/cli/skill-gen.ts`, `gitnexus/test/unit/skill-gen.test.ts`, `gitnexus/test/integration/skills-e2e.test.ts`, `gitnexus/test/unit/analyze-no-stats-bridge.test.ts`, `gitnexus/test/unit/analyze-gitnexusrc.test.ts`.
- **Approach:** Generate reserved, deterministic community names; write each directory directly under `.claude/skills/`; clean only legacy generated output and stale directories in the reserved namespace; return and render the direct path consistently; update mocked path fixtures that model the output contract.
- **Execution note:** Characterize existing name normalization and idempotency first, then add red tests for a community label that would collide with a standard or common user skill.
- **Patterns to follow:** `toKebabName`, `renderSkillMarkdown`, and existing repeat-run tests.
- **Test scenarios:**
- Covers AE2. A representative community produces a direct namespaced directory whose basename equals frontmatter `name` and returned metadata `name`.
- A `Cli` community does not overwrite the standard `gitnexus-cli` skill.
- A pre-existing unrelated `.claude/skills/auth/SKILL.md` survives generation of an Auth community.
- Covers AE3. A repeat run removes stale GitNexus-generated community directories and the legacy `generated/` output while preserving unrelated direct skills.
- The end-to-end `analyze --skills` fixture finds generated files at direct paths and context tables point to those exact paths on first and second runs.
- **Verification:** Unit and integration tests prove collision resistance, ownership-bounded cleanup, path/frontmatter parity, and deterministic regeneration.
### U3. Align CLI help and path-contract assertions
- **Goal:** Remove stale user-facing descriptions of grouped skill directories and lock the corrected contract into CLI coverage.
- **Requirements:** R7 and the active-reference portion of R2/R4.
- **Dependencies:** U1 and U2 determine the final standard and generated naming conventions.
- **Files:** `gitnexus/src/cli/index.ts`, `gitnexus/src/cli/i18n/zh-CN.ts`, `gitnexus/test/unit/skip-git-cli.test.ts`, `gitnexus/test/unit/ai-context.test.ts`, `gitnexus/test/integration/skills-e2e.test.ts`.
- **Approach:** Update English and Chinese help copy and strengthen existing help/context assertions so legacy grouped paths fail tests if reintroduced.
- **Patterns to follow:** Existing Commander option descriptions, `help.option.analyze.*` translation keys, and `skip-git-cli.test.ts` help assertions.
- **Test scenarios:**
- `gitnexus analyze --help` names the direct standard location and the reserved direct community naming convention.
- Generated AGENTS.md and CLAUDE.md contain no active `.claude/skills/gitnexus/` or `.claude/skills/generated/` routing entries.
- Chinese help retains the same flag semantics while naming corrected locations.
- **Verification:** Focused CLI/help tests and repository search confirm stale active path copy is gone from changed runtime and test surfaces.
### U4. Align the repository's checked-in standard skills
- **Goal:** Ensure the canonical GitNexus checkout demonstrates the same discoverable layout the corrected analyzer produces.
- **Requirements:** R8 and the repository-facing portion of R2.
- **Dependencies:** U1 establishes the standard direct paths.
- **Files:** `.claude/skills/gitnexus-exploring/SKILL.md`, `.claude/skills/gitnexus-debugging/SKILL.md`, `.claude/skills/gitnexus-impact-analysis/SKILL.md`, `.claude/skills/gitnexus-refactoring/SKILL.md`, `.claude/skills/gitnexus-guide/SKILL.md`, `.claude/skills/gitnexus-cli/SKILL.md`, `AGENTS.md`, `CLAUDE.md`.
- **Approach:** Relocate exactly the six analyzer-installed standard skill directories from the grouped path to direct children and update only their managed routing rows; preserve the extra hand-maintained nested skills unchanged.
- **Patterns to follow:** The direct paths produced by U1 and the existing GitNexus-managed block markers in AGENTS.md and CLAUDE.md.
- **Test scenarios:** Test expectation: none -- this unit relocates checked-in skill assets without changing their bodies; repository search and the focused path-contract tests cover their discoverability contract.
- **Verification:** Each of the six direct files exists with unchanged content, the six legacy grouped copies are absent, the three extra nested skill directories remain, and both managed tables point to the direct files.
---
## Verification Contract
| Gate | Command | Proves |
|---|---|---|
| Focused standard installer | `cd gitnexus && npx vitest run test/unit/ai-context.test.ts` | Direct standard paths, managed rows, migration safety, skip flags |
| Focused community generator | `cd gitnexus && npx vitest run test/unit/skill-gen.test.ts` | Namespacing, collision handling, cleanup, metadata/frontmatter parity |
| CLI help | `cd gitnexus && npx vitest run test/unit/skip-git-cli.test.ts` | User-facing flag path contract |
| Community end to end | `cd gitnexus && npx vitest run test/integration/skills-e2e.test.ts` | Real analyze output and repeat-run references across fixtures |
| CLI/Core regression | `cd gitnexus && npm test` | Full package behavior |
| Type safety | `cd gitnexus && npx tsc --noEmit` | TypeScript contract integrity |
| Change scope | GitNexus `detect_changes` before each commit | Only expected CLI skill-generation symbols and flows are affected |
---
## Definition of Done
- U1-U3 requirements and test scenarios pass.
- U4's checked-in relocation and managed-row verification pass.
- Standard and community skills are direct children of `.claude/skills/` and discoverable by documented Claude Code rules.
- No runtime or generated-document surface points to the two legacy grouping layouts.
- Unrelated user-authored skills and unknown legacy-group children are preserved by regression tests.
- `--skip-skills`, `--skills`, and `--index-only` retain their documented independence.
- Full `gitnexus` tests and typecheck pass, or any environment-only exception is documented with focused proof.
- GitNexus change detection reports only the expected CLI generation and test scope.
- Abandoned experimental code and temporary artifacts from implementation are absent from the final diff.

View file

@ -1,228 +0,0 @@
# GitNexus Engineering Plan
> Task: Fix #2432 — `analyze` aborts with `Napi::Error` SIGABRT on triton-lang/triton: pathological C++ capture extraction triggers worker timeouts, then worker termination lands mid-native-call.
> Evidence verified at commit 737a8cdb; GitNexus index refreshed this session (`node .gitnexus/run.cjs analyze --index-only --pdg`, 230,253 nodes / 487,362 edges). Deepened same session: assumption A1 empirically refuted (see §5a/§12); §6-C redesigned accordingly. (Note: the MCP context resource still displays a stale banner after refresh — tools serve the refreshed data; PDG queries succeed. Cosmetic cache issue, recorded in §12.)
## 1. Objective
`gitnexus analyze` on triton (repro: `GITNEXUS_MAX_FILE_SIZE=5120 … analyze --worker-timeout 60`) must complete without SIGABRT. Two stacked defects, both fixed:
1. **Perf (trigger):** C++ scope-capture extraction is O(calls × args × treeSize) per file — `lib/Dialect/TritonInstrument/IR/FunctionBuilder.cpp` (194 KB, parses in 46 ms) burns **151 s** in the worker; `hip_prof_str.h` (`.h` → cpp provider) burns 116 s. Measured via `--cpu-prof` on the real dist worker `[verified]`.
2. **Crash (abort):** terminating a worker thread that is inside an N-API call — whether via pool shutdown, breaker trip, **or plain process exit** — makes the pending `Napi::Error` escape as an uncaught C++ exception → `std::terminate` → SIGABRT kills the whole CLI (workers are `worker_threads`, shared process). Reproduced 2/2 on origin/main, exit 134 `[verified]`; process-exit variant reproduced directly `[verified]` (§5a).
Acceptance criteria: triton repro completes (exit 0); `FunctionBuilder.cpp` extraction drops from ~151 s to sub-second; no worker-pool or cpp-resolver test regressions.
## 2. Current Behaviour
Per-file worker flow (`parse-worker.ts` `processFileGroup`): parse → `query.matches` → `extractParsedFile` → provider `emitScopeCaptures` (`emitCppScopeCaptures` for `.cpp`/`.h` — `c-cpp.ts:435` maps both) `[verified]`.
For every call-expression capture, `inferCppCallArgTypeClasses` (`cpp/captures.ts:929`, driven from `:325`) classifies each identifier argument via:
- `lookupDeclaredTypeClassForIdentifier` (`:1133`) — linear scan of the enclosing scope's children per identifier `[verified]`;
- `lookupFunctionParameterTypeClass` (`:1182`) + `findEnclosingFunctionParameter` (`:1200`) — walk up + param scan per identifier `[verified]`;
- **`isKnownEnumName` (`:1248`) — walks to the AST root and full-tree DFS for `enum_specifier`, per identifier argument** `[verified]`. CPU profile: 87.7 s of 149.9 s inside it; self-time dominated by tree-sitter N-API accessors (`child`/`childCount`/`type`/`unmarshalNode`) `[verified]`.
Crash path: idle-timeout/give-up paths all pass `'retire'` → `retireWorkerAfterTimeout` (`worker-pool.ts:1188`) defers terminate until the worker posts `sub-batch-done`/`result`/`error` (the #1848 fix) `[verified]`. But:
- `parse-impl.ts:1123-1125` `finally { await workerPool?.terminate() }` → pool `terminate` (`worker-pool.ts:2025` awaits) → `terminateTrackedWorkers` (`:971-980`) — terminates every live AND retired worker unconditionally `[verified]`.
- `tripBreaker` (`:1303`) fire-and-forgets the same (`void terminateTrackedWorkers`, `:1312`) on breaker trip — including live workers that may be mid-native-parse `[verified]`.
- These are the ONLY two callers `[verified]` (grep; matches the graph's d=1).
- **Existing test `worker-pool-timeout-retire.test.ts:97` asserts the crash-causing contract**: a never-safe retired mock worker gets `terminateCalls === 1` after `pool.terminate()` (`:114-118`); `:155` asserts the same for breaker trips `[verified]`. Both flip intentionally under this fix.
- Run evidence: process died 12 s after the last retire log with no error-path output; the 2-file mini corpus (retired workers finish before shutdown) exits 0 `[verified]`.
## 3. Relevant Architecture
- Shared ingestion pipeline is language-agnostic (AGENTS.md): the fix stays inside the cpp language module (`languages/cpp/captures.ts`) and the generic worker pool; no `LanguageProvider` interface change.
- Precedent for exactly this bug class: Go scope-capture re-walk fix (#1848/#1915), Python (#1918), C++ ADL once-built index (#1990) — `languages/c/captures.ts:39-42` documents the pattern `[verified]`.
- The C provider has its own thin `emitCScopeCaptures` (164 lines, no arg-type-class inference) — not affected `[verified]`.
- Workers cluster (76 symbols, 65% cohesion) is self-contained; depth-3 upstream impact of the shutdown change stays entirely inside it `[graph]`.
- `parse-worker.ts:1362-1369` documents the group-catch trap: a throw escaping per-file processing makes the language-group catch drop every remaining file — any new bail path must be caught per-file, never thrown outward `[verified]`.
## 4. GitNexus Findings
- `impact {target: emitCppScopeCaptures, direction: upstream, maxDepth: 2}` → 0 dependents, LOW `[graph]`. **Graph/source discrepancy:** the real consumer is the provider-hook indirection (`c-cpp.ts:495 emitScopeCaptures: emitCppScopeCaptures` → `scope-extractor-bridge.ts:41 extractParsedFile`) which the call graph doesn't model. Source wins; internal signature changes are still safe (all hot functions are file-private).
- `impact {target: terminateTrackedWorkers, direction: upstream}` at depth 2 → d=1: `tripBreaker`, `terminate`; deepened at `maxDepth: 3, summaryOnly` → 13 symbols total (d1:2, d2:7, d3:4), risk LOW, all in the Workers module. Key output: `"direct": 2` — both d=1 dependents modified deliberately in §6-C and source-confirmed `[verified]`.
- Related tests located and read: `test/unit/worker-pool-timeout-retire.test.ts` (mock `TimeoutThenHealthyWorker` harness with `terminateCalls`/`unrefCalls` counters and a `delayed-safe-return` mode — supports the new scenarios without factory changes `[verified]`), `test/integration/resolvers/cpp.test.ts` + `c.test.ts` (golden equivalence gate), `test/integration/cpp-adl-benchmark.test.ts` (GITNEXUS_BENCH-gated; template for the new benchmark) `[verified]`.
## 5. Statement-Level PDG Findings
- `pdg_query {mode: controls, target: isKnownEnumName}` (28 edges): the DFS body (`:1253-1262`) is control-dependent only on the trivial `typeName === ''` guard (`:1249`, guard:true) and its own loop conditions — **no memoization or early-exit gate exists**; the full-tree walk runs unconditionally on every call `[graph]`, consistent with source `[verified]`.
- `pdg_query {mode: controls, target: terminateTrackedWorkers}` → 0 edges: straight-line, unconditional termination of both worker lists `[graph]`. The crash fix is precisely "add the missing control dependency" (safe-point gate).
- Performance-mode scan: the hot loop's N-API fan-out (`cur.child(i)` per node per DFS per identifier) is the marshalling hotspot (`unmarshalNode` 15.5 s incl.) `[verified via profile]`.
- Ordering constraint: `lookupDeclaredTypeClassForIdentifier` returns the **first** matching `declaration` in scope-child order, with no position filtering relative to the identifier — the replacement index must preserve first-declaration-wins and must NOT introduce use-before-decl filtering `[verified]`.
## 5a. Deepen finding — A1 refuted empirically
Driver test (`exit-with-busy-worker.mjs`, kept in scratchpad): main thread `process.exit(0)` five seconds into the real dist worker's extraction of `FunctionBuilder.cpp`, worker `unref()`d → **process aborts: `terminate called after throwing an instance of 'Napi::Error'`, exit 134** `[verified]`. Node tears down worker environments on process exit through the same terminate path. Consequence: "skip terminating unsafe workers and let the process exit" merely relocates the abort. The shutdown design must instead guarantee workers reach a JS safe point in bounded time before the process exits — this promotes the previously-deferred cooperative extraction deadline into scope (§6-D).
## 6. Proposed Changes
**A. Perf root-cause — per-file lookup index in `gitnexus/src/core/ingestion/languages/cpp/captures.ts`.**
Introduce a lazily-built, per-invocation index object created at the top of `emitCppScopeCaptures` and threaded through `inferCppCallArgTypes` / `inferCppCallArgTypeClasses` → the lookup helpers (all file-private; no exported API change):
- `enumNames: Set<string>` — built by ONE root DFS on first `isKnownEnumName` query (lazy: files with no identifier args pay nothing). `isKnownEnumName` becomes a Set lookup. Behavior-identical: current code matches any `enum_specifier` name anywhere in the translation unit.
- `scopeDecls: Map<number /* scope.id */, Map<string, {typeNode, nameChild, stmt}>>` — per-scope declaration map built on first lookup in that scope by one pass over `scope` children, first-declaration-wins (skip existing keys). Replaces the per-identifier linear scans in `lookupDeclaredTypeClassForIdentifier` / `lookupDeclaredTypeForIdentifier` (`:1090-1131`).
- `fnParams: Map<number /* function node.id */, Map<string, param>>` — same treatment for `findEnclosingFunctionParameter`.
Complexity: O(treeSize + identifiers) per file. Expected: 151 s → sub-second (parse itself is 46 ms). `classifyCppParameterType` / `normalizeCppTypeText` stay per-hit (cheap; memoizing them changes nothing observable).
**B. New benchmark test — `gitnexus/test/integration/cpp-captures-typeclass-benchmark.test.ts`** modeled exactly on `cpp-adl-benchmark.test.ts` (`describe.skipIf(!GITNEXUS_BENCH)`): synthetic C++ file scaling call-sites × enums, asserts sub-quadratic scaling of the capture-emit phase.
**C. Crash fix — safe-point-gated shutdown with bounded drain, `gitnexus/src/core/ingestion/workers/worker-pool.ts`.** (Redesigned after §5a.)
- **C1 (gate):** extend `RetiredWorkerRecord` with `safeToTerminate`, set exactly where `terminateWhenBackInJs` fires today (`onRetiredMessage` for `sub-batch-done`/`result`/`error`, and `messageerror`). `terminateTrackedWorkers` terminates retired records only when safe; unsafe records keep their armed at-safe-point terminate listener.
- **C2 (bounded drain):** pool `terminate()` awaits unsafe retired records' safe-point terminate up to a cap (`GITNEXUS_WORKER_SHUTDOWN_DRAIN_MS`, default ≈ 30 s — comfortably above D's per-file deadline so the drain converges for the known class). On cap expiry: log a clear diagnostic naming the wedged worker + in-flight file and proceed (residual abort risk at process exit remains for truly-wedged native code, now rare and diagnosed). The breaker path (`tripBreaker:1312`) stays fire-and-forget — it must never block the dispatch rejection; its unsafe records drain when the pipeline's `finally` runs pool `terminate()`.
- **C3 (breaker-path live workers):** on breaker trip, live workers in `busySlots` (`:1154` `[verified]`) are routed through `retireWorkerAfterTimeout` instead of direct `terminate()` — same mid-native abort risk, same cure. Idle live workers terminate directly (parked in the JS event loop; safe). The normal post-parse `terminate()` still direct-terminates live workers — all idle by construction (jobs drained).
**D. Cooperative extraction deadline (promoted from deferred Q2 by §5a) — `cpp/captures.ts`.**
Bound per-file wall time inside `emitCppScopeCaptures`'s match loop: check `Date.now()` against a soft budget (`GITNEXUS_CPP_CAPTURE_BUDGET_MS`, default ≈ 20 s; post-A one iteration is microseconds, so check granularity of every N=64 matches is ample). On breach: **return** partial captures accumulated so far + `reportWarning` naming the file — never throw (the group-catch trap, §3). This guarantees cpp extraction returns to JS in bounded time, which is what makes C2's drain converge and process exit safe. Generic all-language budget remains a deferred follow-up (§12).
## 7. Implementation Sequence
1. **cpp captures index (A).** Build the index type + lazy constructors; convert `isKnownEnumName`, `lookupDeclaredType{Class}ForIdentifier`, `lookupFunctionParameterType{Class}`, `findEnclosingFunctionParameter`; thread from `emitCppScopeCaptures`. Gate: `npx vitest run test/integration/resolvers/cpp.test.ts test/integration/resolvers/c.test.ts` passes unchanged.
2. **Benchmark (B).** Add the GITNEXUS_BENCH-gated benchmark; record before/after in the PR body (before: 151 s / 116 s from this plan).
3. **Extraction deadline (D).** Budget check + partial-return + warning; unit test with a tiny budget forcing the bail (assert warning emitted, remaining files in group still processed).
4. **Worker-pool shutdown safety (C1–C3).** Gate + drain + breaker routing. Update `worker-pool-timeout-retire.test.ts:97` and `:155` (both currently assert the buggy contract) and add: (i) `pool.terminate()` with a never-safe retired worker + tiny drain cap → resolves after cap, `terminateCalls === 0`, diagnostic logged; (ii) retired worker signals safe during drain → terminated, `terminate()` resolves promptly; (iii) breaker trip with busy live worker → retired, not direct-terminated.
5. **End-to-end validation.** Rebuild (`npm run build`); re-run the triton repro → exits 0, `FunctionBuilder.cpp` indexed (not quarantined); mini 2-file corpus completes in seconds; re-run the §5a exit-with-busy-worker driver against the built worker with D's budget lowered → clean exit.
Steps 1–2 alone de-trigger #2432; 3–4 close the abort class. Each step leaves the tree green.
## 8. Test Strategy
- **Update:** `worker-pool-timeout-retire.test.ts:97` + `:155` — expectations flip to the new contract (unsafe ⇒ not terminated at shutdown; terminated at safe point). The mock harness supports this as-is `[verified]`.
- **Add:** benchmark (§6-B); three shutdown cases (§7-4); deadline-bail unit test (§7-3).
- **Regression:** resolver goldens `cpp.test.ts`/`c.test.ts` unchanged (equivalence gate); full `npm run test:unit`; `npm run test:integration` (carries its build via `pretest:integration`).
- **Edge cases:** file with enums but no calls (lazy index never built); duplicate declaration in one scope (first-wins preserved); use-before-decl in scope (still resolved — no position filter); anonymous enums (name-less `enum_specifier` excluded, same as today); breaker trip with mixed busy/idle live workers; drain cap = 0 (immediate proceed); deadline breach mid-file (partial captures kept, group continues).
- **Failure paths:** shutdown never hangs (drain is capped); deadline bail is a warning, never a group-dropping throw (§3 trap).
- **Verification commands (verified to exist):** `npm run build`, `npm run test:unit`, `npm run test:integration`, `GITNEXUS_BENCH=1 npx vitest run test/integration/cpp-captures-typeclass-benchmark.test.ts` — all from `gitnexus/`.
## 9. Risk and Impact Analysis
- **d=1 dependents of `terminateTrackedWorkers`** — `tripBreaker` (`:1312`), `terminate` (`:2025`): both modified deliberately; no other callers `[verified]`. Depth-3 radius stays pool-internal (13 symbols, Workers module) `[graph]`.
- **Behavioral-equivalence risk (A):** first-declaration-wins + position-free matching must be preserved (§5). Mitigation: resolver goldens + explicit edge cases.
- **Node identity:** key maps by `SyntaxNode.id` (stable within a tree); wrapper object identity is NOT usable (wrappers are recreated per access — a `WeakMap` would silently fail).
- **Drain-cap tuning (C2 vs D):** drain cap must exceed D's budget or the drain can expire while a worker is legitimately finishing its bailed file — defaults 30 s vs 20 s encode that; both env-tunable, relation asserted in a unit test comment.
- **Residual abort window:** a worker wedged in native code longer than the drain cap still aborts at process exit — now requires non-cpp pathological input (D bounds cpp) and is logged with the culprit file before it can happen. Accepted; full elimination needs child-process workers (out of scope, §12).
- **`emitCppScopeCaptures` consumers:** provider hook only; signature unchanged (D's budget read from env inside the module) — zero external surface.
- **Deadline false positives (D):** 20 s default is ~3 orders of magnitude above post-A extraction cost of the worst observed file; breach ⇒ degraded coverage for that file (warning), never a failed run.
- **Coverage change:** triton's `FunctionBuilder.cpp` was previously quarantined; post-fix it indexes — strictly an improvement.
- **Concurrency:** the new index and deadline state are function-scoped per invocation (per file, per worker thread) — no shared state, no `clearCaches()` interaction.
## 10. Files Expected to Change
| File | Symbols | Reason |
|---|---|---|
| `gitnexus/src/core/ingestion/languages/cpp/captures.ts` | `emitCppScopeCaptures`, `inferCppCallArgTypes`, `inferCppCallArgTypeClasses`, `lookupDeclaredType{Class}ForIdentifier`, `lookupFunctionParameterType{Class}`, `findEnclosingFunctionParameter`, `isKnownEnumName` (+ index type, + deadline) | A: O(n²)→O(n) index; D: bounded extraction |
| `gitnexus/src/core/ingestion/workers/worker-pool.ts` | `RetiredWorkerRecord`, `retireWorkerAfterTimeout`, `terminateTrackedWorkers`, `terminate`, `tripBreaker` | C1–C3: safe-point gate + bounded drain + breaker routing |
| `gitnexus/test/unit/worker-pool-timeout-retire.test.ts` | `:97`, `:155` + 3 new cases | New shutdown contract |
| `gitnexus/test/integration/cpp-captures-typeclass-benchmark.test.ts` | new | Scaling regression gate |
| `gitnexus/test/unit/` (new file) | cpp capture deadline-bail | D coverage |
## 11. Reusable Implementation Context
```yaml
implementation_context:
task_summary: >
Fix #2432 (SIGABRT on triton analyze): (A) replace per-identifier full-tree/
per-scope AST re-walks in cpp capture extraction with a lazily-built per-file
index; (C) gate worker terminate on a JS-safe-point flag with a bounded
shutdown drain and breaker-path retire routing; (D) bound cpp capture
extraction wall-time per file (partial-return + warning, never throw).
acceptance_criteria:
- "Triton repro (avoid[4] artifacts) exits 0, no Napi::Error abort"
- "FunctionBuilder.cpp capture extraction sub-second (was 151s)"
- "resolvers/cpp.test.ts + worker-pool suites green"
- "exit-with-busy-worker driver (avoid[4]) exits cleanly against built worker"
primary_symbols:
- { symbol: isKnownEnumName, file: gitnexus/src/core/ingestion/languages/cpp/captures.ts, lines: "1248-1265", role: "full-tree DFS per identifier — replace with per-file enum-name Set" }
- { symbol: lookupDeclaredTypeClassForIdentifier, file: gitnexus/src/core/ingestion/languages/cpp/captures.ts, lines: "1133-1172", role: "per-identifier scope scan — replace with per-scope decl map; preserve first-wins, position-free" }
- { symbol: lookupFunctionParameterTypeClass, file: gitnexus/src/core/ingestion/languages/cpp/captures.ts, lines: "1182-1224", role: "per-identifier param walk — memoize per function node.id" }
- { symbol: inferCppCallArgTypeClasses, file: gitnexus/src/core/ingestion/languages/cpp/captures.ts, lines: "929-1010", role: "per-call driver — threads the index down; call sites at 311/325" }
- { symbol: emitCppScopeCaptures, file: gitnexus/src/core/ingestion/languages/cpp/captures.ts, lines: "15-", role: "per-file entry — owns index lifetime + D deadline checks in its match loop" }
- { symbol: terminateTrackedWorkers, file: gitnexus/src/core/ingestion/workers/worker-pool.ts, lines: "971-980", role: "add safeToTerminate gate (C1); callers: tripBreaker :1312 (void), terminate :2025 (await) — the only two" }
- { symbol: retireWorkerAfterTimeout, file: gitnexus/src/core/ingestion/workers/worker-pool.ts, lines: "1188-1245", role: "set safeToTerminate where terminateWhenBackInJs fires; unref already at :1240" }
- { symbol: tripBreaker, file: gitnexus/src/core/ingestion/workers/worker-pool.ts, lines: "1303-1315", role: "C3: retire busySlots members instead of direct terminate; stays fire-and-forget" }
- { symbol: "pool terminate", file: gitnexus/src/core/ingestion/workers/worker-pool.ts, lines: "~2010-2027", role: "C2: bounded drain of unsafe records before/instead of force terminate" }
related_symbols:
- { symbol: extractParsedFile, relationship: "CALLS emitScopeCaptures via provider hook", relevance: "graph-invisible consumer; signature unchanged" }
- { symbol: "parse-impl.ts:1124 finally", relationship: CALLS, relevance: "the shutdown trigger; C2 drain runs under this await" }
- { symbol: "c-cpp.ts:435 extensions", relationship: config, relevance: ".h routes to cpp provider — hip_prof_str.h covered by A+D" }
- { symbol: busySlots, relationship: "state read by C3", relevance: "worker-pool.ts:1154; add/delete sites verified at :1631/:1646/:1676/:1687/:1720/:1814" }
execution_path:
- "worker: parse file → query.matches → extractParsedFile → emitCppScopeCaptures"
- "per call capture: inferCppCallArgTypeClasses → per identifier: scope scan + full-tree enum DFS (hot)"
- "worker exceeds idle timeout → retire (no terminate) → parse ends → parse-impl finally → pool.terminate → terminateTrackedWorkers → terminate mid-N-API → SIGABRT"
- "ALSO: process exit with native-busy unref'd worker → same abort (verified) — why C2+D exist"
pdg_constraints:
- description: "isKnownEnumName full-tree DFS gated only by typeName!=='' (guard, line 1249); no memo gate exists"
affected_statements: ["gitnexus/src/core/ingestion/languages/cpp/captures.ts:1253-1262"]
implementation_consequence: "Set lookup is behavior-identical; keep the empty/'unknown' early-out"
- description: "terminateTrackedWorkers is straight-line (0 CDG edges) — terminates unconditionally"
affected_statements: ["gitnexus/src/core/ingestion/workers/worker-pool.ts:975-977"]
implementation_consequence: "add safeToTerminate control dependency; drain bounded, never unbounded await"
- description: "lookupDeclaredTypeClassForIdentifier: first-declaration-wins, position-free scope match"
affected_statements: ["gitnexus/src/core/ingestion/languages/cpp/captures.ts:1148-1170"]
implementation_consequence: "build per-scope map in child order, skip existing keys, no use-before-decl filtering"
architectural_patterns:
- { pattern: "once-built per-file index over repeated AST walks", example_location: "gitnexus/src/core/ingestion/languages/c/captures.ts:39-42 (comment citing go #1848 / python #1918); ADL index #1990", usage_guidance: "thread an index object; key node maps by SyntaxNode.id, never object identity" }
- { pattern: "GITNEXUS_BENCH-gated scaling benchmark", example_location: "gitnexus/test/integration/cpp-adl-benchmark.test.ts", usage_guidance: "copy harness shape incl. skipIf + table output" }
- { pattern: "mock-Worker retire harness", example_location: "gitnexus/test/unit/worker-pool-timeout-retire.test.ts:12-64", usage_guidance: "TimeoutThenHealthyWorker: terminateCalls/unrefCalls counters + 'delayed-safe-return' mode cover all new cases; no factory change needed" }
- { pattern: "per-file bail must not throw", example_location: "gitnexus/src/core/ingestion/workers/parse-worker.ts:1362-1369 (CFG isolation comment)", usage_guidance: "D returns partial captures + reportWarning; a throw drops the whole language group" }
files_to_modify:
- { file: gitnexus/src/core/ingestion/languages/cpp/captures.ts, symbols: [see primary], intended_change: "A index + D deadline" }
- { file: gitnexus/src/core/ingestion/workers/worker-pool.ts, symbols: [see primary], intended_change: "C1 gate, C2 drain, C3 breaker routing" }
- { file: gitnexus/test/unit/worker-pool-timeout-retire.test.ts, symbols: [], intended_change: "flip :97/:155 + 3 new cases" }
- { file: gitnexus/test/integration/cpp-captures-typeclass-benchmark.test.ts, symbols: [], intended_change: "new benchmark" }
tests:
- file: gitnexus/test/unit/worker-pool-timeout-retire.test.ts
scenarios:
- "never-safe retired worker + tiny drain cap → pool.terminate() resolves after cap, terminateCalls === 0, diagnostic logged"
- "retired worker signals safe during drain → terminated, terminate() resolves promptly"
- "breaker trip with busy live worker → routed through retire, not direct terminate"
- "UPDATED :97/:155 — unsafe workers not terminated at shutdown (was: terminated)"
- file: gitnexus/test/integration/cpp-captures-typeclass-benchmark.test.ts
scenarios: ["N call-sites × M enums synthetic file → capture emit scales sub-quadratically"]
- file: "gitnexus/test/unit/ (new: cpp capture deadline test)"
scenarios: ["GITNEXUS_CPP_CAPTURE_BUDGET_MS=1 on a many-call file → partial captures returned, warning emitted, no throw"]
- file: gitnexus/test/integration/resolvers/cpp.test.ts
scenarios: ["existing golden behavior unchanged (equivalence gate — run, don't modify)"]
verification_commands:
- "cd gitnexus && npm run build"
- "cd gitnexus && npm run test:unit"
- "cd gitnexus && npm run test:integration"
- "cd gitnexus && GITNEXUS_BENCH=1 npx vitest run test/integration/cpp-captures-typeclass-benchmark.test.ts"
risks:
- "equivalence break in decl ordering (first-wins) → resolver goldens catch"
- "drain cap must exceed D budget (30s > 20s) or drains expire on legitimately-bailing workers"
- "SyntaxNode object identity is NOT stable — key by node.id"
- "residual abort: non-cpp native wedge longer than drain cap still aborts at exit — logged, accepted (child-process workers out of scope)"
assumptions:
- "D's env-read (GITNEXUS_CPP_CAPTURE_BUDGET_MS) is visible in worker threads — CHECK: workers inherit process.env by default; confirm no env filtering in spawnWorker (worker-pool.ts:909-925 sets only workerData/resourceLimits — none seen)"
open_questions:
- "Q2 (narrowed): generic all-language extraction budget — deferred follow-up issue after cpp-only D lands"
avoid:
- "Do not repeat full repository discovery — symbols and line ranges verified at 737a8cdb"
- "Do not change LanguageProvider or emitScopeCaptures signatures — provider hook consumers are graph-invisible"
- "Do not add position/use-before-decl filtering to scope lookups — changes resolution behavior"
- "Repro artifacts in scratchpad: repro-2432-wt (worktree), triton/, mini-2432/, repro-run{1,2}.log, profiles/CPU.*.cpuprofile, profile-worker.mjs, prof-top.mjs, exit-with-busy-worker.mjs — reuse for §7-5, do not re-derive"
- "Do not let a D bail throw out of emitCppScopeCaptures — the language-group catch drops all remaining files (parse-worker.ts:1362-1369)"
- "Do not edit CHANGELOG (release-time owned)"
```
## 12. Assumptions and Open Questions
- **A1 — RESOLVED (refuted):** process exit with a native-busy unref'd worker DOES abort (§5a, empirical). Design consequence absorbed into §6-C2/§6-D.
- **A2 — RESOLVED:** mock harness supports all new shutdown cases without factory changes (test file read in full).
- **A3 (new, minor):** worker threads see `process.env` for D's budget knob — spawn options set only `workerData`/`resourceLimits`, so default env inheritance applies; executor re-verifies in one line.
- **Q2 (narrowed):** generic per-language extraction budget — file as follow-up issue once cpp-only D proves the shape.
- **Deferred:** `lookupDeclaredTypeForIdentifier` (`:1090`) gets the same index for consistency (cheap, in A) though not hot (0.4 s incl.).
- **Graph/source discrepancies recorded:** (i) provider-hook edges invisible to `impact`; (ii) MCP `context` resource staleness banner not refreshed after `--index-only --pdg` while tools serve fresh data — both worth separate GitNexus issues, not this fix.
## 13. Definition of Done
1. `GITNEXUS_HOME=<fresh> GITNEXUS_LBUG_EXTENSION_INSTALL=never GITNEXUS_MAX_FILE_SIZE=5120 node gitnexus/dist/cli/index.js analyze --worker-timeout 60` on triton-lang/triton exits 0 with no `Napi::Error`/SIGABRT, and `lib/Dialect/TritonInstrument/IR/FunctionBuilder.cpp` appears in the index (not quarantined).
2. Mini 2-file corpus (FunctionBuilder.cpp + hip_prof_str.h) analyzes in seconds (was 318.9 s).
3. The §5a exit-with-busy-worker driver, run against the rebuilt worker, exits cleanly.
4. Updated + new worker-pool unit tests green (including flipped `:97`/`:155` contract); resolver goldens (`cpp.test.ts`, `c.test.ts`) green unchanged; `npm run test:unit` and `npm run test:integration` green in `gitnexus/`.
5. Benchmark demonstrates sub-quadratic capture-emit scaling behind `GITNEXUS_BENCH=1`; deadline-bail test proves partial-return-not-throw.
6. No `LanguageProvider`/public API signature changes; no CHANGELOG edits.

View file

@ -84,6 +84,22 @@ export type { ResolveTypeRefContext } from './scope-resolution/resolve-type-ref.
// ScopeExtractor output contracts (RFC §3.2 Phase 1; Ring 2 PKG #919)
export type { ParsedFile } from './scope-resolution/parsed-file.js';
export type { ReferenceSite, ReferenceKind, CallForm } from './scope-resolution/reference-site.js';
export type {
CallableFlowOperand,
CallableFlowExpectedSignature,
CallableFlowPassingMode,
CallableFlowInvocationKind,
CallableFlowSeedSite,
CallableFlowCopySite,
CallableFlowAliasSite,
CallableFlowAddressSite,
CallableFlowStoreSite,
CallableFlowLoadSite,
CallableFlowFormalSite,
CallableFlowArgumentSite,
CallableFlowInvokeSite,
CallableFlowSite,
} from './scope-resolution/callable-flow-site.js';
// Method-dispatch materialized view over HeritageMap (RFC §3.1; Ring 2 SHARED #914)
export { buildMethodDispatchIndex } from './scope-resolution/method-dispatch-index.js';

View file

@ -0,0 +1,142 @@
/**
* Always-on callable-value-flow facts produced during scope extraction.
*
* These records deliberately contain no parser nodes or provider-private
* objects: they cross the worker, disk-cache, and ParsedFile-store boundaries.
* Language providers recognize syntax and emit `@callable-flow.*` captures;
* the central extractor attaches lexical scope ids and materializes this
* language-neutral representation.
*/
import type { ParameterTypeClass } from './symbol-definition.js';
import type { Range, ScopeId } from './types.js';
/** A lexical binding or abstract indirection cell used by a flow fact. */
export interface CallableFlowOperand {
/** Provider-normalized lexical binding name. */
readonly name: string;
readonly inScope: ScopeId;
readonly atRange: Range;
/** Number of dereference operators applied at this occurrence. */
readonly indirection: number;
/** Whether this occurrence explicitly takes the binding's address. */
readonly addressOf: boolean;
/**
* Provider-normalized expression role. `binding` is the conservative
* default; the other variants prove the occurrence denotes a callable
* designator rather than a value produced by an expression.
*/
readonly expressionKind?:
| 'binding'
| 'callable-designator'
| 'bound-member'
| 'anonymous-callable';
/** Qualified spelling retained for receiver/member-aware lookup. */
readonly qualifiedName?: string;
}
/** Callable shape used to disambiguate overload sets when syntax supplies it. */
export interface CallableFlowExpectedSignature {
readonly parameterCount?: number;
readonly parameterTypes?: readonly string[];
readonly parameterTypeClasses?: readonly ParameterTypeClass[];
/** C++ member-function cv qualifier when the declarator supplies it. */
readonly isConst?: boolean;
}
// 'pointer' is currently behaviorally identical to 'value' in the solver
// (only 'reference' back-propagates); it stays in the union as C/C++
// provenance. A 'callable-object' passing mode had no producer and was
// dropped (#2522 review) — the invocation-kind 'callable-object' below is a
// different, live concept.
export type CallableFlowPassingMode = 'value' | 'reference' | 'pointer';
export interface CallableFlowSeedSite {
readonly kind: 'seed';
readonly destination: CallableFlowOperand;
/** Simple callable name used for lexical lookup. */
readonly targetName: string;
/** Optional provider-normalized qualified spelling (for example `Base.method`). */
readonly targetQualifiedName?: string;
readonly targetRange: Range;
readonly expectedSignature?: CallableFlowExpectedSignature;
}
export interface CallableFlowCopySite {
readonly kind: 'copy';
readonly source: CallableFlowOperand;
readonly destination: CallableFlowOperand;
}
/** True identity (for example a C++ reference), modelled bidirectionally. */
export interface CallableFlowAliasSite {
readonly kind: 'alias';
readonly source: CallableFlowOperand;
readonly destination: CallableFlowOperand;
}
/** `destination` receives the abstract address of `source`. */
export interface CallableFlowAddressSite {
readonly kind: 'address';
readonly source: CallableFlowOperand;
readonly destination: CallableFlowOperand;
}
/** Targets in `source` flow into every abstract cell reached by `pointer`. */
export interface CallableFlowStoreSite {
readonly kind: 'store';
readonly source: CallableFlowOperand;
readonly pointer: CallableFlowOperand;
}
/** Targets in every abstract cell reached by `pointer` flow into `destination`. */
export interface CallableFlowLoadSite {
readonly kind: 'load';
readonly pointer: CallableFlowOperand;
readonly destination: CallableFlowOperand;
}
/** Maps a callable definition's parameter position to its lexical binding. */
export interface CallableFlowFormalSite {
readonly kind: 'formal';
readonly ownerName: string;
readonly ownerRange: Range;
readonly parameterIndex: number;
readonly binding: CallableFlowOperand;
readonly passingMode: CallableFlowPassingMode;
readonly expectedSignature?: CallableFlowExpectedSignature;
}
/** Maps one real call-site argument position to its source operand. */
export interface CallableFlowArgumentSite {
readonly kind: 'argument';
readonly callSite: Range;
readonly parameterIndex: number;
readonly source: CallableFlowOperand;
/** Direct callee spelling used when an earlier resolver already emitted the edge. */
readonly directCalleeName?: string;
}
export type CallableFlowInvocationKind = 'indirect' | 'member-pointer' | 'callable-object';
/** A syntactically indirect invocation owned by callable-value-flow. */
export interface CallableFlowInvokeSite {
readonly kind: 'invoke';
readonly callSite: Range;
readonly inScope: ScopeId;
readonly callee: CallableFlowOperand;
readonly receiver?: CallableFlowOperand;
readonly invocationKind: CallableFlowInvocationKind;
readonly arity?: number;
}
export type CallableFlowSite =
| CallableFlowSeedSite
| CallableFlowCopySite
| CallableFlowAliasSite
| CallableFlowAddressSite
| CallableFlowStoreSite
| CallableFlowLoadSite
| CallableFlowFormalSite
| CallableFlowArgumentSite
| CallableFlowInvokeSite;

View file

@ -55,6 +55,7 @@ import type { Scope, ScopeId } from './types.js';
import type { ParsedImport } from './types.js';
import type { SymbolDefinition } from './symbol-definition.js';
import type { ReferenceSite } from './reference-site.js';
import type { CallableFlowSite } from './callable-flow-site.js';
export interface ParsedFile {
readonly filePath: string;
@ -74,6 +75,12 @@ export interface ParsedFile {
*/
readonly localDefs: readonly SymbolDefinition[];
readonly referenceSites: readonly ReferenceSite[];
/**
* Compact, always-on facts for calls through assigned/passed callable
* values. Optional so hand-built fixtures and providers with no relevant
* syntax remain source-compatible; consumers normalize absence to `[]`.
*/
readonly callableFlowSites?: readonly CallableFlowSite[];
/**
* Opaque, language-private serialization of capture-time side-channel
* state that a provider's `emitScopeCaptures` populates into module-level

View file

@ -38,6 +38,17 @@ export type ReferenceKind =
| 'type-reference'
| 'inherits'
| 'import-use'
// An identifier in object-literal property-value position
// (`{ emitScopeCaptures: emitCppScopeCaptures }`, shorthand `{ hook }`).
// Resolution is owned entirely by the post-finalize property-dispatch pass
// (`emitPropertyDispatchCalls` via the callable-gated finalized-bindings
// walker `findCallableBindingInScope`; `resolveReferenceSites` skips these
// sites), so a non-function value never produces a reference. Emitted as a `USES`
// reference edge — NOT `CALLS` (a registration is not an invocation;
// Kythe `ref` / Joern `METHOD_REF` precedent). The invocation side is
// recovered separately by the property-dispatch pass, which uses
// `propertyKey` to synthesize CALLS at member-call sites (#2437).
| 'value-ref'
// A macro invocation (`log!(...)` / `vec![...]`). Resolved against
// `Macro`-labeled definitions ONLY (see `MacroRegistry`) so a macro
// never aliases a same-named free function — macros and functions are
@ -89,6 +100,14 @@ export interface ReferenceSite {
readonly explicitReceiver?: { readonly name: string };
/** Argument count at the call site; used by `provider.arityCompatibility`. */
readonly arity?: number;
/**
* Object-literal key under which a `value-ref` site registers its value
* (`{ emitScopeCaptures: emitHook }` → `'emitScopeCaptures'`; shorthand
* `{ emitHook }` → `'emitHook'`). Consumed by the property-dispatch pass
* to connect member-call sites (`x.emitScopeCaptures()`) to registered
* functions (#2437). Only set for `kind === 'value-ref'`.
*/
readonly propertyKey?: string;
/**
* Inferred argument types at the call site, one per argument. An
* empty-string entry means "unknown" — consumers narrowing overload

View file

@ -453,7 +453,15 @@ export interface Reference {
readonly toDef: DefId;
/** Location of the reference in source. */
readonly atRange: Range;
readonly kind: 'call' | 'read' | 'write' | 'type-reference' | 'inherits' | 'import-use' | 'macro';
readonly kind:
| 'call'
| 'read'
| 'write'
| 'type-reference'
| 'inherits'
| 'import-use'
| 'value-ref'
| 'macro';
readonly confidence: number;
readonly evidence: readonly ResolutionEvidence[];
}

View file

@ -1 +1 @@
c03f87cd8cd1ee716dea93cceb27109ee5b4584bc9fe426eea3f18fd6f9854cc
a99e69ab2dfb897ed771c6a8e29c5b32843a7f734db701e0699afc07c090e4d5

View file

@ -1,89 +1,128 @@
{
"_comment": "Per-language baselines for bench/scope-capture/measure.mjs --check. fingerprint = order-independent sha256 over the lang-resolution/<lang>-* fixture corpus + a 20-entity synthetic source (correctness gate; re-baseline intentionally on a legitimate capture change). scaling_budget = max allowed (t800/t250)/(800/250); ~1.0 is linear, ~3.2 is quadratic. The synthetic source is now HERITAGE-BEARING for every language (each Entity extends/implements/embeds/uses-trait/conforms-to a shared base) so the #1951 @reference.inherits synth is gated at scale, not just the base capture loop. All languages thread the tree-sitter captured node instead of re-deriving it with findNodeAtRange(tree.rootNode,...) per match, so all are linear (go #1915, python #1918, ruby/php/rust/csharp #1951, java #1956).",
"go": {
"fingerprint": "09ecd94911b830f52fa8807560abcbd79f163d02a2072870c1a59297e9a326e1",
"fingerprint": "57b3c55135af8d2af33b9a7c4bf89796a7bee5b5822b402a2dea91af7232cf4a",
"scaling_budget": 1.5,
"_rebaselined_callable_flow_2522_review": "PR #2522 review hardening: callable operands retain expression/qualified identity and formals retain signature metadata. Prior 3d4e32e7490c830516126e28931827949baa3594cb521f7a3d8dcfed95b6018a -> 57b3c55135af8d2af33b9a7c4bf89796a7bee5b5822b402a2dea91af7232cf4a; scaling 1.058 < 1.5.",
"_rebaselined_callable_flow_2522_followup": "PR #2522 follow-up: provider-owned callable assignment/copy/formal/argument/invoke facts with invocation/constructor-result suppression. Prior 09ecd94911b830f52fa8807560abcbd79f163d02a2072870c1a59297e9a326e1 -> 3d4e32e7490c830516126e28931827949baa3594cb521f7a3d8dcfed95b6018a; scaling 1.039 < 1.5.",
"_rebaselined": "#1976: F33 generic composite literal constructor inference adds generic_type captures in composite_literal patterns; fingerprint drift expected."
},
"cobol": {
"fingerprint": "68ee0e95eb9f86f2d92ca35f730f4c2d4d83abc1b5241ae767ff3437780ec8d1",
"fingerprint": "d45bb091b0893d0de4fae2486b31ba21719c9377bf35a0908fd3a36fa1c3bf4e",
"scaling_budget": 1.5,
"_rebaselined_callable_flow_2522_followup": "PR #2522 follow-up: COBOL procedure-pointer callable flow facts; multi-topic extraction now consumes each grouped scope/declaration match once instead of requiring a duplicate declaration-only match. Prior 68ee0e95eb9f86f2d92ca35f730f4c2d4d83abc1b5241ae767ff3437780ec8d1 -> d45bb091b0893d0de4fae2486b31ba21719c9377bf35a0908fd3a36fa1c3bf4e; scaling 0.853 < 1.5.",
"_note": "Updated for F17-F23 fixes (P2: TIMES guard, ADD GIVING, SQL AS alias). See PR #1959."
},
"c": {
"fingerprint": "12a196b2d6249c8d86a931b12ecebc2a0cdf8d6f47683acdd0d8e9d8bc7657f5",
"fingerprint": "3418cded9f7072152f68992f0a426f43ae7d9d553579a47075fc0cab185848a5",
"scaling_budget": 1.5,
"_rebaselined_callable_flow_2522_review": "PR #2522 review hardening: callable operands retain expression/qualified identity and formals retain signature metadata. Prior 57fee292147ae6d2db7062da1e07d17122cf355207c8967fa85fd2ec9ca398a4 -> 3418cded9f7072152f68992f0a426f43ae7d9d553579a47075fc0cab185848a5; scaling 1.073 < 1.5.",
"_rebaselined_callable_flow_2522_followup": "PR #2522 follow-up: C function-pointer signatures plus direct-callee argument metadata and invocation-result suppression. Prior 75bcdbbf006bf9bd263c0f5857461b118f39b164e9f821cb0651ad0ec46ef6ae -> 57fee292147ae6d2db7062da1e07d17122cf355207c8967fa85fd2ec9ca398a4; scaling 1.035 < 1.5.",
"_rebaselined_callable_flow": "Callable-value-flow facts for C function pointers, copies, pointer-to-pointer cells, arguments, and indirect invokes. Prior 12a196b2d6249c8d86a931b12ecebc2a0cdf8d6f47683acdd0d8e9d8bc7657f5 -> 75bcdbbf006bf9bd263c0f5857461b118f39b164e9f821cb0651ad0ec46ef6ae; measured scaling ratio 0.980 < 1.5.",
"_added": "#1956: c added to the scope-capture bench (was UNBENCHED). C has no inheritance \u2014 flat scale source. Adding it exposed + fixed a pre-existing O(n^2) findNodeAtRange root-walk in c/captures.ts (threaded c.node, byte-identical over c-* fixtures); scaling 3.475 -> 0.96.",
"_note": "#1983: + c-static-linkage-worker fixture (caller.c/lib.c/lib.h/local.c \u2014 worker-path static-linkage side-channel test). Pure fixture-corpus drift: no c/captures.ts or query change branch-vs-main, existing fixtures' captures byte-identical (c-captures.test.ts 45/45), scaling stays linear (~0.97). The baseline was missed when the fixture landed; regenerated here. fingerprint 0de009b->39f3a83.",
"_rebaselined": "#1919 open-language coverage: new lang-resolution fixtures + intended capture additions (F5/F9 c-cpp, F26/F28/F29 dart, F47/F48/F49/F51/F52 kotlin, F75/F79 swift). Fingerprint-only drift; scaling_ratio ~1.0 (linear, no perf regression)."
},
"cpp": {
"fingerprint": "6ab657c8f9bfe988a3759098c2cffdcc0443def75ff263f1282b82c21d96e931",
"fingerprint": "a70625bb0a9ef74e760d9d79cc5557485d0f0d3fb935e8a22a0c9556c65b5bb1",
"scaling_budget": 1.5,
"_rebaselined_callable_flow_2522_review": "PR #2522 review hardening: callable operands retain expression/qualified identity and formals retain signature/cv metadata. Prior dde874d2c30bda9f634f9799281a66de800cad9f76cf65e7c31839e2ae9da9ff -> 57860dd2a8d4b06c6d2dd0d854c08b781faee3da8f2b6c42ba0c68a9f70e5ccb; scaling 1.090 < 1.5.",
"_rebaselined_callable_flow_2522_followup": "PR #2522 follow-up: C++ overload-aware function/reference/member-pointer flow facts with invocation/constructor-result suppression. Prior 3a503a1513e7eede3f7a223dcce0896c06d15bdfa920445224c9025848c0d710 -> dde874d2c30bda9f634f9799281a66de800cad9f76cf65e7c31839e2ae9da9ff; scaling 1.034 < 1.5.",
"_rebaselined_callable_flow": "Callable-value-flow facts for C++ function pointers/references, reference aliases, contextual arity, arguments, and member-pointer syntax. Prior 6ab657c8f9bfe988a3759098c2cffdcc0443def75ff263f1282b82c21d96e931 -> 3a503a1513e7eede3f7a223dcce0896c06d15bdfa920445224c9025848c0d710; measured scaling ratio 1.069 < 1.5.",
"_note_1909_p3_followup": "#1909 P3 follow-up: cpp-variadic-dependent-resolution covers a comment between a pack-expanded base and `...`; review follow-up restores a separate plain `B...` fixture path too. Capture ranges intentionally drift while scaling remains linear (1.116 < 1.5).",
"_note_1899_followup": "#1899 follow-up: braced-init metadata now carries element count, intentionally changing C++ capture output; CI benchmark scaling remains linear (1.129 < 1.5).",
"_added": "#1956: cpp added to the scope-capture bench (was UNBENCHED). Heritage-bearing scale source (: public Base, public Mixin) drives emitCppInheritanceCaptures at scale. Adding it exposed + fixed a pre-existing O(n^2) findNodeAtRange root-walk in cpp/captures.ts (~12 sites, threaded c.node, byte-identical over 263 cpp-* fixtures); scaling 2.30 -> 1.12.",
"_rebaselined": "#1919 open-language coverage: new lang-resolution fixtures + intended capture additions (F5/F9 c-cpp, F26/F28/F29 dart, F47/F48/F49/F51/F52 kotlin, F75/F79 swift). Fingerprint-only drift; scaling_ratio ~1.0 (linear, no perf regression). #2094: deleted C++ declarations retain @declaration.is-deleted metadata; deleted operator and pointer-return shapes plus the expanded deleted-overload fixture are included. Intended capture drift; scaling remains linear (1.139 < 1.5).",
"_note": "#1975: + cpp-out-of-line-class fixture, fixture_count 263->265. #1990: + cpp-adl-ns-plus-hidden-friend-same-name fixture (ADL hidden-friend + namespace-callable merge parity test). Pure fixture-corpus drift \u2014 no scope-extractor change; existing fixtures' captures byte-identical. fixture_count 265->267. #1995: + cpp-union-nested-tail-collision and cpp-anon-ns-tail-collision fixtures \u2014 pure fixture-corpus drift; fixture_count 270->272, fingerprint 538e8be->d63ded6. #1993: + cpp-cross-namespace-same-tail fixture \u2014 pure fixture-corpus drift; fixture_count 272->273, fingerprint d63ded6->6d6207ae. #2077 review follow-up: cpp-member-lattice adds cross-file, qualified-base, nested-template, inherited-using, this-receiver, and non-virtual-override regressions; fixture_count 274->275. Capture scaling remains linear (1.134 < 1.5). #1899: braced-init call arguments emit a conservative parameter-type capture; fixture_count 277, scaling remains linear (1.141 < 1.5)."
"_note": "#1975: + cpp-out-of-line-class fixture, fixture_count 263->265. #1990: + cpp-adl-ns-plus-hidden-friend-same-name fixture (ADL hidden-friend + namespace-callable merge parity test). Pure fixture-corpus drift \u2014 no scope-extractor change; existing fixtures' captures byte-identical. fixture_count 265->267. #1995: + cpp-union-nested-tail-collision and cpp-anon-ns-tail-collision fixtures \u2014 pure fixture-corpus drift; fixture_count 270->272, fingerprint 538e8be->d63ded6. #1993: + cpp-cross-namespace-same-tail fixture \u2014 pure fixture-corpus drift; fixture_count 272->273, fingerprint d63ded6->6d6207ae. #2077 review follow-up: cpp-member-lattice adds cross-file, qualified-base, nested-template, inherited-using, this-receiver, and non-virtual-override regressions; fixture_count 274->275. Capture scaling remains linear (1.134 < 1.5). #1899: braced-init call arguments emit a conservative parameter-type capture; fixture_count 277, scaling remains linear (1.141 < 1.5).",
"_rebaselined_2522_review_fixes": "PR #2522 review fixes: outermost-chain passing modes; ->* ERROR-recovery role order; member-store visibility. Prior 57860dd2a8d4b06c6d2dd0d854c08b781faee3da8f2b6c42ba0c68a9f70e5ccb -> f29bc3f7b1622954d6f6b7647bc9cf6c7a2629ffcc0fe00ac7918e4925876b65; scaling ratio re-verified within budget.",
"_rebaselined_2522_prototype_value_cells": "Plain function/method prototypes no longer index as callable value cells (only pointer/parenthesized variable declarators do) \u2014 removes the spurious indirect-invoke facts that leaked phantom CALLS past two-phase suppression. Prior f29bc3f7b1622954d6f6b7647bc9cf6c7a2629ffcc0fe00ac7918e4925876b65 -> a70625bb0a9ef74e760d9d79cc5557485d0f0d3fb935e8a22a0c9556c65b5bb1; scaling re-verified within budget."
},
"csharp": {
"_rebaselined": "#1956 synth-widening: + csharp-qualified-base fixture; the synth now walks record_declaration + struct_declaration base_lists and handles alias_qualified_name (matching the #1940 legacy leg), so record/struct heritage now emits. csharp-record-base gains a record inherits capture. (record->record SAME-namespace EXTENDS is a separate registry resolution gap, tracked as follow-up.) Linear (~1.00). (Earlier #1956: heritage-bearing scale source.) | #942: scope-resolution-only cleanup reworded fixture comments; capture byte-positions shift, capture LOGIC unchanged. | #1924 F16: record primary-constructor base bindings now exclude constructor arguments; capture fingerprint changes, scaling remains linear. | #2036 review follow-up: csharp-record-base now exercises primary-constructor base dispatch end to end; +2 capture groups, scaling remains linear.",
"fingerprint": "2bb5bc8c19cb8eb08c9590545ad8a1968a7152951f7e12746e2d7901d542fed9",
"fingerprint": "75cf380209fa7d1a8a3ec873be1a9424b4e5173be0b08234c2291e8521a9b3c1",
"scaling_budget": 1.5,
"_rebaselined_callable_flow_2522_review": "PR #2522 review hardening: callable operands retain expression/qualified identity and formals retain signature metadata. Prior f31544530924748f9aa37d11cec570bc10c3ddf9d9b237e6df7a17623fd2bb3a -> 75cf380209fa7d1a8a3ec873be1a9424b4e5173be0b08234c2291e8521a9b3c1; scaling 1.061 < 1.5.",
"_rebaselined_callable_flow_2522_followup": "PR #2522 follow-up: C# method-group/delegate callable flow facts with invocation-result suppression. Prior 2bb5bc8c19cb8eb08c9590545ad8a1968a7152951f7e12746e2d7901d542fed9 -> f31544530924748f9aa37d11cec570bc10c3ddf9d9b237e6df7a17623fd2bb3a; scaling 1.115 < 1.5.",
"_note": "#2046: F35 qualified-constructor captures now emit @reference.qualified-name + a simple-name @reference.name on `new Ns.Foo()`/`new A.B.Foo()`; namespace_declaration/file_scoped_namespace_declaration now emit @declaration.namespace name captures (feeding the non-destructive namespacePrefix sidecar for `new B.Foo()` same-tail disambiguation). + csharp-interface-only-base and csharp-namespace-qualified-ctor fixtures. Pure capture-additive + fixture-corpus drift; scaling stays linear (~1.11)."
},
"rust": {
"fingerprint": "ac610bbe97666bf285923479dd7b43a2fe4c5354aae8df1bcbafdc04fb220f82",
"fingerprint": "df369c5a5f8de7753fc8bab8b4108ef5081750974ea5085ba9a867675ac9eb29",
"scaling_budget": 1.5,
"_rebaselined_callable_flow_2522_review": "PR #2522 review hardening: callable operands retain expression/qualified identity and formals retain signature metadata. Prior 65e5bca66bb1ca117949409e8fb5c80ee69d6f1b5318908eaaecf08da0482e5c -> df369c5a5f8de7753fc8bab8b4108ef5081750974ea5085ba9a867675ac9eb29; scaling 1.065 < 1.5.",
"_rebaselined_callable_flow_2522_followup": "PR #2522 follow-up: Rust fn-value callable flow facts with invocation/constructor-result suppression. Prior ac610bbe97666bf285923479dd7b43a2fe4c5354aae8df1bcbafdc04fb220f82 -> 65e5bca66bb1ca117949409e8fb5c80ee69d6f1b5318908eaaecf08da0482e5c; scaling 1.024 < 1.5.",
"_rebaselined": "#1956 tri-review U1: rust-qualified-trait fixture (scoped + generic-of-scoped impl trait paths); bareTypeIdentifier now resolves scoped_type_identifier bases by their name: tail (additive, no existing-fixture drift); linear (~1.04). #1975: + rust-scoped-impl fixture (impl a::Inner / b::Inner inherent scoped impls) \u2014 legacy @definition.impl scoped arm + findEnclosingClassInfo inherent-impl scoped target; rust scope-extractor captures byte-identical. | #942: scope-resolution-only cleanup reworded fixture comments; capture byte-positions shift, capture LOGIC unchanged.",
"_note": "PR #1934: F66/F68 let-binding pattern narrowing; F71 union (Struct-labeled, now materialized via legacy @definition.struct + resolvable); F72 macro FULLY WIRED \u2014 @declaration.macro/@reference.macro + MacroRegistry \u2192 USES edges to Macro nodes (never a same-named fn). + rust-macro / rust-union fixtures and merged with origin/main #1975 rust-scoped-impl; fingerprint re-baselined (scaling ~0.99, fixture_count 126). #1992: + rust-nested-tail-collision-generic and rust-generic-impl-same-method-name (F3) fixtures \u2014 pure fixture-corpus drift, no scope-extractor change; fixture_count 127->129, fingerprint 56ffc1c0->b00aea0f."
},
"php": {
"fingerprint": "31c9e3f3cb7094a2bf9021cf9db859036e002f8b44605cd993b470fc600e97cb",
"fingerprint": "4a688fa5a7016546f7f3c6d44de023608ae80c5b0e3670c16f6e61b3632608fd",
"scaling_budget": 1.5,
"_rebaselined_callable_flow_2522_review": "PR #2522 review hardening: callable operands retain expression/qualified identity and formals retain signature metadata. Prior df7b1565f9115d66b1ae32e4a408d651afb2521b14e5ca615f3be426c29af618 -> 4a688fa5a7016546f7f3c6d44de023608ae80c5b0e3670c16f6e61b3632608fd; scaling 1.078 < 1.5.",
"_rebaselined_callable_flow_2522_followup": "PR #2522 follow-up: PHP first-class callable and variable-invocation flow facts with invocation-result suppression. Prior 31c9e3f3cb7094a2bf9021cf9db859036e002f8b44605cd993b470fc600e97cb -> df7b1565f9115d66b1ae32e4a408d651afb2521b14e5ca615f3be426c29af618; scaling 1.074 < 1.5.",
"_rebaselined": "#1956: heritage-bearing scale source (class extends Base + use trait); both forms gated at scale; linear (~1.04). | #2481/#2482: PHP imports carry a symbol-kind capture so function/constant imports resolve by declaring file; capture shape changes, scaling remains linear (~1.04).",
"_note": "PR #1931: F53 import multi-clause, F54 enum_case, F55 anonymous_class \u2014 fixture count 138\u2192140, fingerprint drift expected."
},
"ruby": {
"fingerprint": "b5ea93bb3d0469c3821a8c70f5d5991c6f326e41097c119ad691154301dcc753",
"fingerprint": "070e4e11502442998ddf4048c2981cf1b2b735a87362ff854c5d14d71f98f4e2",
"scaling_budget": 1.5,
"_rebaselined_callable_flow_2522_review": "PR #2522 review hardening: callable operands retain expression/qualified identity and formals retain signature metadata. Prior cff273ae6cb7232c977d9241581834a2a2fa8bcf6369f7bd8f2471cd4419a6ef -> bf50ec6a53c8c91680dc6feac63a8956e78b1059249232dc25a0cfed25f31236; scaling 1.103 < 1.5.",
"_rebaselined_callable_flow_2522_followup": "PR #2522 follow-up: Ruby Method/Proc callable flow facts with invocation/constructor-result suppression. Prior b5ea93bb3d0469c3821a8c70f5d5991c6f326e41097c119ad691154301dcc753 -> cff273ae6cb7232c977d9241581834a2a2fa8bcf6369f7bd8f2471cd4419a6ef; scaling 1.086 < 1.5.",
"_rebaselined": "#1956 synth-widening: + ruby-qualified-base fixture; synth now reduces a scope_resolution superclass (class C < Mod::Super) to its trailing constant (matching the #1940 legacy leg), at parity. Linear (~1.03). (Earlier #1956: heritage-bearing scale source.) | #942: scope-resolution-only cleanup reworded fixture comments; capture byte-positions shift, capture LOGIC unchanged.",
"_note": "F62: + scope_resolution class/module declaration captures \u2014 fixture count 78\u219281, fingerprint drift expected. #1975: + ruby-tail-collision fixture (Foo::Bar vs Baz::Bar stay distinct nodes) \u2014 pure fixture-corpus drift, scope-extractor captures unchanged; 81\u219282. #1991: + ruby-nested-mixin-tail-collision fixture (85\u219286). Recomputed on the #942 merge (fixture-comment rewording shifts capture byte-positions, capture LOGIC unchanged): bf6b13a -> b5ea93bb."
"_note": "F62: + scope_resolution class/module declaration captures \u2014 fixture count 78\u219281, fingerprint drift expected. #1975: + ruby-tail-collision fixture (Foo::Bar vs Baz::Bar stay distinct nodes) \u2014 pure fixture-corpus drift, scope-extractor captures unchanged; 81\u219282. #1991: + ruby-nested-mixin-tail-collision fixture (85\u219286). Recomputed on the #942 merge (fixture-comment rewording shifts capture byte-positions, capture LOGIC unchanged): bf6b13a -> b5ea93bb.",
"_rebaselined_2522_review_fixes": "PR #2522 review fixes: bare identifiers are calls, not callable references (bareNamesAreCalls). Prior bf50ec6a53c8c91680dc6feac63a8956e78b1059249232dc25a0cfed25f31236 -> 070e4e11502442998ddf4048c2981cf1b2b735a87362ff854c5d14d71f98f4e2; scaling ratio re-verified within budget."
},
"swift": {
"fingerprint": "180ac68e780bdf6f9089d53f51cbb9a66aed3e7774631cc3fcbaae5020213998",
"fingerprint": "115c5da807e36bb12fdeba28e44f2b6484ef322ff26c19fa0f191febaf774248",
"scaling_budget": 1.5,
"_rebaselined": "#1919 open-language coverage: new lang-resolution fixtures + intended capture additions (F5/F9 c-cpp, F26/F28/F29 dart, F47/F48/F49/F51/F52 kotlin, F75/F79 swift). Fingerprint-only drift; scaling_ratio ~1.0 (linear, no perf regression)."
"_rebaselined_callable_flow_2522_review": "PR #2522 review hardening: callable operands retain expression/qualified identity and formals retain signature metadata. Prior 5f923c6604d825d12b249f31c155b0f4d13a8379d532e5dde64a0f9b15cf4725 -> 7687ee2466e16020a12440a03fbda53e63aa05f94b4481f6133c09867a0d560d; scaling 1.042 < 1.5.",
"_rebaselined_callable_flow_2522_followup": "PR #2522 follow-up: Swift function-value callable flow facts with invocation-result suppression. Prior 180ac68e780bdf6f9089d53f51cbb9a66aed3e7774631cc3fcbaae5020213998 -> 5f923c6604d825d12b249f31c155b0f4d13a8379d532e5dde64a0f9b15cf4725; scaling 1.043 < 1.5.",
"_rebaselined": "#1919 open-language coverage: new lang-resolution fixtures + intended capture additions (F5/F9 c-cpp, F26/F28/F29 dart, F47/F48/F49/F51/F52 kotlin, F75/F79 swift). Fingerprint-only drift; scaling_ratio ~1.0 (linear, no perf regression).",
"_rebaselined_2522_review_fixes": "PR #2522 review fixes: assignment target:/result: fields join the shared fallback. Prior 7687ee2466e16020a12440a03fbda53e63aa05f94b4481f6133c09867a0d560d -> 115c5da807e36bb12fdeba28e44f2b6484ef322ff26c19fa0f191febaf774248; scaling ratio re-verified within budget."
},
"dart": {
"fingerprint": "94bf2c26e1ba96f4211634aa572c0a989b503e717e75dfc5df04f66c417de80f",
"fingerprint": "66a46d5ff09f3d11b2771db0f48596fe7057e95c5bc8f56241fdb911137298c3",
"scaling_budget": 1.5,
"_rebaselined_callable_flow_2522_review": "PR #2522 review hardening: callable operands retain expression/qualified identity and formals retain signature metadata. Prior 29ce2bfe70b246b1c9d5e99c0ec11e850c22e9672737592207242b7f4cc824b8 -> 66a46d5ff09f3d11b2771db0f48596fe7057e95c5bc8f56241fdb911137298c3; scaling 1.054 < 1.5.",
"_rebaselined_callable_flow_2522_followup": "PR #2522 follow-up: Dart function-tearoff and selector callable flow facts, split signature/body lexical ownership, and invocation-result suppression. Prior 94bf2c26e1ba96f4211634aa572c0a989b503e717e75dfc5df04f66c417de80f -> 29ce2bfe70b246b1c9d5e99c0ec11e850c22e9672737592207242b7f4cc824b8; scaling 0.906 < 1.5.",
"_added": "#939: dart added to the scope-capture bench with the registry-primary migration. Heritage-bearing scale source (Entity extends Base implements Marker) gates the @reference.inherits synth + the postfix-chain reference walk at scale. emitDartScopeCaptures threads tree-sitter captured nodes (no findNodeAtRange root-walk), so it is linear (~1.0).",
"_rebaselined": "#1919 review CF3 fix: extended kotlin-local-property-owner (init/accessor destructuring) + new dart-accessor-owner fixture (getter/setter ownership). Fingerprint-only corpus drift; scaling ~1.0."
},
"java": {
"fingerprint": "062d754764aaa8a6772fb90875c710502a63e3e7a300e633942381ed914faada",
"fingerprint": "f3b4f4b6610e07c3ac90deb1c53d3572b6ad55a36e5d7134984876d30031ff67",
"scaling_budget": 1.5,
"_rebaselined_callable_flow_2522_review": "PR #2522 review hardening: callable operands retain expression/qualified identity and formals retain signature metadata; same-name lexical regions use an O(ancestor-depth) ID-set lookup. Prior d5c59d7dc9e206637515d5aea1163f7c1cdd76410c38c5fe6143d13d19677d6a -> 004a3592998dca1193bd1429a8284513725de7764f2a3eceedaaa984cfd763b4; scaling 0.992 < 1.5.",
"_rebaselined_callable_flow_2522_followup": "PR #2522 follow-up: Java method-reference/SAM callable flow facts with invocation-result suppression. Prior 062d754764aaa8a6772fb90875c710502a63e3e7a300e633942381ed914faada -> d5c59d7dc9e206637515d5aea1163f7c1cdd76410c38c5fe6143d13d19677d6a; scaling 1.074 < 1.5.",
"_rebaselined": "#2357 (supersedes #2353): + java-cast-receiver, java-this-field-chain, java-this-dispatch fixtures (cast-wrapped receivers, this.field chains incl. initializer contexts, bare-this dispatch pinning). Drift is purely fixture-additive: with the three new dirs parked, the fingerprint reproduces the prior baseline byte-identically \u2014 no emit/capture change. #1956 synth-widening: + java-iface-extends fixture; synthesizeJavaInheritanceReferences now ALSO walks interface_declaration extends_interfaces (interface IA extends IB, IC<T>), matching the #1940 legacy leg. (Earlier U2+review: java-qualified-base fixture covers 2- AND 3-segment qualified bases guarding the legacy end-anchor; synth tail-resolves scoped bases.) Linear (~1.03). (Earliest: java added to bench, exposed+fixed the O(n^2) findNodeAtRange root-walk; 3.09 -> ~0.99.) | #942: scope-resolution-only cleanup reworded fixture comments; capture byte-positions shift, capture LOGIC unchanged.",
"_note": "#1928 / #2045: F35 adds qualified + qualified-generic constructor query captures (`new pkg.Foo()`, `new a.b.Foo()`, `new pkg.Box<T>()`); F38 synthesizes `@reference.call.constructor` on `super(...)`/`this(...)` explicit_constructor_invocation nodes; F41 generic-aware stripQualifier in interpret (type-binding normalization). + java-qualified-constructor and java-explicit-constructor fixtures. Pure capture-additive + fixture-corpus drift; scaling stays linear (~1.06)."
"_note": "#1928 / #2045: F35 adds qualified + qualified-generic constructor query captures (`new pkg.Foo()`, `new a.b.Foo()`, `new pkg.Box<T>()`); F38 synthesizes `@reference.call.constructor` on `super(...)`/`this(...)` explicit_constructor_invocation nodes; F41 generic-aware stripQualifier in interpret (type-binding normalization). + java-qualified-constructor and java-explicit-constructor fixtures. Pure capture-additive + fixture-corpus drift; scaling stays linear (~1.06).",
"_rebaselined_2522_review_fixes": "PR #2522 review fixes: get/test dropped from callableProtocolMethods. Prior 004a3592998dca1193bd1429a8284513725de7764f2a3eceedaaa984cfd763b4 -> f3b4f4b6610e07c3ac90deb1c53d3572b6ad55a36e5d7134984876d30031ff67; scaling ratio re-verified within budget."
},
"typescript": {
"fingerprint": "3f44a4a6892698df2d145c8ff2812c3b318807648983c88aca28fbd694f172f9",
"fingerprint": "e05446620c5b80b7aae291cfdf32f693580fada2ae687124769b04a0c03bfe63",
"scaling_budget": 1.5,
"_rebaselined_callable_flow_2522_review": "PR #2522 review hardening: callable operands retain expression/qualified identity and formals retain signature metadata. Prior 27f937bfb47d4bded316ea3c785ff659c8cd88a5761d928f113477a08c802c78 -> e05446620c5b80b7aae291cfdf32f693580fada2ae687124769b04a0c03bfe63; scaling 0.983 < 1.5.",
"_rebaselined_callable_flow_2522_followup": "PR #2522 follow-up: lexical callable bindings, direct-callee argument metadata, and invocation-result suppression. Prior db5933cc6760234ed7d495123410feba6de243646d583f20d43032b9459f81fd -> 27f937bfb47d4bded316ea3c785ff659c8cd88a5761d928f113477a08c802c78; scaling 0.975 < 1.5.",
"_rebaselined_callable_flow": "Callable assignment/copy/formal/argument/invoke facts (also consumed by Vue script blocks). Prior 25de86fd3377132c4e35d3d98f4f94a58e0cfeb7c22948a8ea3be4e793be74fd -> db5933cc6760234ed7d495123410feba6de243646d583f20d43032b9459f81fd; measured scaling ratio 0.951 < 1.5.",
"_rebaselined": "#1962: F44 (class scope@), F85 (enum member declarations), F87 (optional_parameter type annotations) add new captures \u2014 fingerprint drift expected.",
"_note": "#1968: F44, F85, F87 \u2014 fingerprint drift expected."
"_note": "#1968: F44, F85, F87 \u2014 fingerprint drift expected.",
"_rebaselined_2522": "#2522 intentional @reference.value-ref/property-key capture additions. GitHub Actions run 29553361660 job 87800394279: prior 3f44a4a6892698df2d145c8ff2812c3b318807648983c88aca28fbd694f172f9 -> 25de86fd3377132c4e35d3d98f4f94a58e0cfeb7c22948a8ea3be4e793be74fd; scaling ratio 0.987 < 1.5."
},
"javascript": {
"fingerprint": "d72f03c6c502235d2d4b74d66baa5c7d361f040d7a1b72e84acad61210d05ae8",
"fingerprint": "479927409bbdd9852a36172c8260aa56df260e99129a7a9c20a0d1903dd5538b",
"scaling_budget": 1.5,
"_rebaselined_callable_flow_2522_review": "PR #2522 review hardening: callable operands retain expression/qualified identity and formals retain signature metadata. Prior b59fe8135b6a31a12bc3f872b224054b16592588153ae3661d03958d787c76f3 -> 479927409bbdd9852a36172c8260aa56df260e99129a7a9c20a0d1903dd5538b; scaling 1.050 < 1.5.",
"_rebaselined_callable_flow_2522_followup": "PR #2522 follow-up: lexical callable bindings, direct-callee argument metadata, and invocation-result suppression. Prior 917a9cd975ba035bdad71fdb70cd72eeddec58c25797e5a1addfa6172808a55c -> b59fe8135b6a31a12bc3f872b224054b16592588153ae3661d03958d787c76f3; scaling 1.093 < 1.5.",
"_rebaselined_callable_flow": "Callable assignment/copy/formal/argument/invoke facts (also consumed by Vue script blocks). Prior 5567dd47e7ba29821a518c4a9852adc3b774e25ef3e7a6e2b3ecb7b59ddab73c -> 917a9cd975ba035bdad71fdb70cd72eeddec58c25797e5a1addfa6172808a55c; measured scaling ratio 1.126 < 1.5.",
"_added": "#1951: bench coverage added (was ungated); scale source heritage-bearing (extends Base); js/kotlin O(n^2) findNodeAtRange-per-match fixed to threaded captured node, now linear.",
"_rebaselined": "#1956 synth-widening: + javascript-qualified-base fixture; synthesizeJsInheritanceReferences now handles a member_expression base (class S extends ns.Base -> Base), matching the #1940 legacy leg + the TS terminalTsTypeNameNode property_identifier case, at parity. Linear (~1.05). | #942: scope-resolution-only cleanup reworded fixture comments; capture byte-positions shift, capture LOGIC unchanged."
"_rebaselined": "#1956 synth-widening: + javascript-qualified-base fixture; synthesizeJsInheritanceReferences now handles a member_expression base (class S extends ns.Base -> Base), matching the #1940 legacy leg + the TS terminalTsTypeNameNode property_identifier case, at parity. Linear (~1.05). | #942: scope-resolution-only cleanup reworded fixture comments; capture byte-positions shift, capture LOGIC unchanged.",
"_rebaselined_2522": "#2522 intentional @reference.value-ref/property-key capture additions. GitHub Actions run 29553361660 job 87800394279: prior d72f03c6c502235d2d4b74d66baa5c7d361f040d7a1b72e84acad61210d05ae8 -> 5567dd47e7ba29821a518c4a9852adc3b774e25ef3e7a6e2b3ecb7b59ddab73c; scaling ratio 1.031 < 1.5."
},
"kotlin": {
"fingerprint": "4900431791f2b9280009deb2b82659c26ead8aa6fb8731190a7c505dec5a9041",
"fingerprint": "4b31f46cfb004ba769a96feeb06ae4ef109c77410f54e7aaab4a688df599b112",
"scaling_budget": 1.5,
"_rebaselined_callable_flow_2522_review": "PR #2522 review hardening: callable operands retain expression/qualified identity and formals retain signature metadata. Prior bddba25d5a88152bbbee8d70e82c944b5302accb4b625df782adb1d4f7a7ac12 -> e856951c2a779163d555dadc8e1bf59304a86caed78ac1f450d9caa2b50f63d1; scaling 1.090 < 1.5.",
"_rebaselined_callable_flow_2522_followup": "PR #2522 follow-up: Kotlin callable-reference flow facts with invocation-result suppression. Prior 4900431791f2b9280009deb2b82659c26ead8aa6fb8731190a7c505dec5a9041 -> bddba25d5a88152bbbee8d70e82c944b5302accb4b625df782adb1d4f7a7ac12; scaling 0.880 < 1.5.",
"_added": "#1951: bench coverage added (was ungated); scale source heritage-bearing (: Base()); js/kotlin O(n^2) findNodeAtRange-per-match fixed to threaded captured node, now linear.",
"_rebaselined": "#1919 review CF3 fix: extended kotlin-local-property-owner (init/accessor destructuring) + new dart-accessor-owner fixture (getter/setter ownership). Fingerprint-only corpus drift; scaling ~1.0.",
"_rebaselined_2271": "PR #2271: re-vendored tree-sitter-kotlin 0.3.8 -> unreleased fwcd main c8ac3d26 for `fun interface` support + new kotlin-fun-interface fixture in the corpus. Drift is both corpus-additive (the fixture) and grammar-driven (the new grammar parses `fun interface` as a class_declaration, not an ERROR node). Baselined to the NEW grammar's fingerprint, so this --check passes only once the regenerated prebuilds land \u2014 until then CI loads the committed 0.3.8 binary and the bench is red, same as the kotlin fun-interface integration tests. scaling ~0.83 (linear)."
"_rebaselined_2271": "PR #2271: re-vendored tree-sitter-kotlin 0.3.8 -> unreleased fwcd main c8ac3d26 for `fun interface` support + new kotlin-fun-interface fixture in the corpus. Drift is both corpus-additive (the fixture) and grammar-driven (the new grammar parses `fun interface` as a class_declaration, not an ERROR node). Baselined to the NEW grammar's fingerprint, so this --check passes only once the regenerated prebuilds land \u2014 until then CI loads the committed 0.3.8 binary and the bench is red, same as the kotlin fun-interface integration tests. scaling ~0.83 (linear).",
"_rebaselined_2522_review_fixes": "PR #2522 review fixes: fieldless assignment nodes decomposed positionally. Prior e856951c2a779163d555dadc8e1bf59304a86caed78ac1f450d9caa2b50f63d1 -> 4b31f46cfb004ba769a96feeb06ae4ef109c77410f54e7aaab4a688df599b112; scaling ratio re-verified within budget."
}
}

View file

@ -607,7 +607,7 @@ function parseDataItemClauses(rest: string): {
// USAGE [IS] <usage-type> — including non-standard COMP-6, COMP-X etc.
const usageMatch = text.match(
/\bUSAGE\s+(?:IS\s+)?(COMP(?:UTATIONAL)?(?:-[0-9X])?|BINARY|PACKED-DECIMAL|DISPLAY|INDEX|POINTER|NATIONAL)\b/i,
/\bUSAGE\s+(?:IS\s+)?(PROCEDURE-POINTER|FUNCTION-POINTER|COMP(?:UTATIONAL)?(?:-[0-9X])?|BINARY|PACKED-DECIMAL|DISPLAY|INDEX|POINTER|NATIONAL)\b/i,
);
if (usageMatch) {
result.usage = usageMatch[1].toUpperCase();

View file

@ -289,6 +289,10 @@ function mapKindToType(kind: Reference['kind']): RelationshipType {
case 'type-reference':
case 'import-use':
case 'macro':
// value-ref: function registered as an object-literal property value —
// a reference, not an invocation; CALLS are synthesized separately by
// the property-dispatch pass (#2437).
case 'value-ref':
return 'USES';
}
}

View file

@ -11,6 +11,26 @@ import { parseSourceSafe } from '../../../tree-sitter/safe-parse.js';
import { splitCInclude } from './import-decomposer.js';
import { computeCDeclarationArity, computeCCallArity } from './arity-metadata.js';
import { markStaticName } from './static-linkage.js';
import {
synthesizeCallableFlowCaptures,
type CallableCaptureSignature,
} from '../../utils/callable-flow-captures.js';
const C_CALLABLE_CAPTURE_OPTIONS = {
functionNodeTypes: new Set(['function_definition']),
callNodeTypes: new Set(['call_expression']),
parameterListNodeTypes: new Set(['parameter_list', 'argument_list']),
parameterNodeTypes: new Set(['parameter_declaration']),
bindingNodeTypes: new Set(['init_declarator']),
assignmentNodeTypes: new Set(['assignment_expression']),
identifierNodeTypes: new Set(['identifier', 'field_identifier', 'type_identifier']),
callableSignatureDeclarationNodeTypes: new Set(['declaration', 'parameter_declaration']),
emitCanonicalInvokeReference: true,
parameterPassingMode: (parameter: SyntaxNode) =>
containsNodeType(parameter, 'pointer_declarator') ? ('pointer' as const) : ('value' as const),
expectedSignature: (container: SyntaxNode, destination: SyntaxNode) =>
functionDeclaratorSignature(destination) ?? functionDeclaratorSignature(container),
} as const;
export function emitCScopeCaptures(
sourceText: string,
@ -146,9 +166,56 @@ export function emitCScopeCaptures(
out.push(grouped);
}
out.push(...synthesizeCallableFlowCaptures(tree.rootNode, C_CALLABLE_CAPTURE_OPTIONS));
return out;
}
function functionDeclaratorSignature(node: SyntaxNode): CallableCaptureSignature | undefined {
const declarator = findDescendantOfType(node, 'function_declarator');
const parameters = declarator?.childForFieldName('parameters');
if (parameters === null || parameters === undefined) return undefined;
const parameterNodes = parameters.namedChildren.filter(
(child): child is SyntaxNode => child !== null && child.type === 'parameter_declaration',
);
// tree-sitter-c materializes `...` as a NAMED `variadic_parameter` node —
// the anonymous-token checks never matched, so variadic signatures were
// emitted with a wrong fixed arity (#2522 review). Keep the token checks
// for grammar variants that expose `...` as an anonymous literal.
const hasEllipsis = parameters.children.some(
(child) =>
child.type === 'variadic_parameter' ||
child.type === '...' ||
(!child.isNamed && child.text === '...'),
);
const isVoidOnly =
parameterNodes.length === 1 &&
parameterNodes[0]!.namedChildCount === 1 &&
parameterNodes[0]!.firstNamedChild?.text === 'void';
if (isVoidOnly) return { parameterCount: 0, parameterTypes: [] };
const parameterTypes = parameterNodes.map(
(parameter) => parameter.childForFieldName('type')?.text ?? 'unknown',
);
if (hasEllipsis) parameterTypes.push('...');
return {
...(hasEllipsis ? {} : { parameterCount: parameterNodes.length }),
parameterTypes,
};
}
function containsNodeType(root: SyntaxNode, type: string): boolean {
return findDescendantOfType(root, type) !== null;
}
function findDescendantOfType(root: SyntaxNode, type: string): SyntaxNode | null {
const stack: SyntaxNode[] = [root];
while (stack.length > 0) {
const node = stack.pop()!;
if (node.type === type) return node;
for (const child of node.namedChildren) if (child !== null) stack.push(child);
}
return null;
}
/**
* Check if a C function_definition or declaration has `static` storage class.
* Walks direct children for a `storage_class_specifier` node with text `static`.

View file

@ -118,6 +118,23 @@ const C_SCOPE_QUERY = `
declarator: (pointer_declarator
declarator: (identifier) @declaration.name)) @declaration.variable
;; Declarations — function-pointer variables (\`void (*fp)(int);\`), with and
;; without initializer. Without these the file-scope pointer has no scope-tree
;; binding, so callable-flow cells written in one function and read in another
;; canonicalize to different keys and never join (#2522 review, H1).
(declaration
declarator: (function_declarator
declarator: (parenthesized_declarator
(pointer_declarator
declarator: (identifier) @declaration.name)))) @declaration.variable
(declaration
declarator: (init_declarator
declarator: (function_declarator
declarator: (parenthesized_declarator
(pointer_declarator
declarator: (identifier) @declaration.name))))) @declaration.variable
;; Declarations — macro definitions
(preproc_def
name: (identifier) @declaration.name) @declaration.macro

View file

@ -126,4 +126,10 @@ export const cScopeResolver: ScopeResolver = {
const simple = def.qualifiedName?.split('.').pop() ?? def.qualifiedName ?? '';
return isStaticName(def.filePath, simple);
},
// Precise linkage-only variant used to associate a visible prototype with
// its implementation during callable-value actual-to-formal propagation.
hasFileLocalCallableLinkage: (def: SymbolDefinition) => {
const simple = def.qualifiedName?.split('.').pop() ?? def.qualifiedName ?? '';
return isStaticName(def.filePath, simple);
},
};

View file

@ -60,6 +60,23 @@ export function emitCobolScopeCaptures(
const extracted = extractCobolSymbolsWithRegex(cleaned, _filePath);
const out: CaptureMatch[] = [];
const procedurePointers = new Set(
extracted.dataItems
.filter((item) => /(?:PROCEDURE|FUNCTION)-POINTER/.test(item.usage?.toUpperCase() ?? ''))
.map((item) => item.name.toUpperCase()),
);
// Fallback for declarations the clause parser missed. Scan CLEANED lines —
// on raw fixed-format sources the sequence number (cols 1-6) satisfied
// `\d+` and the LEVEL NUMBER got captured as the name, so the set never
// contained the real pointer name and the whole feature no-op'd (#2522
// review, H3). COBOL data names must contain a letter, so `[A-Z]` first
// rejects level numbers.
for (const line of cleaned.split(/\r?\n/)) {
const declaration = line.match(
/^\s*\d{1,2}\s+([A-Z][A-Z0-9-]*).*\b(?:PROCEDURE|FUNCTION)-POINTER\b/i,
);
if (declaration !== null) procedurePointers.add(declaration[1]!.toUpperCase());
}
// ── 1. PROGRAM-ID → @scope.module ───────────────────────────────────
// The primary program name (first PROGRAM-ID encountered)
@ -278,6 +295,123 @@ export function emitCobolScopeCaptures(
if (m !== null) out.push(m);
}
// ── 9. Procedure-pointer value flow ────────────────────────────────
// ISO COBOL exposes callable values through USAGE PROCEDURE-POINTER,
// SET ... TO ENTRY, and dynamic CALL data-items. The regex provider emits
// the same normalized facts as AST-backed providers so shared ingestion
// remains language-agnostic.
for (const program of extracted.programs) {
for (let index = 0; index < (program.procedureUsing?.length ?? 0); index++) {
const parameter = program.procedureUsing![index]!;
const ownerRange = rangeOf(program.startLine, 0, program.endLine, 0);
out.push({
'@callable-flow.formal': capture('@callable-flow.formal', ownerRange, program.name),
'@callable-flow.owner': capture('@callable-flow.owner', ownerRange, program.name),
'@callable-flow.binding': capture(
'@callable-flow.binding',
rangeOf(program.startLine, 0, program.startLine, parameter.length),
parameter,
),
'@callable-flow.parameter-index': capture(
'@callable-flow.parameter-index',
ownerRange,
String(index),
),
'@callable-flow.passing-mode': capture(
'@callable-flow.passing-mode',
ownerRange,
'reference',
),
});
}
}
for (let index = 0; index < lines.length; index++) {
const line = lines[index]!;
// Comment lines must not seed flows — a commented-out SET produced a
// live seed and a false CALLS edge from dead code (#2522 review, M1).
// Fixed format marks comments with '*'/'/' in indicator column 7; free
// format uses '*>' (also valid inline, so strip the tail).
if (line[6] === '*' || line[6] === '/') continue;
const code = line.split('*>')[0]!;
const lineNumber = index + 1;
const lineRange = rangeOf(lineNumber, 0, lineNumber, endColFrom(line));
const entry = code.match(
/\bSET\s+([A-Z0-9][A-Z0-9-]*)\s+TO\s+ENTRY\s+(?:"([^"]+)"|'([^']+)')/i,
);
if (entry !== null) {
const destination = entry[1]!;
const target = entry[2] ?? entry[3]!;
if (procedurePointers.has(destination.toUpperCase())) {
out.push({
'@callable-flow.seed': capture('@callable-flow.seed', lineRange, line),
'@callable-flow.destination': capture(
'@callable-flow.destination',
lineRange,
destination,
),
'@callable-flow.target': capture('@callable-flow.target', lineRange, target),
'@callable-flow.target-name': capture('@callable-flow.target-name', lineRange, target),
});
}
continue;
}
const copy = code.match(/\bSET\s+([A-Z0-9][A-Z0-9-]*)\s+TO\s+([A-Z0-9][A-Z0-9-]*)\b/i);
if (
copy !== null &&
procedurePointers.has(copy[1]!.toUpperCase()) &&
procedurePointers.has(copy[2]!.toUpperCase())
) {
out.push({
'@callable-flow.copy': capture('@callable-flow.copy', lineRange, line),
'@callable-flow.destination': capture('@callable-flow.destination', lineRange, copy[1]!),
'@callable-flow.source': capture('@callable-flow.source', lineRange, copy[2]!),
});
}
}
for (const call of extracted.calls) {
const line = lines[call.line - 1] ?? '';
const lineRange = rangeOf(call.line, 0, call.line, endColFrom(line));
for (let index = 0; index < (call.parameters?.length ?? 0); index++) {
const parameter = call.parameters![index]!;
out.push({
'@callable-flow.argument': capture('@callable-flow.argument', lineRange, line),
'@callable-flow.source': capture('@callable-flow.source', lineRange, parameter),
'@callable-flow.parameter-index': capture(
'@callable-flow.parameter-index',
lineRange,
String(index),
),
...(!call.isQuoted && procedurePointers.has(call.target.toUpperCase())
? {}
: {
'@callable-flow.direct-callee-name': capture(
'@callable-flow.direct-callee-name',
lineRange,
call.target,
),
}),
});
}
if (!call.isQuoted && procedurePointers.has(call.target.toUpperCase())) {
out.push({
'@callable-flow.invoke': capture('@callable-flow.invoke', lineRange, line),
'@callable-flow.callee': capture('@callable-flow.callee', lineRange, call.target),
'@callable-flow.invocation-kind': capture(
'@callable-flow.invocation-kind',
lineRange,
'indirect',
),
'@callable-flow.arity': capture(
'@callable-flow.arity',
lineRange,
String(call.parameters?.length ?? 0),
),
});
}
}
return out;
}

View file

@ -58,6 +58,15 @@ const cobolScopeResolver: ScopeResolver = {
return 'incompatible';
},
// PROGRAM-ID declarations bridge to legacy Module graph nodes. COBOL's
// procedure-pointer ENTRY values therefore target Module defs, while every
// AST-backed provider keeps the shared callable-label default.
isCallableValueTarget: (def) => def.type === 'Module',
// Structural COBOL CALLS/IMPORTS remain owned by the established regex
// processor; this resolver contributes only procedure-pointer CALLS.
scopeResolutionEdgeMode: 'callable-flow-only',
// No inheritance in COBOL — empty MRO map.
buildMro: () => new Map(),

View file

@ -14,6 +14,7 @@ import {
classifyCppParameterType,
computeCppDeclarationArity,
computeCppCallArity,
normalizeCppParamType,
} from './arity-metadata.js';
import { markCppAnonymousNamespaceRange, markFileLocal } from './file-local-linkage.js';
import { markCppDependentBase, markCppDependentPackBase } from './two-phase-lookup.js';
@ -23,6 +24,111 @@ import { extractCppTemplateConstraints } from './constraint-extractor.js';
import { captureCppMemberLookupFacts } from './member-lookup.js';
import { CPP_BRACED_INIT_TYPE_PREFIX } from './conversion-rank.js';
import { logger } from '../../../logger.js';
import {
synthesizeCallableFlowCaptures,
type CallableCaptureSignature,
} from '../../utils/callable-flow-captures.js';
const CPP_CALLABLE_CAPTURE_OPTIONS = {
functionNodeTypes: new Set(['function_definition', 'lambda_expression']),
callNodeTypes: new Set(['call_expression']),
parameterListNodeTypes: new Set(['parameter_list', 'argument_list']),
parameterNodeTypes: new Set(['parameter_declaration', 'optional_parameter_declaration']),
bindingNodeTypes: new Set(['init_declarator']),
assignmentNodeTypes: new Set(['assignment_expression']),
identifierNodeTypes: new Set([
'identifier',
'field_identifier',
'type_identifier',
'namespace_identifier',
]),
callableSignatureDeclarationNodeTypes: new Set([
'declaration',
'field_declaration',
'parameter_declaration',
'optional_parameter_declaration',
]),
callableReferenceNodeTypes: new Set(['qualified_identifier']),
emitCanonicalInvokeReference: true,
memberPointerOperators: new Set(['.*', '->*']),
memberPointerParts: (node: SyntaxNode) => cppRecoveredMemberPointerParts(node),
parameterPassingMode: (parameter: SyntaxNode) => cppOutermostPassingMode(parameter),
isTrueReferenceBinding: (_container: SyntaxNode, destination: SyntaxNode) =>
cppContainsNodeType(destination, 'reference_declarator'),
expectedSignature: (container: SyntaxNode, destination: SyntaxNode) =>
cppFunctionDeclaratorSignature(destination) ?? cppFunctionDeclaratorSignature(container),
normalizeQualifiedName: (raw: string) => raw.replaceAll('::', '.'),
} as const;
/**
* Passing mode from the parameter's OUTERMOST declarator chain only. A deep
* subtree scan inverted copy vs alias: `void reg(void (*cb)(int& out))` has a
* `reference_declarator` inside the nested parameter list, but `cb` itself is
* a by-value pointer copy — classifying it 'reference' made the solver
* back-propagate formal targets into every caller's argument cell (#2522
* review, M5). The chain walk never descends into nested parameter lists.
*/
function cppOutermostPassingMode(parameter: SyntaxNode): 'reference' | 'pointer' | 'value' {
let sawReference = false;
let sawPointer = false;
let node: SyntaxNode | null = parameter.childForFieldName('declarator') ?? null;
const visited = new Set<number>();
while (node !== null && !visited.has(node.id)) {
visited.add(node.id);
if (node.type === 'reference_declarator' || node.type === 'abstract_reference_declarator') {
// A reference ANYWHERE on the chain aliases the caller's storage —
// `void (*&cb)(int)` is a reference to pointer, i.e. an alias.
sawReference = true;
} else if (node.type === 'pointer_declarator' || node.type === 'abstract_pointer_declarator') {
sawPointer = true;
}
const next: SyntaxNode | null = node.childForFieldName('declarator');
if (next !== null) {
node = next;
continue;
}
if (node.type === 'parenthesized_declarator') {
node =
node.namedChildren.find(
(child): child is SyntaxNode => child !== null && child.type.includes('declarator'),
) ?? null;
continue;
}
break;
}
return sawReference ? 'reference' : sawPointer ? 'pointer' : 'value';
}
function cppRecoveredMemberPointerParts(
node: SyntaxNode,
): { receiver: SyntaxNode; member: SyntaxNode; operator: string } | undefined {
if (!node.type.includes('parenthesized')) return undefined;
const recovered = node.namedChildren.find(
(child): child is SyntaxNode => child !== null && child.type === 'ERROR',
);
if (recovered === undefined) return undefined;
const operator = recovered.children.find((child) => child !== null && child.text === '->*');
const sibling = node.namedChildren.find(
(child): child is SyntaxNode => child !== null && child.id !== recovered.id,
);
const errIdentifier = recovered.namedChildren.find(
(child): child is SyntaxNode => child !== null,
);
if (operator === undefined || sibling === undefined || errIdentifier === undefined) {
return undefined;
}
// tree-sitter's error recovery groups `(obj->*ptr)` two ways depending on
// recovery cost (identifier lengths!): either `[identifier, ERROR "->*m"]`
// or `[ERROR "obj->*", identifier]`. Disambiguate by token order INSIDE the
// ERROR: an identifier BEFORE `->*` is the receiver (the sibling is the
// pointer/member); an identifier AFTER `->*` is the member (the sibling is
// the receiver). Assuming one fixed grouping swapped the roles and silently
// dropped the call site (#2522 review, H2).
if (errIdentifier.startIndex < operator.startIndex) {
return { receiver: errIdentifier, member: sibling, operator: operator.text };
}
return { receiver: sibling, member: errIdentifier, operator: operator.text };
}
/**
* Per-file wall-clock budget for the capture-emit loop (#2432). A worker
@ -519,9 +625,134 @@ export function emitCppScopeCaptures(
detectCppDependentBases(tree.rootNode, filePath);
captureCppMemberLookupFacts(tree.rootNode, filePath);
out.push(...synthesizeCallableFlowCaptures(tree.rootNode, CPP_CALLABLE_CAPTURE_OPTIONS));
return out;
}
function cppFunctionDeclaratorSignature(node: SyntaxNode): CallableCaptureSignature | undefined {
const declarator = cppFindDescendantOfType(node, 'function_declarator');
const parameters = declarator?.childForFieldName('parameters');
if (parameters === null || parameters === undefined) {
return recoverCppMemberPointerSignature(node.text);
}
const parameterNodes = parameters.namedChildren.filter(
(child): child is SyntaxNode => child !== null && child.type.includes('parameter_declaration'),
);
const hasEllipsis =
parameters.children.some(
(child) => child.type === '...' || (!child.isNamed && child.text === '...'),
) || parameterNodes.some((parameter) => parameter.type === 'variadic_parameter_declaration');
const isVoidOnly =
parameterNodes.length === 1 &&
parameterNodes[0]!.namedChildCount === 1 &&
parameterNodes[0]!.firstNamedChild?.text === 'void';
if (isVoidOnly) {
return { parameterCount: 0, parameterTypes: [], parameterTypeClasses: [] };
}
const parameterTypes: string[] = [];
const parameterTypeClasses: ParameterTypeClass[] = [];
for (const parameter of parameterNodes) {
if (parameter.type === 'variadic_parameter_declaration') {
parameterTypes.push('...');
parameterTypeClasses.push({
base: '...',
cv: 'unknown',
indirection: 'unknown',
pointerDepth: 0,
});
continue;
}
const rawType = parameter.childForFieldName('type')?.text ?? 'unknown';
const declaratorText = parameter.childForFieldName('declarator')?.text;
parameterTypes.push(normalizeCppParamType(rawType));
parameterTypeClasses.push(classifyCppParameterType(rawType, declaratorText, parameter.text));
}
if (hasEllipsis && !parameterTypes.includes('...')) {
parameterTypes.push('...');
parameterTypeClasses.push({
base: '...',
cv: 'unknown',
indirection: 'unknown',
pointerDepth: 0,
});
}
return {
...(hasEllipsis ? {} : { parameterCount: parameterNodes.length }),
parameterTypes,
parameterTypeClasses,
isConst: /\)\s*const(?:\s|$)/.test(declarator.text),
};
}
/**
* tree-sitter-cpp parses a non-const pointer-to-member variable such as
* `void (Base::*member)()` as nested call expressions (while the cv-qualified
* twin is a declaration). Recover the callable shape from the declarator text
* so `const`/non-`const` overload sets remain distinguishable.
*/
function recoverCppMemberPointerSignature(text: string): CallableCaptureSignature | undefined {
const match = text.match(/\(\s*[^()]*::\s*\*\s*[A-Za-z_]\w*\s*\)\s*\(([^()]*)\)\s*(const\b)?/);
if (match === null) return undefined;
const rawParameters = match[1]!.trim();
const parameterCount =
rawParameters === '' || rawParameters === 'void'
? 0
: splitTopLevelCppParameters(rawParameters).length;
return { parameterCount, isConst: match[2] !== undefined };
}
function splitTopLevelCppParameters(text: string): string[] {
const out: string[] = [];
let start = 0;
let angleDepth = 0;
let parenDepth = 0;
let bracketDepth = 0;
for (let index = 0; index < text.length; index++) {
switch (text[index]) {
case '<':
angleDepth++;
break;
case '>':
angleDepth = Math.max(0, angleDepth - 1);
break;
case '(':
parenDepth++;
break;
case ')':
parenDepth = Math.max(0, parenDepth - 1);
break;
case '[':
bracketDepth++;
break;
case ']':
bracketDepth = Math.max(0, bracketDepth - 1);
break;
case ',':
if (angleDepth === 0 && parenDepth === 0 && bracketDepth === 0) {
out.push(text.slice(start, index).trim());
start = index + 1;
}
break;
}
}
out.push(text.slice(start).trim());
return out.filter((parameter) => parameter.length > 0);
}
function cppContainsNodeType(root: SyntaxNode, type: string): boolean {
return cppFindDescendantOfType(root, type) !== null;
}
function cppFindDescendantOfType(root: SyntaxNode, type: string): SyntaxNode | null {
const stack: SyntaxNode[] = [root];
while (stack.length > 0) {
const node = stack.pop()!;
if (node.type === type) return node;
for (const child of node.namedChildren) if (child !== null) stack.push(child);
}
return null;
}
function extractCppDeclarationReturnType(fnNode: SyntaxNode): string | undefined {
const typeNode = fnNode.childForFieldName('type');
if (typeNode === null) return undefined;

View file

@ -288,6 +288,20 @@ export const cppScopeResolver: ScopeResolver = {
return false;
},
// Keep declaration/definition identity narrower than the global free-call
// visibility gate above: namespace and class ownership do not imply
// internal linkage, while a namespace-scope `static` function does.
hasFileLocalCallableLinkage: (def: SymbolDefinition) => {
// Class members have EXTERNAL linkage even when declared `static` —
// inside a class, `static` means "no instance", not internal linkage.
// The name-keyed set below is populated from every `static` declaration,
// so without this gate a header-declared static member refused its
// cross-file definition join (#2522 review, M2).
if (def.type === 'Method' || def.type === 'Constructor') return false;
const simple = def.qualifiedName?.split('.').pop() ?? def.qualifiedName ?? '';
return isFileLocal(def.filePath, simple);
},
// C++ two-phase template lookup: inside a class template body,
// unqualified calls MUST NOT bind to members of a dependent base
// class. The standard requires `this->name()` or `Base<T>::name()`

View file

@ -30,6 +30,7 @@ import { getCsharpParser, getCsharpScopeQuery } from './query.js';
import { recordCacheHit, recordCacheMiss } from './cache-stats.js';
import { getTreeSitterBufferSize } from '../../constants.js';
import { parseSourceSafe } from '../../../tree-sitter/safe-parse.js';
import { synthesizeCallableFlowCaptures } from '../../utils/callable-flow-captures.js';
/** Declaration anchors that carry function-like arity metadata. */
const FUNCTION_DECL_TAGS = [
@ -48,6 +49,28 @@ const FUNCTION_NODE_TYPES = [
'local_function_statement',
] as const;
const CSHARP_CALLABLE_CAPTURE_OPTIONS = {
functionNodeTypes: new Set([
...FUNCTION_NODE_TYPES,
'lambda_expression',
'anonymous_method_expression',
]),
callNodeTypes: new Set(['invocation_expression']),
parameterListNodeTypes: new Set(['parameter_list', 'argument_list']),
parameterNodeTypes: new Set(['parameter']),
bindingNodeTypes: new Set(['variable_declarator']),
assignmentNodeTypes: new Set(['assignment_expression']),
identifierNodeTypes: new Set(['identifier', 'generic_name']),
callableProtocolMethods: new Set(['Invoke']),
extractAssignment: (node: SyntaxNode) => {
if (node.type !== 'variable_declarator') return undefined;
const destination = node.childForFieldName('name');
const source = node.lastNamedChild;
if (destination === null || source === null || source.id === destination.id) return undefined;
return { destination, source };
},
} as const;
const BUILTIN_TYPE_NAMES = new Set([
'bool',
'byte',
@ -292,6 +315,7 @@ export function emitCsharpScopeCaptures(
out.push(...synthesizeGenericTypeArgumentReferences(tree.rootNode));
out.push(...synthesizeCsharpInheritanceReferences(tree.rootNode));
out.push(...synthesizeCsharpConstructorInitializerReferences(tree.rootNode));
out.push(...synthesizeCallableFlowCaptures(tree.rootNode, CSHARP_CALLABLE_CAPTURE_OPTIONS));
return out;
}

View file

@ -44,6 +44,7 @@ import { getTreeSitterBufferSize } from '../../constants.js';
import { parseSourceSafe } from '../../../tree-sitter/safe-parse.js';
import { encodeMarker } from '../../utils/heritage-marker.js';
import { DART_BUILT_INS } from './built-ins.js';
import { synthesizeCallableFlowCaptures } from '../../utils/callable-flow-captures.js';
const FUNCTION_DECL_TAGS = [
'@declaration.function',
@ -51,6 +52,34 @@ const FUNCTION_DECL_TAGS = [
'@declaration.constructor',
] as const;
const DART_CALLABLE_CAPTURE_OPTIONS = {
functionNodeTypes: new Set(['function_signature', 'function_expression']),
callNodeTypes: new Set(['selector']),
parameterListNodeTypes: new Set(['formal_parameter_list', 'arguments']),
parameterNodeTypes: new Set(['formal_parameter']),
bindingNodeTypes: new Set(['initialized_variable_definition']),
assignmentNodeTypes: new Set(['assignment_expression']),
identifierNodeTypes: new Set(['identifier', 'type_identifier']),
lexicalFunctionOwner: (node: SyntaxNode) => dartLexicalFunctionOwner(node),
isCallNode: (node: SyntaxNode) => node.namedChild(0)?.type === 'argument_part',
extractCallCallee: (node: SyntaxNode) => dartCallableCallee(node) ?? undefined,
callSiteNode: (node: SyntaxNode) => dartCallableCallee(node) ?? undefined,
callableProtocolMethods: new Set(['call']),
} as const;
function dartLexicalFunctionOwner(input: SyntaxNode): SyntaxNode | undefined {
let node: SyntaxNode | null = input;
while (node !== null) {
if (node.type === 'function_signature' || node.type === 'function_expression') return node;
if (node.type === 'function_body') {
const signature = node.previousNamedSibling;
if (signature?.type === 'function_signature') return signature;
}
node = node.parent;
}
return undefined;
}
export function emitDartScopeCaptures(
sourceText: string,
_filePath: string,
@ -159,9 +188,20 @@ export function emitDartScopeCaptures(
}
});
out.push(...synthesizeCallableFlowCaptures(root, DART_CALLABLE_CAPTURE_OPTIONS));
return out;
}
function dartCallableCallee(selector: SyntaxNode): SyntaxNode | null {
if (selector.namedChild(0)?.type !== 'argument_part') return null;
const previous = selector.previousNamedSibling;
if (previous?.type === 'identifier') return previous;
if (previous?.type !== 'selector') return null;
const inner = previous.namedChild(0);
return inner !== null && ASSIGNABLE_SELECTORS.has(inner.type) ? selectorName(inner) : null;
}
// ─── Function scope synthesis ───────────────────────────────────────────────
/**

View file

@ -13,6 +13,36 @@ import { synthesizeGoReceiverBinding } from './receiver-binding.js';
import { synthesizeGoTypeBindings, extractSimpleTypeNameText } from './type-binding.js';
import { getTreeSitterBufferSize } from '../../constants.js';
import { parseSourceSafe } from '../../../tree-sitter/safe-parse.js';
import { synthesizeCallableFlowCaptures } from '../../utils/callable-flow-captures.js';
const GO_CALLABLE_CAPTURE_OPTIONS = {
functionNodeTypes: new Set(['function_declaration', 'method_declaration', 'func_literal']),
callNodeTypes: new Set(['call_expression']),
parameterListNodeTypes: new Set(['parameter_list', 'argument_list']),
parameterNodeTypes: new Set(['parameter_declaration', 'variadic_parameter_declaration']),
bindingNodeTypes: new Set(['short_var_declaration', 'var_spec']),
assignmentNodeTypes: new Set(['assignment_statement']),
identifierNodeTypes: new Set(['identifier', 'field_identifier', 'package_identifier']),
// Multi-value forms (`a, b := f, g`) pair LHS/RHS expression_list entries
// positionally — the shared field fallback cross-wired first-LHS with
// last-RHS and synthesized a garbage comma-joined qualified name (#2522
// review). A length mismatch (multi-return call RHS) recognizes the node
// but emits nothing.
extractAssignment: (node: SyntaxNode) => {
if (node.type !== 'short_var_declaration' && node.type !== 'assignment_statement') {
return undefined;
}
const left = node.childForFieldName('left');
const right = node.childForFieldName('right');
if (left === null || right === null) return undefined;
if (left.type !== 'expression_list' || right.type !== 'expression_list') return undefined;
const destinations = left.namedChildren.filter((child): child is SyntaxNode => child !== null);
const sources = right.namedChildren.filter((child): child is SyntaxNode => child !== null);
if (destinations.length <= 1 && sources.length <= 1) return undefined;
if (destinations.length !== sources.length) return [];
return destinations.map((destination, index) => ({ destination, source: sources[index]! }));
},
} as const;
export function emitGoScopeCaptures(
sourceText: string,
@ -175,6 +205,7 @@ export function emitGoScopeCaptures(
}
out.push(...synthesizeGoInheritanceReferences(tree.rootNode));
out.push(...synthesizeCallableFlowCaptures(tree.rootNode, GO_CALLABLE_CAPTURE_OPTIONS));
return out;
}

View file

@ -23,6 +23,7 @@ import { getJavaParser, getJavaScopeQuery } from './query.js';
import { recordCacheHit, recordCacheMiss } from './cache-stats.js';
import { getTreeSitterBufferSize } from '../../constants.js';
import { parseSourceSafe } from '../../../tree-sitter/safe-parse.js';
import { synthesizeCallableFlowCaptures } from '../../utils/callable-flow-captures.js';
/** Declaration anchors that carry function-like arity metadata. */
const FUNCTION_DECL_TAGS = ['@declaration.method', '@declaration.constructor'] as const;
@ -30,6 +31,25 @@ const FUNCTION_DECL_TAGS = ['@declaration.method', '@declaration.constructor'] a
/** tree-sitter-java node types that the method extractor accepts. */
const FUNCTION_NODE_TYPES = ['method_declaration', 'constructor_declaration'] as const;
const JAVA_CALLABLE_CAPTURE_OPTIONS = {
functionNodeTypes: new Set([...FUNCTION_NODE_TYPES, 'lambda_expression']),
callNodeTypes: new Set(['method_invocation']),
parameterListNodeTypes: new Set(['formal_parameters', 'argument_list']),
parameterNodeTypes: new Set(['formal_parameter', 'spread_parameter', 'receiver_parameter']),
bindingNodeTypes: new Set(['variable_declarator']),
assignmentNodeTypes: new Set(['assignment_expression']),
identifierNodeTypes: new Set(['identifier', 'type_identifier']),
callableReferenceNodeTypes: new Set(['method_reference']),
// java.util.function SAM names, MINUS 'get' and 'test': those two collide
// with ubiquitous non-functional APIs (Map/List/Optional/Future.get,
// Predicate-unrelated test methods), emitting a spurious callable-object
// invoke fact for every container access (#2522 review). Supplier.get /
// Predicate.test dispatch is deliberately traded away until the check can
// gate on the receiver's declared type.
callableProtocolMethods: new Set(['run', 'apply', 'accept', 'call']),
normalizeQualifiedName: (raw: string) => raw.replaceAll('::', '.'),
} as const;
/** Suppress read.member emissions when the field_access is already
* covered by a method_invocation (object of a call) or an
* assignment_expression (write target). */
@ -220,6 +240,7 @@ export function emitJavaScopeCaptures(
...resolveVarTypeBindings(out),
...synthesizeJavaInheritanceReferences(tree.rootNode),
...synthesizeJavaExplicitConstructorReferences(tree.rootNode),
...synthesizeCallableFlowCaptures(tree.rootNode, JAVA_CALLABLE_CAPTURE_OPTIONS),
];
}

View file

@ -41,6 +41,7 @@ import { synthesizeTsReceiverBinding } from '../typescript/receiver-binding.js';
import { isArrayMethodCallbackArrow } from '../typescript/array-callback.js';
import { getTreeSitterBufferSize } from '../../constants.js';
import { parseSourceSafe } from '../../../tree-sitter/safe-parse.js';
import { synthesizeCallableFlowCaptures } from '../../utils/callable-flow-captures.js';
import {
deriveDefaultExportHocName,
isBlockedDefaultExportHoc,
@ -67,6 +68,21 @@ const CALL_TAGS = [
'@reference.call.constructor',
] as const;
const JS_CALLABLE_CAPTURE_OPTIONS = {
functionNodeTypes: new Set<string>(FUNCTION_NODE_TYPES),
callNodeTypes: new Set(['call_expression']),
parameterListNodeTypes: new Set(['formal_parameters', 'arguments']),
parameterNodeTypes: new Set(['identifier', 'rest_pattern', 'assignment_pattern']),
bindingNodeTypes: new Set(['variable_declarator']),
assignmentNodeTypes: new Set(['assignment_expression', 'augmented_assignment_expression']),
identifierNodeTypes: new Set([
'identifier',
'property_identifier',
'shorthand_property_identifier_pattern',
'private_property_identifier',
]),
} as const;
function pickFirstDefined(grouped: CaptureMatch, tags: readonly string[]): Capture | undefined {
for (const tag of tags) {
const cap = grouped[tag];
@ -940,6 +956,7 @@ export function emitJsScopeCaptures(
synthesizeForOfMapTupleBindings(tree.rootNode, out);
synthesizeInstanceofNarrowings(tree.rootNode, out);
synthesizeJsInheritanceReferences(tree.rootNode, out);
out.push(...synthesizeCallableFlowCaptures(tree.rootNode, JS_CALLABLE_CAPTURE_OPTIONS));
return out;
}

View file

@ -464,6 +464,20 @@ const JAVASCRIPT_SCOPE_QUERY = `
(member_expression
object: (_) @reference.receiver
property: (property_identifier) @reference.name) @reference.read.member
;; Value position (#2437): function identifier as object-literal property
;; value ({ emitScopeCaptures: emitHook }) or shorthand ({ emitHook }).
;; Resolution is callable-gated (MethodRegistry) and emits a USES reference;
;; @reference.property-key feeds the property-dispatch pass, which
;; synthesizes CALLS at x.<key>() sites. Two separate patterns (tree-sitter
;; 0.21 alternation hazard); destructuring shorthand is
;; shorthand_property_identifier_pattern and cannot match.
(pair
key: (property_identifier) @reference.property-key
value: (identifier) @reference.name @reference.value-ref)
(object
(shorthand_property_identifier) @reference.name @reference.property-key @reference.value-ref)
`;
/** JSX-only suffix — appended when compiling against the JSX grammar for .jsx files. */

View file

@ -14,9 +14,51 @@ import { normalizeKotlinType } from './interpret.js';
import { synthesizeKotlinReceiverBinding } from './receiver-binding.js';
import { getKotlinParser, getKotlinScopeQuery } from './query.js';
import { markCompanionScope } from './companion-scopes.js';
import { synthesizeCallableFlowCaptures } from '../../utils/callable-flow-captures.js';
const FUNCTION_DECL_TAGS = ['@declaration.function'] as const;
const KOTLIN_CALLABLE_CAPTURE_OPTIONS = {
functionNodeTypes: new Set(['function_declaration', 'anonymous_function', 'lambda_literal']),
callNodeTypes: new Set(['call_expression']),
parameterListNodeTypes: new Set(['function_value_parameters', 'value_arguments']),
parameterNodeTypes: new Set(['parameter']),
bindingNodeTypes: new Set(['property_declaration']),
assignmentNodeTypes: new Set(['assignment']),
identifierNodeTypes: new Set(['simple_identifier', 'type_identifier']),
callableReferenceNodeTypes: new Set(['callable_reference']),
callableProtocolMethods: new Set(['invoke']),
functionName: (node: SyntaxNode) =>
node.namedChildren.find(
(child): child is SyntaxNode => child !== null && child.type === 'simple_identifier',
)?.text,
extractAssignment: (node: SyntaxNode) => {
// tree-sitter-kotlin's `assignment` node is FIELDLESS (positional
// `directly_assignable_expression` then the value), so the shared
// field-based fallback returned nothing and nested reassignments
// (`chosen = ::target` inside a block) never produced flow facts
// (#2522 review, shallow-coverage gap).
if (node.type === 'assignment') {
const named = node.namedChildren.filter((child): child is SyntaxNode => child !== null);
if (named.length < 2) return undefined;
return { destination: named[0]!, source: named[named.length - 1]! };
}
if (node.type !== 'property_declaration') return undefined;
if (!node.children.some((child) => child.text === '=')) return undefined;
const destination = node.namedChildren.find(
(child): child is SyntaxNode => child !== null && child.type === 'variable_declaration',
);
const source = [...node.namedChildren]
.reverse()
.find(
(child): child is SyntaxNode =>
child !== null && child.id !== destination?.id && child.type !== 'binding_pattern_kind',
);
return destination === undefined || source === undefined ? undefined : { destination, source };
},
normalizeQualifiedName: (raw: string) => raw.replaceAll('::', '.'),
} as const;
export function emitKotlinScopeCaptures(
sourceText: string,
filePath: string,
@ -222,6 +264,8 @@ export function emitKotlinScopeCaptures(
if (extensionFallback !== null) out.push(extensionFallback);
}
out.push(...synthesizeCallableFlowCaptures(tree.rootNode, KOTLIN_CALLABLE_CAPTURE_OPTIONS));
return out;
}

View file

@ -45,9 +45,41 @@ import { getPhpParser, getPhpScopeQuery } from './query.js';
import { recordCacheHit, recordCacheMiss } from './cache-stats.js';
import { getTreeSitterBufferSize } from '../../constants.js';
import { parseSourceSafe } from '../../../tree-sitter/safe-parse.js';
import { synthesizeCallableFlowCaptures } from '../../utils/callable-flow-captures.js';
type SyntaxNode = ReturnType<ReturnType<typeof getPhpParser>['parse']>['rootNode'];
const PHP_CALLABLE_CAPTURE_OPTIONS = {
functionNodeTypes: new Set([
'function_definition',
'method_declaration',
'anonymous_function',
'arrow_function',
]),
callNodeTypes: new Set(['function_call_expression']),
parameterListNodeTypes: new Set(['formal_parameters', 'arguments']),
// tree-sitter-php has no 'optional_parameter' node (defaults ride on
// simple_parameter); property promotion is constructor-only and carries no
// callable-flow value (#2522 review).
parameterNodeTypes: new Set(['simple_parameter', 'variadic_parameter']),
bindingNodeTypes: new Set(['assignment_expression']),
assignmentNodeTypes: new Set(['assignment_expression']),
identifierNodeTypes: new Set(['name', 'qualified_name', 'namespace_name']),
functionScopedValueBindings: true,
emitCanonicalInvokeReference: true,
extractCallableReference: (node: SyntaxNode) => {
if (node.type !== 'function_call_expression') return undefined;
const args = node.childForFieldName('arguments');
const isFirstClass =
args?.namedChildren.some(
(child) => child !== null && child.type === 'variadic_placeholder',
) === true;
const target = node.childForFieldName('function');
if (!isFirstClass || target === null || target.type === 'variable_name') return undefined;
return { name: target.text.replace(/^\\+/, ''), anchor: target };
},
} as const;
/** Declaration anchors that carry function-like arity metadata. */
const FUNCTION_DECL_TAGS = ['@declaration.method', '@declaration.function'] as const;
@ -259,6 +291,19 @@ export function emitPhpScopeCaptures(
const callTag = (
['@reference.call.free', '@reference.call.member', '@reference.call.constructor'] as const
).find((t) => grouped[t] !== undefined);
if (callTag !== undefined) {
const possibleFirstClass = nodeIfType(nodeMap[callTag], 'function_call_expression');
const argumentsNode = possibleFirstClass?.childForFieldName('arguments');
if (
argumentsNode?.namedChildren.some(
(child) => child !== null && child.type === 'variadic_placeholder',
) === true
) {
// PHP 8.1 `target(...)` creates a Closure; it does not invoke target.
// Callable-flow synthesis below owns this site as a seed.
continue;
}
}
if (callTag !== undefined && grouped['@reference.arity'] === undefined) {
const callNode = nodeIfType(
nodeMap[callTag],
@ -297,6 +342,7 @@ export function emitPhpScopeCaptures(
}
out.push(...synthesizePhpInheritanceReferences(tree.rootNode));
out.push(...synthesizeCallableFlowCaptures(tree.rootNode, PHP_CALLABLE_CAPTURE_OPTIONS));
return out;
}

View file

@ -32,6 +32,25 @@ import { recordCacheHit, recordCacheMiss } from './cache-stats.js';
import { getTreeSitterBufferSize } from '../../constants.js';
import { parseSourceSafe } from '../../../tree-sitter/safe-parse.js';
import { pythonFunctionDefinitionLabel } from './simple-hooks.js';
import { synthesizeCallableFlowCaptures } from '../../utils/callable-flow-captures.js';
const PYTHON_CALLABLE_CAPTURE_OPTIONS = {
functionNodeTypes: new Set(['function_definition', 'lambda']),
callNodeTypes: new Set(['call']),
parameterListNodeTypes: new Set(['parameters', 'argument_list']),
parameterNodeTypes: new Set([
'identifier',
'default_parameter',
'typed_parameter',
'typed_default_parameter',
'list_splat_pattern',
'dictionary_splat_pattern',
]),
bindingNodeTypes: new Set(['assignment', 'named_expression']),
assignmentNodeTypes: new Set(['assignment', 'named_expression']),
identifierNodeTypes: new Set(['identifier']),
functionScopedValueBindings: true,
} as const;
export function emitPythonScopeCaptures(
sourceText: string,
@ -169,6 +188,7 @@ export function emitPythonScopeCaptures(
}
out.push(...synthesizePythonInheritanceReferences(tree.rootNode));
out.push(...synthesizeCallableFlowCaptures(tree.rootNode, PYTHON_CALLABLE_CAPTURE_OPTIONS));
return out;
}

View file

@ -14,6 +14,7 @@ import { getTreeSitterBufferSize } from '../../constants.js';
import { parseSourceSafe } from '../../../tree-sitter/safe-parse.js';
import { splitQualifiedName } from '../../utils/qualified-name.js';
import { encodeMarker } from '../../utils/heritage-marker.js';
import { synthesizeCallableFlowCaptures } from '../../utils/callable-flow-captures.js';
const FUNCTION_NODE_TYPES = ['method', 'singleton_method'] as const;
const HERITAGE_CALL_NAMES: ReadonlySet<string> = new Set(['include', 'extend', 'prepend']);
@ -23,6 +24,46 @@ const ATTR_CALL_NAMES: ReadonlySet<string> = new Set([
'attr_writer',
]);
const RUBY_CALLABLE_CAPTURE_OPTIONS = {
functionNodeTypes: new Set(['method', 'singleton_method', 'lambda']),
callNodeTypes: new Set(['call']),
parameterListNodeTypes: new Set(['method_parameters', 'argument_list']),
parameterNodeTypes: new Set([
'identifier',
'optional_parameter',
'splat_parameter',
'hash_splat_parameter',
'block_parameter',
]),
bindingNodeTypes: new Set(['assignment']),
assignmentNodeTypes: new Set(['assignment', 'operator_assignment']),
identifierNodeTypes: new Set([
'identifier',
'constant',
'instance_variable',
'class_variable',
'global_variable',
]),
functionScopedValueBindings: true,
callableProtocolMethods: new Set(['call']),
// A bare receiver-less identifier in value position is a method CALL in
// Ruby (`action = process` stores process's RETURN value) — only explicit
// reference forms (method(:x), &:x, lambda/proc) reference the callable.
bareNamesAreCalls: true,
extractCallableReference: (node: SyntaxNode) => {
if (node.type !== 'call') return undefined;
const method = node.childForFieldName('method');
if (method?.text !== 'method' && method?.text !== 'public_method') return undefined;
const args = node.childForFieldName('arguments');
const symbol = args?.namedChildren.find(
(child): child is SyntaxNode => child !== null && child.type === 'simple_symbol',
);
if (symbol === undefined) return undefined;
const name = symbol.text.replace(/^:/, '');
return name.length === 0 ? undefined : { name, anchor: symbol };
},
} as const;
/**
* Build the full `.`-joined qualified owner name for a heritage/attr call by
* walking ALL enclosing class/module ancestors (not just the immediate one),
@ -477,6 +518,7 @@ export function emitRubyScopeCaptures(
// flow through `emitHeritageEdges` (the `__heritage__:` import path above),
// an independent lane that stays intact when the legacy heritage leg is gated off.
out.push(...synthesizeRubySuperclassReferences(tree.rootNode));
out.push(...synthesizeCallableFlowCaptures(tree.rootNode, RUBY_CALLABLE_CAPTURE_OPTIONS));
return out;
}

View file

@ -12,6 +12,19 @@ import { splitRustUseDeclaration } from './import-decomposer.js';
import { synthesizeRustReceiverBinding } from './receiver-binding.js';
import { getTreeSitterBufferSize } from '../../constants.js';
import { parseSourceSafe } from '../../../tree-sitter/safe-parse.js';
import { synthesizeCallableFlowCaptures } from '../../utils/callable-flow-captures.js';
const RUST_CALLABLE_CAPTURE_OPTIONS = {
functionNodeTypes: new Set(['function_item', 'closure_expression']),
callNodeTypes: new Set(['call_expression']),
parameterListNodeTypes: new Set(['parameters', 'arguments']),
parameterNodeTypes: new Set(['parameter', 'self_parameter']),
bindingNodeTypes: new Set(['let_declaration']),
assignmentNodeTypes: new Set(['assignment_expression']),
identifierNodeTypes: new Set(['identifier', 'type_identifier', 'field_identifier']),
callableReferenceNodeTypes: new Set(['scoped_identifier']),
normalizeQualifiedName: (raw: string) => raw.replaceAll('::', '.'),
} as const;
export function emitRustScopeCaptures(
sourceText: string,
@ -163,6 +176,7 @@ export function emitRustScopeCaptures(
}
out.push(...synthesizeRustInheritanceReferences(tree.rootNode));
out.push(...synthesizeCallableFlowCaptures(tree.rootNode, RUST_CALLABLE_CAPTURE_OPTIONS));
return out;
}

View file

@ -49,10 +49,25 @@ import { getSwiftParser, getSwiftScopeQuery } from './query.js';
import { recordCacheHit, recordCacheMiss } from './cache-stats.js';
import { getTreeSitterBufferSize } from '../../constants.js';
import { parseSourceSafe } from '../../../tree-sitter/safe-parse.js';
import { synthesizeCallableFlowCaptures } from '../../utils/callable-flow-captures.js';
/** Declaration anchors that carry function-like arity metadata. */
const FUNCTION_DECL_TAGS = ['@declaration.method', '@declaration.constructor'] as const;
const SWIFT_CALLABLE_CAPTURE_OPTIONS = {
functionNodeTypes: new Set(['function_declaration', 'lambda_literal']),
callNodeTypes: new Set(['call_expression']),
parameterListNodeTypes: new Set(['value_arguments']),
parameterNodeTypes: new Set(['parameter']),
bindingNodeTypes: new Set(['property_declaration']),
assignmentNodeTypes: new Set(['assignment']),
identifierNodeTypes: new Set(['simple_identifier', 'type_identifier']),
extractFunctionParameters: (node: SyntaxNode) =>
node.namedChildren.filter(
(child): child is SyntaxNode => child !== null && child.type === 'parameter',
),
} as const;
/** tree-sitter-swift node types that carry arity. */
const FUNCTION_NODE_TYPES = [
'function_declaration',
@ -292,6 +307,7 @@ export function emitSwiftScopeCaptures(
// legacy heritage-capture leg (removed in #942), which the worker pipeline
// drops for registry-primary languages (issue #1951).
out.push(...synthesizeSwiftInheritanceReferences(tree.rootNode));
out.push(...synthesizeCallableFlowCaptures(tree.rootNode, SWIFT_CALLABLE_CAPTURE_OPTIONS));
return out;
}

View file

@ -40,6 +40,7 @@ import { computeTsArityMetadata } from './arity-metadata.js';
import { isArrayMethodCallbackArrow } from './array-callback.js';
import { getTreeSitterBufferSize } from '../../constants.js';
import { parseSourceSafe } from '../../../tree-sitter/safe-parse.js';
import { synthesizeCallableFlowCaptures } from '../../utils/callable-flow-captures.js';
import {
deriveDefaultExportHocName,
isBlockedDefaultExportHoc,
@ -70,6 +71,26 @@ const CALL_TAGS = [
'@reference.call.constructor',
] as const;
const TS_CALLABLE_CAPTURE_OPTIONS = {
functionNodeTypes: new Set<string>(FUNCTION_NODE_TYPES),
callNodeTypes: new Set(['call_expression']),
parameterListNodeTypes: new Set(['formal_parameters', 'arguments']),
parameterNodeTypes: new Set([
'required_parameter',
'optional_parameter',
'rest_pattern',
'identifier',
]),
bindingNodeTypes: new Set(['variable_declarator']),
assignmentNodeTypes: new Set(['assignment_expression', 'augmented_assignment_expression']),
identifierNodeTypes: new Set([
'identifier',
'property_identifier',
'shorthand_property_identifier_pattern',
'private_property_identifier',
]),
} as const;
function pickFirstCapture(grouped: CaptureMatch, tags: readonly string[]): Capture | undefined {
for (const tag of tags) {
const cap = grouped[tag];
@ -421,6 +442,7 @@ export function emitTsScopeCaptures(
synthesizeForOfMapTupleBindings(tree.rootNode, out);
synthesizeInstanceofNarrowings(tree.rootNode, out);
synthesizeTsInheritanceReferences(tree.rootNode, out);
out.push(...synthesizeCallableFlowCaptures(tree.rootNode, TS_CALLABLE_CAPTURE_OPTIONS));
return out;
}

View file

@ -994,6 +994,26 @@ const TYPESCRIPT_SCOPE_QUERY = `
(member_expression
object: (_) @reference.receiver
property: (property_identifier) @reference.name) @reference.read.member
;; References — value position (#2437): a function identifier assigned as an
;; object-literal property value (\`{ emitScopeCaptures: emitCppScopeCaptures }\`)
;; or shorthand (\`{ emitHook }\`). Resolution keeps only callable targets
;; (MethodRegistry), so plain-value pairs (\`{ port: DEFAULT_PORT }\`) emit
;; nothing; the emitted edge is a reference-class USES (registration ≠
;; invocation). \`@reference.property-key\` records the key so the
;; property-dispatch pass can synthesize CALLS at \`x.<key>()\` sites; for
;; shorthand the key IS the name (both tags on one node). Two separate
;; patterns — never combined in \`[...]\` (tree-sitter 0.21 drops alternation
;; siblings around predicates). Destructuring shorthand is a different node
;; (\`shorthand_property_identifier_pattern\`), so \`const { a } = o\` cannot
;; match; string/computed keys carry no \`property_identifier\` and register
;; without a dispatch key.
(pair
key: (property_identifier) @reference.property-key
value: (identifier) @reference.name @reference.value-ref)
(object
(shorthand_property_identifier) @reference.name @reference.property-key @reference.value-ref)
`;
/**

View file

@ -414,7 +414,8 @@ export async function runChunkedParseAndResolve(
const skippedByLang = new Map<string, number>();
for (const f of scannedFiles) {
const lang = getLanguageFromFilename(f.path);
if (lang && !isLanguageAvailable(lang)) {
const provider = lang === null ? undefined : getProvider(lang);
if (lang && provider?.parseStrategy !== 'standalone' && !isLanguageAvailable(lang)) {
skippedByLang.set(lang, (skippedByLang.get(lang) || 0) + 1);
}
}

View file

@ -115,6 +115,11 @@ export function resolveReferenceSites(input: ResolveReferencesInput): ResolveRef
let unresolved = 0;
for (const site of scopes.referenceSites) {
// value-ref sites resolve post-finalize (imports live in finalized
// bindings the registries can't see — same reason free calls need
// `emitFreeCallFallback`). `emitPropertyDispatchCalls` owns their
// resolution and emission entirely (#2437).
if (site.kind === 'value-ref') continue;
sitesProcessed++;
const resolutions = lookupForSite(
@ -174,6 +179,7 @@ export function resolveReferenceSites(input: ResolveReferencesInput): ResolveRef
* | `type-reference` | ClassRegistry | CLASS_KINDS |
* | `read`/`write` | FieldRegistry | FIELD_KINDS |
* | `import-use` | tiered fallback | METHOD ∪ CLASS ∪ FIELD |
* | `value-ref` | (skipped here) | post-finalize walker in `emitPropertyDispatchCalls` |
* | `macro` | MacroRegistry | MACRO_KINDS (`Macro` only) |
*
* `macro` has its own single-kind registry so a macro invocation
@ -229,6 +235,11 @@ function lookupForSite(
if (methodHits.length > 0) return methodHits;
return fieldRegistry.lookup(site.name, site.inScope);
}
case 'value-ref': {
// Unreachable — filtered before lookup (post-finalize resolution in
// `emitPropertyDispatchCalls`, #2437). Kept for switch exhaustiveness.
return [];
}
case 'macro': {
// Macro-only namespace: resolves against `Macro`-labeled defs, never
// functions. No receiver, no arity — see `MacroRegistry`.

View file

@ -61,6 +61,10 @@
import type {
BindingRef,
CallableFlowExpectedSignature,
CallableFlowOperand,
CallableFlowPassingMode,
CallableFlowSite,
CaptureMatch,
ImportEdge,
ParameterTypeClass,
@ -187,6 +191,12 @@ export function extract(
scopeTree,
);
// ── Pass 6: collect normalized callable-value-flow facts ───────────
// Kept after (and independent from) Pass 5 so existing reference-site
// extraction remains byte-identical.
const callableFlowSites: CallableFlowSite[] = [];
pass6CollectCallableFlows(partitioned.callableFlow, positionIndex, filePath, callableFlowSites);
// Freeze Scope drafts into final shape and return.
const frozenScopes = scopeDrafts.map(draftToScope);
return Object.freeze({
@ -196,6 +206,9 @@ export function extract(
parsedImports: Object.freeze(parsedImports.slice()),
localDefs: Object.freeze(localDefs.slice()),
referenceSites: Object.freeze(referenceSites.slice()),
...(callableFlowSites.length > 0
? { callableFlowSites: Object.freeze(callableFlowSites.slice()) }
: {}),
});
}
@ -207,12 +220,13 @@ interface Partitioned {
readonly import_: readonly CaptureMatch[];
readonly typeBinding: readonly CaptureMatch[];
readonly reference: readonly CaptureMatch[];
readonly callableFlow: readonly CaptureMatch[];
}
/**
* Bucket each match by the topic of its anchor capture. The anchor is the
* capture whose name is prefixed with the match's topic (`@scope.*`,
* `@declaration.*`, `@import.*`, `@type-binding.*`, `@reference.*`).
* Bucket each match by every topic represented by its anchor captures. An
* emitter may deliberately group a lexical scope and its declaration in one
* match so both passes observe the exact same source range.
*
* A match may contain additional captures (e.g., `@import.source`,
* `@declaration.class.name`) that are used by the provider hooks to
@ -225,49 +239,49 @@ function partitionByTopic(matches: readonly CaptureMatch[]): Partitioned {
const import_: CaptureMatch[] = [];
const typeBinding: CaptureMatch[] = [];
const reference: CaptureMatch[] = [];
const callableFlow: CaptureMatch[] = [];
for (const match of matches) {
const topic = topicOf(match);
switch (topic) {
case 'scope':
scope.push(match);
break;
case 'declaration':
declaration.push(match);
break;
case 'import':
import_.push(match);
break;
case 'type-binding':
typeBinding.push(match);
break;
case 'reference':
reference.push(match);
break;
case 'unknown':
// Unrecognized anchor — silently skip. Providers may emit extra
// captures (e.g., `@comment`) that the extractor has no topic for.
break;
for (const topic of topicsOf(match)) {
switch (topic) {
case 'scope':
scope.push(match);
break;
case 'declaration':
declaration.push(match);
break;
case 'import':
import_.push(match);
break;
case 'type-binding':
typeBinding.push(match);
break;
case 'reference':
reference.push(match);
break;
case 'callable-flow':
callableFlow.push(match);
break;
}
}
}
return { scope, declaration, import_, typeBinding, reference };
return { scope, declaration, import_, typeBinding, reference, callableFlow };
}
type Topic = 'scope' | 'declaration' | 'import' | 'type-binding' | 'reference' | 'unknown';
type Topic = 'scope' | 'declaration' | 'import' | 'type-binding' | 'reference' | 'callable-flow';
function topicOf(match: CaptureMatch): Topic {
// The anchor is the capture whose name uses one of the known topic
// prefixes. For multi-capture matches, ALL captures share the topic;
// we pick the first matching key for efficiency.
function topicsOf(match: CaptureMatch): ReadonlySet<Topic> {
const topics = new Set<Topic>();
for (const name of Object.keys(match)) {
if (name.startsWith('@scope.')) return 'scope';
if (name.startsWith('@declaration.')) return 'declaration';
if (name.startsWith('@import.')) return 'import';
if (name.startsWith('@type-binding.')) return 'type-binding';
if (name.startsWith('@reference.')) return 'reference';
if (name.startsWith('@scope.')) topics.add('scope');
else if (name.startsWith('@declaration.')) topics.add('declaration');
else if (name.startsWith('@import.')) topics.add('import');
else if (name.startsWith('@type-binding.')) topics.add('type-binding');
else if (name.startsWith('@reference.')) topics.add('reference');
else if (name.startsWith('@callable-flow.')) topics.add('callable-flow');
}
return 'unknown';
return topics;
}
// ─── Internal: Scope draft model ───────────────────────────────────────────
@ -754,6 +768,8 @@ function normalizeNodeLabel(kindStr: string): SymbolDefinition['type'] | undefin
return 'Annotation';
case 'namespace':
return 'Namespace';
case 'program':
return 'Module';
case 'macro':
return 'Macro';
default:
@ -1025,6 +1041,10 @@ function pass5CollectReferences(
match['@reference.parameter-type-classes'],
);
// Object-literal key for value-ref sites (`{ key: fn }` / shorthand);
// consumed by the property-dispatch pass (#2437).
const propertyKeyCap = match['@reference.property-key'];
const site: ReferenceSite = {
name: nameCap.text,
atRange: anchor.range,
@ -1033,6 +1053,9 @@ function pass5CollectReferences(
...(qualifiedCap?.text !== undefined && qualifiedCap.text.length > 0
? { rawQualifiedName: qualifiedCap.text }
: {}),
...(propertyKeyCap?.text !== undefined && propertyKeyCap.text.length > 0
? { propertyKey: propertyKeyCap.text }
: {}),
...(callForm !== undefined ? { callForm } : {}),
...(explicitReceiver !== undefined ? { explicitReceiver } : {}),
...(arity !== undefined ? { arity } : {}),
@ -1065,6 +1088,8 @@ function referenceKindFromAnchor(name: string): ReferenceKind | undefined {
return 'import-use';
case 'macro':
return 'macro';
case 'value-ref':
return 'value-ref';
default:
return undefined;
}
@ -1125,6 +1150,272 @@ function extractArgumentTypes(match: CaptureMatch): readonly string[] | undefine
return undefined;
}
// ─── Pass 6: collect callable-value-flow facts ─────────────────────────────
const CALLABLE_FLOW_KINDS = [
'seed',
'copy',
'alias',
'address',
'store',
'load',
'formal',
'argument',
'invoke',
] as const;
type CallableFlowKind = (typeof CALLABLE_FLOW_KINDS)[number];
function pass6CollectCallableFlows(
matches: readonly CaptureMatch[],
positionIndex: ReturnType<typeof buildPositionIndex>,
filePath: string,
out: CallableFlowSite[],
): void {
for (const match of matches) {
const kind = callableFlowKind(match);
if (kind === undefined) continue;
const anchor = match[`@callable-flow.${kind}`];
if (anchor === undefined) continue;
switch (kind) {
case 'seed': {
const destination = callableFlowOperand(match, 'destination', positionIndex, filePath);
const target = match['@callable-flow.target'];
const targetName = match['@callable-flow.target-name']?.text ?? target?.text;
if (destination === undefined || target === undefined || !nonEmpty(targetName)) continue;
const expectedSignature = callableFlowExpectedSignature(match);
out.push({
kind,
destination,
targetName,
targetRange: target.range,
...(nonEmpty(match['@callable-flow.target-qualified-name']?.text)
? { targetQualifiedName: match['@callable-flow.target-qualified-name']!.text }
: {}),
...(expectedSignature !== undefined ? { expectedSignature } : {}),
});
break;
}
case 'copy':
case 'alias': {
const source = callableFlowOperand(match, 'source', positionIndex, filePath);
const destination = callableFlowOperand(match, 'destination', positionIndex, filePath);
if (source === undefined || destination === undefined) continue;
out.push({ kind, source, destination });
break;
}
case 'address': {
const source = callableFlowOperand(match, 'source', positionIndex, filePath);
const destination = callableFlowOperand(match, 'destination', positionIndex, filePath);
if (source === undefined || destination === undefined) continue;
out.push({ kind, source, destination });
break;
}
case 'store': {
const source = callableFlowOperand(match, 'source', positionIndex, filePath);
const pointer = callableFlowOperand(match, 'pointer', positionIndex, filePath);
if (source === undefined || pointer === undefined) continue;
out.push({ kind, source, pointer });
break;
}
case 'load': {
const pointer = callableFlowOperand(match, 'pointer', positionIndex, filePath);
const destination = callableFlowOperand(match, 'destination', positionIndex, filePath);
if (pointer === undefined || destination === undefined) continue;
out.push({ kind, pointer, destination });
break;
}
case 'formal': {
const owner = match['@callable-flow.owner'];
const binding = callableFlowOperand(match, 'binding', positionIndex, filePath);
const parameterIndex = parseNonNegativeInt(match['@callable-flow.parameter-index']?.text);
const passingMode = parseCallablePassingMode(match['@callable-flow.passing-mode']?.text);
if (
owner === undefined ||
!nonEmpty(owner.text) ||
binding === undefined ||
parameterIndex === undefined ||
passingMode === undefined
) {
continue;
}
const expectedSignature = callableFlowExpectedSignature(match);
out.push({
kind,
ownerName: owner.text,
ownerRange: owner.range,
parameterIndex,
binding,
passingMode,
...(expectedSignature !== undefined ? { expectedSignature } : {}),
});
break;
}
case 'argument': {
const source = callableFlowOperand(match, 'source', positionIndex, filePath);
const parameterIndex = parseNonNegativeInt(match['@callable-flow.parameter-index']?.text);
if (source === undefined || parameterIndex === undefined) continue;
out.push({
kind,
callSite: anchor.range,
parameterIndex,
source,
...(nonEmpty(match['@callable-flow.direct-callee-name']?.text)
? { directCalleeName: match['@callable-flow.direct-callee-name']!.text }
: {}),
});
break;
}
case 'invoke': {
const callee = callableFlowOperand(match, 'callee', positionIndex, filePath);
const inScope = positionIndex.atPosition(
filePath,
anchor.range.startLine,
anchor.range.startCol,
);
const invocationKind = parseCallableInvocationKind(
match['@callable-flow.invocation-kind']?.text,
);
if (callee === undefined || inScope === undefined || invocationKind === undefined) continue;
const receiver = callableFlowOperand(match, 'receiver', positionIndex, filePath);
const arity = parseNonNegativeInt(match['@callable-flow.arity']?.text);
out.push({
kind,
callSite: anchor.range,
inScope,
callee,
invocationKind,
...(receiver !== undefined ? { receiver } : {}),
...(arity !== undefined ? { arity } : {}),
});
break;
}
}
}
}
function callableFlowKind(match: CaptureMatch): CallableFlowKind | undefined {
return CALLABLE_FLOW_KINDS.find((kind) => match[`@callable-flow.${kind}`] !== undefined);
}
function callableFlowOperand(
match: CaptureMatch,
role: 'source' | 'destination' | 'pointer' | 'binding' | 'callee' | 'receiver',
positionIndex: ReturnType<typeof buildPositionIndex>,
filePath: string,
): CallableFlowOperand | undefined {
const cap = match[`@callable-flow.${role}`];
if (cap === undefined || !nonEmpty(cap.text)) return undefined;
const inScope = positionIndex.atPosition(filePath, cap.range.startLine, cap.range.startCol);
if (inScope === undefined) return undefined;
const expressionKind = parseCallableOperandKind(match[`@callable-flow.${role}-kind`]?.text);
const indirection = parseNonNegativeInt(match[`@callable-flow.${role}-indirection`]?.text);
if (indirection !== undefined && indirection > 16) return undefined;
return {
name: cap.text,
inScope,
atRange: cap.range,
indirection: indirection ?? 0,
addressOf: match[`@callable-flow.${role}-address`]?.text === 'true',
...(expressionKind !== undefined ? { expressionKind } : {}),
...(nonEmpty(match[`@callable-flow.${role}-qualified-name`]?.text)
? { qualifiedName: match[`@callable-flow.${role}-qualified-name`]!.text }
: {}),
};
}
function callableFlowExpectedSignature(
match: CaptureMatch,
): CallableFlowExpectedSignature | undefined {
const parameterCount = parseNonNegativeInt(match['@callable-flow.expected-arity']?.text);
const parameterTypes = parseJsonStringArray(match['@callable-flow.expected-types']?.text);
const parameterTypeClasses = parseJsonParameterTypeClassesCapture(
match['@callable-flow.expected-type-classes'],
);
const isConst = parseBooleanText(match['@callable-flow.expected-const']?.text);
if (
parameterCount === undefined &&
parameterTypes === undefined &&
parameterTypeClasses === undefined &&
isConst === undefined
) {
return undefined;
}
return {
...(parameterCount !== undefined ? { parameterCount } : {}),
...(parameterTypes !== undefined ? { parameterTypes } : {}),
...(parameterTypeClasses !== undefined ? { parameterTypeClasses } : {}),
...(isConst !== undefined ? { isConst } : {}),
};
}
function parseCallableOperandKind(
text: string | undefined,
): 'binding' | 'callable-designator' | 'bound-member' | 'anonymous-callable' | undefined {
switch (text) {
case 'binding':
case 'callable-designator':
case 'bound-member':
case 'anonymous-callable':
return text;
default:
return undefined;
}
}
function parseBooleanText(text: string | undefined): boolean | undefined {
if (text === 'true') return true;
if (text === 'false') return false;
return undefined;
}
function parseJsonStringArray(text: string | undefined): readonly string[] | undefined {
if (text === undefined) return undefined;
try {
const parsed: unknown = JSON.parse(text);
return Array.isArray(parsed) && parsed.every((value) => typeof value === 'string')
? parsed
: undefined;
} catch {
return undefined;
}
}
function parseNonNegativeInt(text: string | undefined): number | undefined {
if (text === undefined || !/^\d+$/.test(text)) return undefined;
const value = Number.parseInt(text, 10);
return Number.isSafeInteger(value) ? value : undefined;
}
function parseCallablePassingMode(text: string | undefined): CallableFlowPassingMode | undefined {
switch (text) {
case 'value':
case 'reference':
case 'pointer':
return text;
default:
return undefined;
}
}
function parseCallableInvocationKind(
text: string | undefined,
): 'indirect' | 'member-pointer' | 'callable-object' | undefined {
switch (text) {
case 'indirect':
case 'member-pointer':
case 'callable-object':
return text;
default:
return undefined;
}
}
function nonEmpty(value: string | undefined): value is string {
return value !== undefined && value.length > 0;
}
// ─── Internal: range + capture utilities ───────────────────────────────────
function rangesEqual(a: Range, b: Range): boolean {
@ -1155,6 +1446,7 @@ const KNOWN_SUB_TAGS: ReadonlySet<string> = new Set<string>([
'@type-binding.type',
'@reference.name',
'@reference.qualified-name',
'@reference.property-key',
'@reference.receiver',
'@reference.operator',
'@reference.arity',

View file

@ -413,6 +413,23 @@ export interface ScopeResolver {
*/
arityCompatibility(callsite: Callsite, def: SymbolDefinition): ArityVerdict;
/**
* Add provider-specific callable value targets beyond the shared
* Function/Method/Constructor set. This is intentionally a predicate hook:
* shared flow analysis never branches on a language name or syntax kind.
*/
readonly isCallableValueTarget?: (def: SymbolDefinition) => boolean;
/**
* Restrict this provider's scope-resolution graph mutations to callable-value
* CALLS edges. Providers with an existing structural edge pipeline can use
* the shared scope model and callable solver without duplicating their
* established CALLS, IMPORTS, heritage, or property-dispatch edges.
*
* Default: `all`.
*/
readonly scopeResolutionEdgeMode?: 'all' | 'callable-flow-only';
/**
* Per-language constraint compatibility between a callsite and a
* candidate `def` that carries `templateConstraints` metadata.
@ -746,6 +763,20 @@ export interface ScopeResolver {
*/
readonly isFileLocalDef?: (def: SymbolDefinition) => boolean;
/**
* Optional precise linkage predicate used when callable-value flow joins a
* declaration graph node (for example a C/C++ prototype in a caller file)
* to its out-of-file definition. Unlike `isFileLocalDef`, this hook MUST
* answer only language-level internal/file-local linkage. It must not fold
* in broader unqualified-name visibility rules such as namespace or member
* lookup: an explicit declaration already establishes caller visibility.
*
* C and C++ provide this hook for `static` free functions. Languages whose
* declaration/definition identity is already represented by imports or one
* graph node leave it undefined, disabling cross-file prototype joining.
*/
readonly hasFileLocalCallableLinkage?: (def: SymbolDefinition) => boolean;
/**
* Optional predicate to identify members for which dispatch through
* an instance receiver is **invalid at the language level** — i.e.

View file

@ -21,9 +21,9 @@
* `(atRange.startLine, atRange.startCol)` is byte-equal to U1's `at` — no
* normalization needed.
*
* Gating (R4): the concrete sink is created in `run.ts` only when
* `input.pdg === true`; otherwise `undefined` is threaded through, so off-mode
* does zero work and emits byte-identical output.
* Gating (R4): the concrete sink is created in `run.ts` when PDG is enabled OR
* always-on callable-flow facts need direct call targets for actual→formal
* propagation. The read-side CFG join remains strictly PDG-gated.
*
* Multi-target dispatch (R2/KTD8): one site → multiple emit calls → the `Set`
* accumulates every resolved target. Capture is per-emit-call, so the
@ -74,15 +74,19 @@ export interface CalleeIdMapView {
/** The concrete accumulator: a write sink that also exposes the read view. */
export interface CalleeIdAccumulator extends CalleeIdSink, CalleeIdMapView {}
export type CalleeIdCaptureFilter = (filePath: string, line: number, col: number) => boolean;
/**
* Create the concrete nested-`Map` accumulator. Call ONLY when
* `input.pdg === true` (else thread `undefined` for byte-identity / zero
* overhead — R4).
* Create the concrete nested-`Map` accumulator for PDG callee capture or
* callable-value-flow's direct-target index.
*/
export function createCalleeIdAccumulator(): CalleeIdAccumulator {
export function createCalleeIdAccumulator(
shouldCapture?: CalleeIdCaptureFilter,
): CalleeIdAccumulator {
const byFile = new Map<string, Map<CalleeIdPosKey, Set<string>>>();
return {
add(filePath: string, line: number, col: number, calleeId: string): void {
if (shouldCapture?.(filePath, line, col) === false) return;
let byPos = byFile.get(filePath);
if (byPos === undefined) {
byPos = new Map<CalleeIdPosKey, Set<string>>();

View file

@ -24,7 +24,9 @@ import type { CalleeIdSink } from './callee-id-sink.js';
/**
* Optional resolved-callee-id capture context (#2227 follow-up U2). Threaded
* in only under `--pdg` (else `undefined` → zero overhead, byte-identity R4).
* in under `--pdg` OR when always-on callable-flow facts need direct call
* targets (#2437 — the accumulator then carries a position filter); else
* `undefined` → zero overhead, byte-identity (R4).
* `filePath` is NOT on the `site` param, so it rides here alongside the sink.
*/
export interface CalleeIdCaptureCtx {
@ -50,6 +52,12 @@ export function mapReferenceKindToEdgeType(
return 'EXTENDS';
case 'type-reference':
return 'USES';
// A function registered as an object-literal property value emits a
// reference-class USES edge, NOT CALLS — a registration is not an
// invocation (Kythe `ref` vs `ref/call`; Joern `METHOD_REF`). The
// invocation side is synthesized by the property-dispatch pass (#2437).
case 'value-ref':
return 'USES';
// Macro invocations resolve to a `Macro` node (never a function), so
// they emit `USES` — kept out of the `CALLS` keyspace which denotes
// function/method dispatch (#1934 review).

View file

@ -48,6 +48,7 @@ function isCallerAnchorLabel(label: NodeLabel): boolean {
label === 'Function' ||
label === 'Method' ||
label === 'Constructor' ||
label === 'Module' ||
label === 'Class' ||
label === 'Interface' ||
label === 'Struct' ||

View file

@ -178,6 +178,9 @@ export function isLinkableLabel(label: NodeLabel): boolean {
label === 'Function' ||
label === 'Method' ||
label === 'Constructor' ||
// Program-like module declarations are provider-gated callable-value
// targets and need the same def→graph bridge.
label === 'Module' ||
label === 'Class' ||
label === 'Interface' ||
label === 'Struct' ||

View file

@ -88,9 +88,16 @@ export function emitReferencesViaLookup(
// Resolved-callee-id capture (#2227 U2/KTD6/R8): record this CALLS site's
// resolved target BEFORE the `seen` dedup, keyed on `ref.atRange`
// (byte-equal to U1's SiteRecord.at: 1-based line / 0-based col). Only
// CALLS feeds the bridge; ACCESSES/USES/EXTENDS are skipped. `fromFilePath`
// CALLS from real call sites feeds the bridge; ACCESSES/USES/EXTENDS are
// skipped, and so are value-ref CALLS (#2437) — a property-value
// reference is not a call site U1 could have stamped. `fromFilePath`
// is the call-site (caller) file — the same file U1 stamps the site on.
if (calleeIdSink !== undefined && edgeType === 'CALLS' && fromFilePath !== undefined) {
if (
calleeIdSink !== undefined &&
edgeType === 'CALLS' &&
ref.kind === 'call' &&
fromFilePath !== undefined
) {
calleeIdSink.add(fromFilePath, ref.atRange.startLine, ref.atRange.startCol, targetGraphId);
}

File diff suppressed because it is too large Load diff

View file

@ -89,10 +89,13 @@ export function emitFreeCallFallback(
* candidate (monotonicity). */
readonly constraintCompatibility?: ScopeResolver['constraintCompatibility'];
readonly recordResolutionOutcome?: ResolutionOutcomeRecorder;
/** Resolved-callee-id capture sink (#2227 U2). Threaded in only under
* `--pdg`; `undefined` ⇒ zero overhead, byte-identity (R4). Captured at
* the CALLS emit below BEFORE the collapsed `seen` dedup (KTD6) so
* same-target multi-line calls are still recorded per site. */
/** Call sites owned by a later precise pass (for example callable-value-flow). */
readonly skipSites?: ReadonlySet<string>;
/** Resolved-callee-id capture sink (#2227 U2). Threaded in under `--pdg`
* OR for callable-flow's direct-target index (#2437, position-filtered);
* `undefined` ⇒ zero overhead, byte-identity (R4). Captured at the CALLS
* emit below BEFORE the collapsed `seen` dedup (KTD6) so same-target
* multi-line calls are still recorded per site. */
readonly calleeIdSink?: CalleeIdSink;
} = {},
): number {
@ -123,6 +126,7 @@ export function emitFreeCallFallback(
for (const site of parsed.referenceSites) {
if (site.kind !== 'call') continue;
if (site.explicitReceiver !== undefined) continue;
if (options.skipSites?.has(siteKey(parsed.filePath, site)) === true) continue;
// Constructor form (`new User(...)`): resolve the class, then
// emit CALLS to its explicit Constructor def (when present) or

View file

@ -0,0 +1,148 @@
/**
* `emitPropertyDispatchCalls` — value-ref registration edges + field-based
* dispatch for functions registered as object-literal property values
* (#2437).
*
* A provider-hook registration (`{ emitScopeCaptures: emitCppScopeCaptures }`)
* emits a USES reference edge — a registration is not an invocation (Kythe
* `ref` vs `ref/call`; Joern `METHOD_REF`). The invocation happens later
* through the property (`provider.emitScopeCaptures(...)`) — a dispatch the
* receiver-bound pass cannot resolve because object literals are not
* IMPLEMENTS-linked implementors. This pass closes that soundness gap the
* field-based way (Feldthaus et al., ICSE'13; CodeQL `impliedReceiverStep`):
* key registrations by property name and connect every member-call site
* `x.<key>(...)` to every function registered under `<key>`.
*
* This pass is the SINGLE owner of `value-ref` resolution. The shared
* registries only consult pre-finalize local bindings — imported names live
* in finalized bindings (the same reason free calls need
* `emitFreeCallFallback`) — so `resolveReferenceSites` skips `value-ref`
* sites and this pass resolves them post-finalize via
* `findCallableBindingInScope` (Function/Method/Constructor only — the
* callable gate that keeps `{ port: DEFAULT_PORT }` from emitting anything).
*
* Precision posture (mirrors `emitInterfaceDispatchFor`):
* - reason `'property-dispatch'` keeps synthesized CALLS auditable;
* - dispatch confidence 0.7 sits below the 0.85 resolved baseline;
* - a per-key fan-out cap drops promiscuous names (`handler`, `callback`)
* entirely rather than truncating silently — property-name collisions
* across unrelated objects are the documented field-based failure mode.
*
* Ordering: runs AFTER the precise emit passes. `graph.addRelationship` is
* first-write-wins on the position-keyed edge id, so a site that already
* resolved precisely to the same target keeps its precise edge.
*
* Language-neutral: consumes only `value-ref` sites (any language that
* emits the capture participates) and generic member-call sites.
*/
import type { ParsedFile, SymbolDefinition } from 'gitnexus-shared';
import type { KnowledgeGraph } from '../../../graph/types.js';
import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
import { tryEmitEdge, type CalleeIdCaptureCtx } from '../graph-bridge/edges.js';
import type { GraphNodeLookup } from '../graph-bridge/node-lookup.js';
import type { CalleeIdSink } from '../graph-bridge/callee-id-sink.js';
import { findCallableBindingInScope } from '../scope/walkers.js';
/**
* Keys registered by more than this many distinct functions are skipped —
* dispatch through such a name says nothing about which function runs.
* Calibrated on this repo's own provider tables: `emitScopeCaptures` has 16
* legitimate registrations (one per language provider), so the cap sits at
* 2× that — dropping the motivating key was the failure the first value (8)
* had. ponytail: flat cap; revisit with per-receiver narrowing if real
* repos show useful keys being dropped (§12 of the #2437 plan).
*/
export const MAX_PROPERTY_DISPATCH_FANOUT = 32;
/** Below the 0.85 resolved baseline; same discount idea as interface-dispatch. */
export const PROPERTY_DISPATCH_CONFIDENCE = 0.7;
export function emitPropertyDispatchCalls(
graph: KnowledgeGraph,
scopes: ScopeResolutionIndexes,
parsedFiles: readonly ParsedFile[],
nodeLookup: GraphNodeLookup,
calleeIdSink?: CalleeIdSink,
): {
usesEmitted: number;
callsEmitted: number;
skippedKeys: number;
skippedKeyNames: readonly string[];
} {
const seen = new Set<string>();
let usesEmitted = 0;
// Sweep 1 — resolve every value-ref site: emit the USES registration edge
// and index dispatchable registrations by property key.
const registrations = new Map<string, Map<string, SymbolDefinition>>();
for (const parsed of parsedFiles) {
for (const site of parsed.referenceSites) {
if (site.kind !== 'value-ref') continue;
const def = findCallableBindingInScope(site.inScope, site.name, scopes);
if (def === undefined) continue;
const ok = tryEmitEdge(
graph,
scopes,
nodeLookup,
site,
def,
'scope-resolution: value-ref',
seen,
);
if (ok) usesEmitted++;
if (site.propertyKey === undefined) continue;
let byDef = registrations.get(site.propertyKey);
if (byDef === undefined) {
byDef = new Map<string, SymbolDefinition>();
registrations.set(site.propertyKey, byDef);
}
byDef.set(def.nodeId, def);
}
}
// Keep the dropped key NAMES (bounded) — an over-cap key means no CALLS
// are synthesized through it, and a count alone leaves the operator unable
// to tell WHICH hook table lost coverage (#2522 review).
let skippedKeys = 0;
const skippedKeyNames: string[] = [];
for (const [key, defs] of registrations) {
if (defs.size > MAX_PROPERTY_DISPATCH_FANOUT) {
registrations.delete(key);
skippedKeys++;
if (skippedKeyNames.length < 20) skippedKeyNames.push(key);
}
}
// Sweep 2 — synthesize CALLS from member-call sites through registered keys.
let callsEmitted = 0;
if (registrations.size > 0) {
for (const parsed of parsedFiles) {
const calleeCapture: CalleeIdCaptureCtx | undefined =
calleeIdSink !== undefined ? { sink: calleeIdSink, filePath: parsed.filePath } : undefined;
for (const site of parsed.referenceSites) {
if (site.kind !== 'call' || site.callForm !== 'member') continue;
const defs = registrations.get(site.name);
if (defs === undefined) continue;
for (const def of defs.values()) {
const ok = tryEmitEdge(
graph,
scopes,
nodeLookup,
site,
def,
'property-dispatch',
seen,
PROPERTY_DISPATCH_CONFIDENCE,
false,
calleeCapture,
);
if (ok) callsEmitted++;
}
}
}
}
return { usesEmitted, callsEmitted, skippedKeys, skippedKeyNames };
}

View file

@ -161,9 +161,10 @@ export function emitReceiverBoundCalls(
model: SemanticModel,
options: {
readonly recordResolutionOutcome?: ResolutionOutcomeRecorder;
/** Resolved-callee-id capture sink (#2227 U2). Threaded in only under
* `--pdg`; `undefined` ⇒ zero overhead, byte-identity (R4). Per-file
* capture contexts are built from this + `parsed.filePath` in the loop. */
/** Resolved-callee-id capture sink (#2227 U2). Threaded in under `--pdg`
* OR for callable-flow's direct-target index (#2437, position-filtered);
* `undefined` ⇒ zero overhead, byte-identity (R4). Per-file capture
* contexts are built from this + `parsed.filePath` in the loop. */
readonly calleeIdSink?: CalleeIdSink;
} = {},
): number {

View file

@ -152,11 +152,10 @@ export const scopeResolutionPhase: PipelinePhase<ScopeResolutionOutput> = {
// extract per file inside runScopeResolution.
const parsedFileStorePath = ctx.options?.parseCache?.storagePath;
// Drop pre-extracted entries for standalone providers — these
// languages are skipped by the canonical guard below (line 164)
// and never consume preExtractedByPath, so holding onto their
// entries leaks memory until the cleanup loop at 262-264 which
// also never runs for skipped providers.
// Standalone providers are re-extracted from source on the main thread;
// workers intentionally do not serialize ParsedFile artifacts for them.
// Drop any stale/cache-sourced entry defensively so provider-owned regex
// capture logic is always the source of truth for the current content.
for (const [path] of preExtractedByPath) {
const lang = getLanguageFromFilename(path);
if (lang === null) continue;
@ -205,15 +204,15 @@ export const scopeResolutionPhase: PipelinePhase<ScopeResolutionOutput> = {
for (const f of scannedFiles) {
const fileLang = getLanguageFromFilename(f.path);
if (fileLang === null) continue;
// Skip files whose grammar isn't available (optional grammars like
// swift/dart/kotlin on an install where the binding is absent or the
// user set GITNEXUS_SKIP_OPTIONAL_GRAMMARS). The parse phase already
// excluded and warned about these (parse-impl.ts); without this guard the
// file would fall through to the main-thread re-extract in run.ts and
// throw "Unsupported language" (caught, but noisy, and it needlessly
// loads the grammar on the main thread). `isLanguageAvailable` is
// memoized, so this stays O(1) per language. (#2091, #2093)
if (!isLanguageAvailable(fileLang)) continue;
// Tree-sitter providers require an available grammar. Standalone regex
// providers deliberately have none and re-extract on the main thread.
const resolver = SCOPE_RESOLVERS.get(fileLang);
if (
resolver?.languageProvider.parseStrategy !== 'standalone' &&
!isLanguageAvailable(fileLang)
) {
continue;
}
let bucket = filesByLang.get(fileLang);
if (bucket === undefined) {
bucket = [];
@ -304,14 +303,6 @@ export const scopeResolutionPhase: PipelinePhase<ScopeResolutionOutput> = {
let pdgSinkSettled = false;
try {
for (const [lang, provider] of SCOPE_RESOLVERS) {
// Standalone providers (COBOL, JCL) don't emit graph edges yet
// through the scope-resolution path. This is the canonical guard:
// runScopeResolution is never called for standalone providers, which
// keeps cobolPhase as the sole IMPORTS edge producer. Keep this guard
// in sync with any additional standalone providers added to
// SCOPE_RESOLVERS.
if (provider.languageProvider.parseStrategy === 'standalone') continue;
const primaryLangFiles = filesByLang.get(lang) ?? [];
if (primaryLangFiles.length === 0) continue;
const primaryFilePaths = primaryLangFiles.map((f) => f.path);

View file

@ -68,12 +68,21 @@ import { buildPopulatedMethodDispatch } from '../graph-bridge/method-dispatch.js
import { propagateImportedReturnTypes } from '../passes/imported-return-types.js';
import { emitReceiverBoundCalls } from '../passes/receiver-bound-calls.js';
import { emitFreeCallFallback } from '../passes/free-call-fallback.js';
import {
emitPropertyDispatchCalls,
MAX_PROPERTY_DISPATCH_FANOUT,
} from '../passes/property-dispatch.js';
import { emitReferencesViaLookup } from '../graph-bridge/references-to-edges.js';
import {
createCalleeIdAccumulator,
type CalleeIdAccumulator,
} from '../graph-bridge/callee-id-sink.js';
import { emitImportEdges } from '../graph-bridge/imports-to-edges.js';
import {
callableFlowSiteKey,
collectDeferredIndirectSites,
emitCallableValueFlow,
} from '../passes/callable-value-flow.js';
import type { ScopeResolver } from '../contract/scope-resolver.js';
import { findEnclosingClassDef, resolveInheritanceBaseInScope } from '../scope/walkers.js';
import { buildWorkspaceResolutionIndex } from '../workspace-index.js';
@ -410,6 +419,12 @@ interface RunScopeResolutionStats {
readonly resolve: ResolveStats;
readonly referenceEdgesEmitted: number;
readonly referenceSkipped: number;
/**
* Property-dispatch keys dropped for exceeding the fan-out cap. Non-zero
* means member calls through those keys got NO synthesized CALLS — the
* #2437 false-safe gap for exactly those keys (names are in the warn log).
*/
readonly propertyDispatchSkippedKeys: number;
readonly resolutionOutcomes: readonly ResolutionOutcome[];
/**
* Per-function taint summaries harvested in the pdg window (#2084 M4 U1).
@ -434,6 +449,7 @@ export function runScopeResolution(
provider: ScopeResolver,
): RunScopeResolutionStats {
const { graph, files } = input;
const callableFlowOnly = provider.scopeResolutionEdgeMode === 'callable-flow-only';
const onWarn = input.onWarn ?? (() => {});
const resolutionOutcomes: ResolutionOutcome[] = [];
const recordResolutionOutcome: ResolutionOutcomeRecorder = (outcome) => {
@ -521,6 +537,28 @@ export function runScopeResolution(
);
provider.populateWorkspaceOwners?.(parsedFiles, { fileContents: getFileContents() });
// A callable-flow-only provider has no reason to build the whole-graph
// lookup or finalize ordinary references when none of its files emitted a
// callable fact. This keeps the opt-in path proportional to source scanning
// for repositories that use the provider but no first-class callables.
if (
callableFlowOnly &&
!parsedFiles.some((parsed) => (parsed.callableFlowSites?.length ?? 0) > 0)
) {
return {
filesProcessed: parsedFiles.length,
filesSkipped,
importsEmitted: 0,
resolve: { sitesProcessed: 0, referencesEmitted: 0, unresolved: 0 },
referenceEdgesEmitted: 0,
referenceSkipped: 0,
propertyDispatchSkippedKeys: 0,
resolutionOutcomes,
functionSummaries: [],
callSummaries: [],
};
}
// Reconcile scope-resolution's ownership view into the SemanticModel.
// See `reconcile-ownership.ts` for the full rationale (Contract
// Invariant I9). Debug-mode validator runs immediately after to
@ -543,6 +581,7 @@ export function runScopeResolution(
resolve: { sitesProcessed: 0, referencesEmitted: 0, unresolved: 0 },
referenceEdgesEmitted: 0,
referenceSkipped: 0,
propertyDispatchSkippedKeys: 0,
resolutionOutcomes,
functionSummaries: [],
callSummaries: [],
@ -573,18 +612,24 @@ export function runScopeResolution(
},
});
logHeapProbe('sr-post-finalize', `lang=${provider.language}`);
const preEmittedInheritanceSites = preEmitInheritanceEdges(graph, finalized, nodeLookup);
const preEmittedInheritanceSites = callableFlowOnly
? new Set<string>()
: preEmitInheritanceEdges(graph, finalized, nodeLookup);
// Call-based heritage hook (e.g., Ruby include/extend/prepend) — emits
// IMPLEMENTS edges that `preEmitInheritanceEdges` cannot produce because
// the heritage declarations are syntactic method calls, not grammar-level
// heritage clauses. Must run BEFORE `buildMro` so MRO construction sees
// the freshly-emitted IMPLEMENTS edges.
provider.emitHeritageEdges?.(graph, parsedFiles, nodeLookup, finalized);
if (!callableFlowOnly) {
provider.emitHeritageEdges?.(graph, parsedFiles, nodeLookup, finalized);
}
// Implicit IMPORTS-edge hook — for languages whose files have compiler-
// implicit cross-file visibility (no syntactic import statement). The
// finalized-ImportEdge pipeline (`emitImportEdges`) cannot produce these
// because there is no `ImportEdge` to materialize. Idempotent.
provider.emitImplicitImportEdges?.(graph, parsedFiles, nodeLookup, resolutionConfig);
if (!callableFlowOnly) {
provider.emitImplicitImportEdges?.(graph, parsedFiles, nodeLookup, resolutionConfig);
}
// Rebuild the node lookup after heritage-edge emission. Languages like
// Ruby create Property graph nodes inside `emitHeritageEdges`; those
// nodes must be visible to downstream passes (`emitReceiverBoundCalls`
@ -592,15 +637,19 @@ export function runScopeResolution(
// `nodeLookup`). Without this rebuild, Property nodes added by the
// heritage hook are invisible and ACCESSES edges silently fail to emit.
const postHeritageNodeLookup =
provider.emitHeritageEdges !== undefined ? buildGraphNodeLookup(graph) : nodeLookup;
emitDetectedInterfaceImplementations(
graph,
parsedFiles,
postHeritageNodeLookup,
provider,
finalized,
readonlyModel,
);
!callableFlowOnly && provider.emitHeritageEdges !== undefined
? buildGraphNodeLookup(graph)
: nodeLookup;
if (!callableFlowOnly) {
emitDetectedInterfaceImplementations(
graph,
parsedFiles,
postHeritageNodeLookup,
provider,
finalized,
readonlyModel,
);
}
const mroByClassDefId = provider.buildMro(graph, parsedFiles, postHeritageNodeLookup);
const extendsOnlyMroByClassDefId = provider.buildExtendsOnlyMro?.(
graph,
@ -721,29 +770,47 @@ export function runScopeResolution(
// ── Phase 4: emit graph edges (LOAD-BEARING ORDER — see I1) ────────────
input.onProgress?.('linking symbols', files.length, files.length);
const handledSites = new Set<string>(preEmittedInheritanceSites);
// Resolved-callee-id capture accumulator (#2227 U2). Created ONLY under
// `--pdg` — `undefined` otherwise so the three emitters do zero work and emit
// byte-identical output (R4). Populated below at all three CALLS emit paths
// (each before its dedup, KTD6/R8); consumed by the CFG-emit join (U3) at
// `emitFileCfgs` below to produce `BasicBlock.calleeIds`.
const deferredIndirectSites = collectDeferredIndirectSites(emitParsedFiles, indexes);
const callableArgumentSites = new Set<string>();
if (input.pdg !== true && deferredIndirectSites.size > 0) {
for (const parsed of emitParsedFiles) {
for (const site of parsed.callableFlowSites ?? []) {
if (site.kind === 'argument') {
callableArgumentSites.add(callableFlowSiteKey(parsed.filePath, site.callSite));
}
}
}
}
// Resolved-callee-id accumulator (#2227 U2 + callable-value-flow). Created
// for PDG OR when indirect-call facts need direct targets for actual→formal
// propagation. Populated below at every CALLS emit path before dedup; the CFG
// join still consumes it only inside the `input.pdg` block.
const calleeIdAccumulator: CalleeIdAccumulator | undefined =
input.pdg === true ? createCalleeIdAccumulator() : undefined;
const receiverExtras = emitReceiverBoundCalls(
graph,
indexes,
emitParsedFiles,
postHeritageNodeLookup,
handledSites,
provider,
workspaceIndex,
readonlyModel,
{
recordResolutionOutcome,
calleeIdSink: calleeIdAccumulator,
},
);
input.pdg === true || deferredIndirectSites.size > 0
? createCalleeIdAccumulator(
input.pdg === true
? undefined
: (filePath, line, col) => callableArgumentSites.has(`${filePath}:${line}:${col}`),
)
: undefined;
const receiverExtras = callableFlowOnly
? 0
: emitReceiverBoundCalls(
graph,
indexes,
emitParsedFiles,
postHeritageNodeLookup,
handledSites,
provider,
workspaceIndex,
readonlyModel,
{
recordResolutionOutcome,
calleeIdSink: calleeIdAccumulator,
},
);
const unresolvedReceiverExtras =
provider.emitUnresolvedReceiverEdges !== undefined
!callableFlowOnly && provider.emitUnresolvedReceiverEdges !== undefined
? provider.emitUnresolvedReceiverEdges(
graph,
indexes,
@ -753,47 +820,106 @@ export function runScopeResolution(
readonlyModel,
)
: 0;
const freeCallExtras = emitFreeCallFallback(
graph,
indexes,
emitParsedFiles,
postHeritageNodeLookup,
referenceIndex,
handledSites,
readonlyModel,
workspaceIndex,
{
allowGlobalFallback: provider.allowGlobalFreeCallFallback === true,
constructorCallTargetsClass: provider.constructorCallTargetsClass === true,
isFileLocalDef: provider.isFileLocalDef,
isCallableVisibleFromCaller: provider.isCallableVisibleFromCaller,
resolveAdlCandidates: provider.resolveAdlCandidates,
conversionRankFn: provider.conversionRankFn,
conversionOnlyArgTypePrefixes: provider.conversionOnlyArgTypePrefixes,
constraintCompatibility: provider.constraintCompatibility,
recordResolutionOutcome,
calleeIdSink: calleeIdAccumulator,
},
);
const { emitted, skipped } = emitReferencesViaLookup(
graph,
indexes,
referenceIndex,
postHeritageNodeLookup,
handledSites,
calleeIdAccumulator,
);
const importsEmitted = emitImportEdges(
graph,
indexes.imports,
indexes.scopeTree,
provider.importEdgeReason,
);
const freeCallExtras = callableFlowOnly
? 0
: emitFreeCallFallback(
graph,
indexes,
emitParsedFiles,
postHeritageNodeLookup,
referenceIndex,
handledSites,
readonlyModel,
workspaceIndex,
{
allowGlobalFallback: provider.allowGlobalFreeCallFallback === true,
constructorCallTargetsClass: provider.constructorCallTargetsClass === true,
isFileLocalDef: provider.isFileLocalDef,
isCallableVisibleFromCaller: provider.isCallableVisibleFromCaller,
resolveAdlCandidates: provider.resolveAdlCandidates,
conversionRankFn: provider.conversionRankFn,
conversionOnlyArgTypePrefixes: provider.conversionOnlyArgTypePrefixes,
constraintCompatibility: provider.constraintCompatibility,
recordResolutionOutcome,
calleeIdSink: calleeIdAccumulator,
skipSites: deferredIndirectSites,
},
);
const referenceSkipSites = new Set(handledSites);
for (const key of deferredIndirectSites) referenceSkipSites.add(key);
const { emitted, skipped } = callableFlowOnly
? { emitted: 0, skipped: 0 }
: emitReferencesViaLookup(
graph,
indexes,
referenceIndex,
postHeritageNodeLookup,
referenceSkipSites,
calleeIdAccumulator,
);
// value-ref registrations (#2437): USES edges at the registration sites
// plus field-based dispatch — synthesized CALLS from member-call sites to
// functions registered under the same property key. This runs after the
// ordinary precise passes but before callable-value-flow: property-dispatched
// wrapper calls must populate the callee accumulator before actual→formal
// propagation. `graph.addRelationship` remains first-write-wins, so precise
// edges already emitted for a site retain ownership.
const propertyDispatch = callableFlowOnly
? { usesEmitted: 0, callsEmitted: 0, skippedKeys: 0, skippedKeyNames: [] as readonly string[] }
: emitPropertyDispatchCalls(
graph,
indexes,
emitParsedFiles,
postHeritageNodeLookup,
calleeIdAccumulator,
);
if (propertyDispatch.skippedKeys > 0) {
// Never drop dispatch coverage silently: a hook table larger than the
// fan-out cap means member calls through those keys get no synthesized
// CALLS — the #2437 false-safe gap reappears for exactly those keys.
logger.warn(
{
lang: provider.language,
skippedKeys: propertyDispatch.skippedKeys,
skippedKeyNames: propertyDispatch.skippedKeyNames,
fanoutCap: MAX_PROPERTY_DISPATCH_FANOUT,
},
'property-dispatch: keys over the fan-out cap were dropped (no CALLS synthesized for them)',
);
}
const callableValueFlow =
calleeIdAccumulator === undefined
? {
emitted: 0,
resolvedInvokes: 0,
ambiguousInvokes: 0,
unmatchedInvokes: 0,
iterations: 0,
}
: emitCallableValueFlow({
graph,
scopes: indexes,
parsedFiles: emitParsedFiles,
nodeLookup: postHeritageNodeLookup,
calleeIds: calleeIdAccumulator,
language: provider.language,
collapseByCallerTarget: provider.collapseMemberCallsByCallerTarget === true,
isCallableValueTarget: provider.isCallableValueTarget,
hasFileLocalCallableLinkage: provider.hasFileLocalCallableLinkage,
onWarn: (warning) =>
logger.warn(
warning,
'callable-value-flow: candidate set exceeded the cap; no partial CALLS emitted',
),
});
const importsEmitted = callableFlowOnly
? 0
: emitImportEdges(graph, indexes.imports, indexes.scopeTree, provider.importEdgeReason);
// Language-specific supplementary edges (e.g. Vue template-derived
// BINDS_EVENT_HANDLER / EMITS_EVENT / CALLS / ACCESSES edges).
// Runs last so the full graph — including import edges — is visible.
if (provider.emitPostResolutionEdges !== undefined) {
if (!callableFlowOnly && provider.emitPostResolutionEdges !== undefined) {
provider.emitPostResolutionEdges(graph, emitParsedFiles, postHeritageNodeLookup, indexes, {
fileContents: getFileContents(),
resolutionConfig,
@ -941,8 +1067,8 @@ export function runScopeResolution(
(message) => logger.warn(message),
// U3 (#2227): the resolved-callee-id map for this file (captured at the
// three CALLS emit paths in U2), joined by exact call-site position to
// emit `BasicBlock.calleeIds`. `undefined` when pdg is off (the
// accumulator is only created under `input.pdg === true`).
// emit `BasicBlock.calleeIds`. Callable-flow may also have allocated
// the accumulator in a normal run, but this join remains PDG-only.
calleeIdAccumulator?.get(pf.filePath),
);
cfgBlocks += emitted.blocks;
@ -1198,8 +1324,16 @@ export function runScopeResolution(
filesSkipped,
importsEmitted,
resolve: resolveStats,
referenceEdgesEmitted: emitted + receiverExtras + unresolvedReceiverExtras + freeCallExtras,
referenceEdgesEmitted:
emitted +
receiverExtras +
unresolvedReceiverExtras +
freeCallExtras +
callableValueFlow.emitted +
propertyDispatch.usesEmitted +
propertyDispatch.callsEmitted,
referenceSkipped: skipped,
propertyDispatchSkippedKeys: propertyDispatch.skippedKeys,
resolutionOutcomes,
functionSummaries: harvestedSummaries,
callSummaries: harvestedCallSummaries,

File diff suppressed because it is too large Load diff

View file

@ -55,7 +55,7 @@ import type { ParseWorkerResult } from '../core/ingestion/workers/parse-worker.j
// the main thread (the #1983 OOM). Because the two stores share this version,
// any future change to the `ParsedFile` serialization shape MUST bump
// SCHEMA_BUMP so both invalidate in lockstep.
const SCHEMA_BUMP = 13; // Durable ParsedFile chunk directories now replace one complete generation instead of accumulating worker shards across cache-miss analyses. Invalidate once so existing unbounded stores are rebuilt under the bounded contract. (12 = #2391 follow-up: Python module constant extraction semantics changed.)
const SCHEMA_BUMP = 17; // Callable-value-flow operands now retain expression kind/qualified identity and formal signature metadata. (16 = direct callee identity; 15 = always-on callableFlowSites; 14 = #2437 value refs.)
const GITNEXUS_PKG_VERSION = (() => {
try {
// package.json sits at gitnexus/package.json — two levels up from

View file

@ -52,12 +52,17 @@ import { promises as fs, mkdirSync, writeFileSync } from 'node:fs';
import path from 'node:path';
import v8 from 'node:v8';
import vm from 'node:vm';
import type { ParsedFile, SymbolDefinition } from 'gitnexus-shared';
import type { CallableFlowSite, ParsedFile, SymbolDefinition } from 'gitnexus-shared';
import { logger } from '../core/logger.js';
import { mapReplacer, mapReviver } from './parse-cache.js';
const STORE_DIRNAME = 'parsedfile-store';
const DURABLE_DIRNAME = 'parsedfile-cache';
const DURABLE_INDEX_FILENAME = 'index.json';
const MAX_CALLABLE_FLOW_SITES_PER_FILE = 100_000;
const MAX_CALLABLE_FLOW_INDIRECTION = 16;
const MAX_CALLABLE_FLOW_NAME_LENGTH = 4_096;
const MAX_CALLABLE_FLOW_PARAMETERS = 1_024;
/**
* Build a JSON.parse reviver that (a) interns every string against a shared
@ -240,6 +245,8 @@ export const loadParsedFilesForPaths = async (
// (one `int` / one repeated filePath for the whole language), which is where
// most of the saving comes from. Dropped when this function returns.
const pool = new Map<string, string>();
let droppedSites = 0;
let filesWithDroppedSites = 0;
for (let i = 0; i < shards.length; i++) {
// Per-shard def pool: a SymbolDefinition's three serialized copies live within
// a single shard (one ParsedFile), so the dedup is shard-local. A cross-shard
@ -256,8 +263,15 @@ export const loadParsedFilesForPaths = async (
}
if (!Array.isArray(parsed)) continue;
for (const pf of parsed) {
if (pf && typeof pf.filePath === 'string' && wantPaths.has(pf.filePath)) {
if (!pf || typeof pf.filePath !== 'string' || !wantPaths.has(pf.filePath)) continue;
const flow = sanitizeCallableFlowSites(pf.callableFlowSites);
if (flow === undefined) continue; // non-array garbage → distrust the file, re-extract
if (flow.dropped === 0) {
out.set(pf.filePath, pf);
} else {
droppedSites += flow.dropped;
filesWithDroppedSites++;
out.set(pf.filePath, { ...pf, callableFlowSites: flow.sites });
}
}
// Every few shards, reclaim the transient pre-intern parse churn before it
@ -268,9 +282,174 @@ export const loadParsedFilesForPaths = async (
await new Promise<void>((resolve) => setImmediate(resolve));
}
}
if (droppedSites > 0) {
// Facts for the dropped sites are omitted this run (the file itself is
// retained, so no re-extract happens) — surface it so a recurring drop
// on every warm load is observable rather than silent (#2522 review).
logger.warn(
{ droppedSites, files: filesWithDroppedSites },
'parsedfile-store: dropped malformed/over-bound callable-flow sites at load; files retained without those facts',
);
}
return out;
};
/**
* Treat the durable ParsedFile store as an untrusted serialization boundary.
* Sanitation is per-SITE, not per-file: one malformed or over-bound fact drops
* only itself (counted, logged by the caller), so a legitimately pathological
* source file cannot push its whole ParsedFile into a permanent, silent
* warm-cache-miss reparse loop (#2522 review). Only a non-array field —
* i.e. garbage that says the serialization itself is untrustworthy — rejects
* the file, and `undefined` (never emitted / no facts) passes through.
* Returns `undefined` for the reject-file case.
*/
function sanitizeCallableFlowSites(
value: unknown,
): { sites: readonly CallableFlowSite[] | undefined; dropped: number } | undefined {
if (value === undefined) return { sites: undefined, dropped: 0 };
if (!Array.isArray(value)) return undefined;
const bounded =
value.length > MAX_CALLABLE_FLOW_SITES_PER_FILE
? value.slice(0, MAX_CALLABLE_FLOW_SITES_PER_FILE)
: value;
const sites = bounded.filter(isValidCallableFlowSite);
return { sites, dropped: value.length - sites.length };
}
function isValidCallableFlowSite(value: unknown): value is CallableFlowSite {
if (!isRecord(value) || typeof value.kind !== 'string') return false;
switch (value.kind) {
case 'seed':
return (
isValidOperand(value.destination) &&
isBoundedString(value.targetName) &&
isOptionalBoundedString(value.targetQualifiedName) &&
isValidRange(value.targetRange) &&
isValidExpectedSignature(value.expectedSignature)
);
case 'copy':
case 'alias':
case 'address':
return isValidOperand(value.source) && isValidOperand(value.destination);
case 'store':
return isValidOperand(value.source) && isValidOperand(value.pointer);
case 'load':
return isValidOperand(value.pointer) && isValidOperand(value.destination);
case 'formal':
return (
isBoundedString(value.ownerName) &&
isValidRange(value.ownerRange) &&
isSafeIndex(value.parameterIndex) &&
isValidOperand(value.binding) &&
(value.passingMode === 'value' ||
value.passingMode === 'reference' ||
value.passingMode === 'pointer') &&
isValidExpectedSignature(value.expectedSignature)
);
case 'argument':
return (
isValidRange(value.callSite) &&
isSafeIndex(value.parameterIndex) &&
isValidOperand(value.source) &&
isOptionalBoundedString(value.directCalleeName)
);
case 'invoke':
return (
isValidRange(value.callSite) &&
isBoundedString(value.inScope) &&
isValidOperand(value.callee) &&
(value.receiver === undefined || isValidOperand(value.receiver)) &&
(value.invocationKind === 'indirect' ||
value.invocationKind === 'member-pointer' ||
value.invocationKind === 'callable-object') &&
(value.arity === undefined || isSafeIndex(value.arity))
);
default:
return false;
}
}
function isValidOperand(value: unknown): boolean {
if (!isRecord(value)) return false;
return (
isBoundedString(value.name) &&
isBoundedString(value.inScope) &&
isValidRange(value.atRange) &&
Number.isInteger(value.indirection) &&
(value.indirection as number) >= 0 &&
(value.indirection as number) <= MAX_CALLABLE_FLOW_INDIRECTION &&
typeof value.addressOf === 'boolean' &&
(value.expressionKind === undefined ||
value.expressionKind === 'binding' ||
value.expressionKind === 'callable-designator' ||
value.expressionKind === 'bound-member' ||
value.expressionKind === 'anonymous-callable') &&
isOptionalBoundedString(value.qualifiedName)
);
}
function isValidExpectedSignature(value: unknown): boolean {
if (value === undefined) return true;
if (!isRecord(value)) return false;
return (
(value.parameterCount === undefined || isSafeIndex(value.parameterCount)) &&
isValidBoundedStringArray(value.parameterTypes) &&
(value.parameterTypeClasses === undefined ||
(Array.isArray(value.parameterTypeClasses) &&
value.parameterTypeClasses.length <= MAX_CALLABLE_FLOW_PARAMETERS &&
value.parameterTypeClasses.every(isRecord))) &&
(value.isConst === undefined || typeof value.isConst === 'boolean')
);
}
function isValidBoundedStringArray(value: unknown): boolean {
return (
value === undefined ||
(Array.isArray(value) &&
value.length <= MAX_CALLABLE_FLOW_PARAMETERS &&
// '' is a legitimate entry meaning "unknown type" — the same convention
// as ReferenceSite.argumentTypes. C++ emits it for cv-qualifier-only or
// ERROR-recovered parameter types, so requiring non-empty here rejected
// real extractor output (#2522 review).
value.every(
(entry) => typeof entry === 'string' && entry.length <= MAX_CALLABLE_FLOW_NAME_LENGTH,
))
);
}
function isValidRange(value: unknown): boolean {
if (!isRecord(value)) return false;
return (
isNonNegativeInteger(value.startLine) &&
isNonNegativeInteger(value.startCol) &&
isNonNegativeInteger(value.endLine) &&
isNonNegativeInteger(value.endCol)
);
}
function isRecord(value: unknown): value is Record<string, unknown> {
return value !== null && typeof value === 'object' && !Array.isArray(value);
}
function isBoundedString(value: unknown): value is string {
return (
typeof value === 'string' && value.length > 0 && value.length <= MAX_CALLABLE_FLOW_NAME_LENGTH
);
}
function isOptionalBoundedString(value: unknown): boolean {
return value === undefined || isBoundedString(value);
}
function isNonNegativeInteger(value: unknown): boolean {
return Number.isSafeInteger(value) && (value as number) >= 0;
}
function isSafeIndex(value: unknown): boolean {
return isNonNegativeInteger(value) && (value as number) <= MAX_CALLABLE_FLOW_PARAMETERS;
}
// ─── Durable, content-addressed sibling store (warm-cache coverage) ──────────
//
// Layout: `<durableDir>/<chunkHash>/<chunkHash>-w<tid>-<seq>.json` plus a

View file

@ -355,8 +355,13 @@ export interface RepoMeta {
* writeback preserves unchanged-file rows, so a top-up against a pre-v6 index
* would MIX old 1-based rows with new 0-based ones — and the 1-based MCP display
* would render the stale rows one line too high — so force a full re-analyze.
* v7: callable-value-flow CALLS/USES edges added (#2437/#2522) — new edges can
* connect two files whose content did not change, but the incremental write set
* only covers changed files (`computeEffectiveWriteSet`), so a top-up against a
* pre-v7 index would silently omit the new edges for every unchanged file pair;
* force a full re-analyze instead (same contract as v2–v6).
*/
export const INCREMENTAL_SCHEMA_VERSION = 6;
export const INCREMENTAL_SCHEMA_VERSION = 7;
export interface IndexedRepo {
repoPath: string;

View file

@ -1,11 +1,11 @@
{
"csharp-alias-imports/Models/Repo.cs": {
"captureGroups": 13,
"digest": "89499f7c26b495994d869b802deaeecc1de68c7d1e1b41629b57f9baaff4c240"
"captureGroups": 15,
"digest": "044a25113f7b90a72ad0e42cd7f747848222a21c4093775f08c133188dc25687"
},
"csharp-alias-imports/Models/User.cs": {
"captureGroups": 13,
"digest": "d6f73d9a16c5d07f7b7c3c85fe2464426ad6fec89b05312ebccdf3391b6e52e5"
"captureGroups": 15,
"digest": "a3fe19d6bf25831c2e1afc836e0ed82cc903a43e006d7428a2386b0bc7ea0c47"
},
"csharp-alias-imports/Services/Main.cs": {
"captureGroups": 18,
@ -40,40 +40,40 @@
"digest": "064640e172244bee5078073d4eed69df177681cba8c0a8105a391a65864612f9"
},
"csharp-assignment-chain/Program.cs": {
"captureGroups": 30,
"digest": "a3eb329f0ca262da99fa1000b49f7b10e7e5c3fbc157f0d95825e866cdd4c190"
"captureGroups": 32,
"digest": "7698bdabe97661a2a2539a13cf9d886cd7efb2f4924b17a15597ce63cec5126a"
},
"csharp-async-binding/Order.cs": {
"captureGroups": 9,
"digest": "c2d5ef9688ff2fce345fe8d8148ee0d8281032b55083f70ac925e5407b3c0d0c"
},
"csharp-async-binding/OrderService.cs": {
"captureGroups": 12,
"digest": "9ea84a606a4bbee462b5cbc8becff91aa07847187b40f1af6d7c429c2876df4a"
"captureGroups": 14,
"digest": "712fb5f3a791581ab56c37df58d8245a17a674a6d2f7bd25b2bc8d1632f751c3"
},
"csharp-async-binding/Program.cs": {
"captureGroups": 32,
"digest": "a90d5d0f8b41f8752b5b629aa4d1bf58b8dcda6181c41fbe9453eb758c4bbfa3"
"captureGroups": 37,
"digest": "73735c3910ed4db423302d9575cec86156d420e9961c592c34e0436301cac7ce"
},
"csharp-async-binding/User.cs": {
"captureGroups": 9,
"digest": "d771395595ea17b22e3f7b448bb10463086aa89fe49f54fe24ee514e2392a880"
},
"csharp-async-binding/UserService.cs": {
"captureGroups": 12,
"digest": "08c1546fd0a591d45041f4840725aa16cb832d6cd848563db45a83868352afa7"
"captureGroups": 14,
"digest": "a4e6b093fa23a86313bc468f8b6a89a96d5c90b1d16f166417745bd36dfbd10f"
},
"csharp-call-result-binding/App.cs": {
"captureGroups": 24,
"digest": "da0e6286460e3eb719452574c2c627f3d242394b165525e026926c08b26cb88c"
"captureGroups": 27,
"digest": "3d8c7dc0b7f5bd60c74d6a595bd4b49bdb13b62522fb7f0c1434495e10e1171b"
},
"csharp-calls/Services/UserService.cs": {
"captureGroups": 11,
"digest": "d527d47a183733c34d56227c383267f25acc951413639f1e6cc6cb7eabd2440a"
},
"csharp-calls/Utils/OneArg.cs": {
"captureGroups": 7,
"digest": "8d9ac2bdedab0c1202d99233bbf03364d17e67fbfe9ecdd0ecb924198e1cd393"
"captureGroups": 8,
"digest": "65bf57fabe6074033a64e5e5adca6aa433e5087576eac96b13172934f55f0c82"
},
"csharp-calls/Utils/ZeroArg.cs": {
"captureGroups": 7,
@ -124,12 +124,12 @@
"digest": "37c48de332491deb8c31c5f1f8184e301900f87258eac47321ebf12065991cad"
},
"csharp-deep-field-chain/Service.cs": {
"captureGroups": 13,
"digest": "a10037598e4ee49a9e2ce2c4bf397bee1ca44faec8e7abda852efe665f7dfbfa"
"captureGroups": 14,
"digest": "f988dfa882e07384c055a431d970550281aa1fa5d14de0daf7ecdde3a1b9e6f2"
},
"csharp-dictionary-keys-values/App.cs": {
"captureGroups": 19,
"digest": "b445e081f6556e4f84e45f0ad3b4d6f8a4b64ff24a6e53a7ac2109d80fc3225a"
"captureGroups": 21,
"digest": "ee8eb9c569b71d7050f292bc7fbf89b68cdbb60b4bcd557f2523c86831891543"
},
"csharp-dictionary-keys-values/Repo.cs": {
"captureGroups": 7,
@ -140,20 +140,20 @@
"digest": "49ec4930562a8e12040010b12535a1620aa5ab86e2aab3cad2623e8aec106518"
},
"csharp-explicit-ctor-init/Models/Base.cs": {
"captureGroups": 8,
"digest": "a96546d27fbb8b2ddf8571f0253bb145565223e52f45d8ff0cf84c6ff5cb1ee1"
"captureGroups": 9,
"digest": "6adf4ac489b35e51161a4d67a0ab2fe3c7191e08a1c0c6bf051cb653a1cb4b31"
},
"csharp-explicit-ctor-init/Models/Child.cs": {
"captureGroups": 16,
"digest": "fe50206ccb6df1b631bb5c22892e1e3f668181e9ba9f3b55981e9df97ff10ff2"
"captureGroups": 17,
"digest": "f056fbf3be0445392d0f67e35dff5e7690c6244d650322d20a2d35ca9f04c9aa"
},
"csharp-field-types/Models.cs": {
"captureGroups": 17,
"digest": "2e698fecbe4099c8aa5c63ba5ec7adad0dbb64912163685507a96fb681545d03"
},
"csharp-field-types/Service.cs": {
"captureGroups": 10,
"digest": "4cf80db658b9491052b4f629fce5f8397d1e8d537753ff3e729f2ab9c446f367"
"captureGroups": 11,
"digest": "6f6a983bab2d73fb0f83d879311a4822356b2aa6ee1029fc12d04b7a1c3acd58"
},
"csharp-foreach/Models/Repo.cs": {
"captureGroups": 8,
@ -164,8 +164,8 @@
"digest": "064640e172244bee5078073d4eed69df177681cba8c0a8105a391a65864612f9"
},
"csharp-foreach/Program.cs": {
"captureGroups": 18,
"digest": "be6192b93b692af37eb8564714faa1486c9463c8fac4c41d4c17c47e3306474d"
"captureGroups": 20,
"digest": "810f0f65e956343cf6817918dc5b28ce7bc1f1f755f89e008a6e8b05864ff469"
},
"csharp-frozen-binding-collision/App/Program.cs": {
"captureGroups": 18,
@ -188,8 +188,8 @@
"digest": "2f193c298218e84b404be421c513c2f0ee2f21c05e266b0c523b293b17529aeb"
},
"csharp-generic-type-refs/Program.cs": {
"captureGroups": 23,
"digest": "97ce65fba71e65ad7dfebb630d9f38b0ad598d45cd461b5e1275ec80168df27b"
"captureGroups": 25,
"digest": "11958e1426be1f2f06d82c93fd1f813607b7665072e95356a8584aa21c0c2119"
},
"csharp-grandparent-resolution/Models/A.cs": {
"captureGroups": 10,
@ -212,16 +212,16 @@
"digest": "ec4326a3db72676fb5e17b9fad3de503989a7f34b1b7159fe86093728fbf1737"
},
"csharp-hello/Hello.cs": {
"captureGroups": 20,
"digest": "140b0d111483cecb5a875d42a081fb0e19e378cd98802890210c23773350e6dd"
"captureGroups": 22,
"digest": "0765643d95d2c40a64d3f9b71fd488fc6cbfdb0cc72cc91eec94e46da1267722"
},
"csharp-interface-default-method/App.cs": {
"captureGroups": 12,
"digest": "1df65065f20b8d316238bd3f7c43f95cc0e2527c8f7cca88e95a872016a04ed2"
},
"csharp-interface-default-method/User.cs": {
"captureGroups": 12,
"digest": "647035b25d003e3ddf4763e36311b638391b0147e762865618a83285fab50f36"
"captureGroups": 14,
"digest": "ef10faa8e723eab836331e5c2d29c4b167cfd2c1e15c391989ea394eb1cab5c4"
},
"csharp-interface-default-method/Validator.cs": {
"captureGroups": 8,
@ -232,12 +232,12 @@
"digest": "ca1d074837f867a78d9b5e1f197ab967128e89b20d62acdba34bfaa08b01ef6d"
},
"csharp-interface-dispatch/IRepository.cs": {
"captureGroups": 7,
"digest": "14743965ad586b13254a9618686d601b0dd36f3be97531c9fb0605139e83809d"
"captureGroups": 9,
"digest": "88c0a5b4146a78b07cfe361bfb20ced73c05be707997fbfa4bcefb8dd60f8b50"
},
"csharp-interface-dispatch/SqlRepository.cs": {
"captureGroups": 12,
"digest": "65bd6fd311969af60313ea33345188894fa236ca26020b3e681fc8c3144898fb"
"captureGroups": 14,
"digest": "4ec288844786e2b45a1cb7421474b3c1cc5606e42c34f2364fce642c60f1aae2"
},
"csharp-interface-heritage/src/IAuditableService.cs": {
"captureGroups": 8,
@ -264,8 +264,8 @@
"digest": "c5a6484f74d530463f3028036f401323d84ec2a825fd52450326d95c8088ccb2"
},
"csharp-interface-receiver-static/src/ILogger.cs": {
"captureGroups": 7,
"digest": "8bfccfdf1696c38539df95c001029d4d40f09d2f84ed273e5e4ddf860dad7d10"
"captureGroups": 8,
"digest": "57032d77c03d756e71307aab5fc993754b4972598b6362689bf905786c4a32ac"
},
"csharp-interface-receiver-static/src/Runner.cs": {
"captureGroups": 9,
@ -280,8 +280,8 @@
"digest": "2c97632b3beb8f5500e4d02b1150d2845fa6d6bc26ee1838e4e1118ec6a8e847"
},
"csharp-is-pattern/services/App.cs": {
"captureGroups": 11,
"digest": "e125e671dfcf30a41b26c29bfd57fb9fe9b8e0d1a0dabedbdd3ac1451693672e"
"captureGroups": 12,
"digest": "4ad57edbcf5462d7a254fe63f59e5e513ab4032c18d437cc1154cde5c857897d"
},
"csharp-large-cache-miss-resolution/Models/User.cs": {
"captureGroups": 8,
@ -296,12 +296,12 @@
"digest": "ac7dcf56199eb29174cfbfb775e956247f72abc476cc8e28751d94084d1b5c7a"
},
"csharp-local-shadow/App/Main.cs": {
"captureGroups": 13,
"digest": "e1ba476d7984abedd654e461d9a971e8e566116e6414871f1962feef75b91d50"
"captureGroups": 14,
"digest": "12c7b2f27fc6e68ece2e588ab060d60d5ab0b2ac679bbccc236c170dacd5d130"
},
"csharp-local-shadow/Utils/Logger.cs": {
"captureGroups": 9,
"digest": "324feff6c194e3f07493e63efe46165832033c855978358f0a0fd3cd6d6d732a"
"captureGroups": 10,
"digest": "0b65e06e2c32dabd8e3c5ede48d088323f145ad2f55760b79f4f4af59aa5f033"
},
"csharp-member-calls/Models/User.cs": {
"captureGroups": 8,
@ -312,12 +312,12 @@
"digest": "662512b485c31efe8b5a37df38e31bccf130e7625c7ef7594ea44e27b0b79d2c"
},
"csharp-method-chain-binding/App.cs": {
"captureGroups": 53,
"digest": "09e0bed66a03cddb567b863b8d916b5b2f4210547caf2f8682c266d36102125e"
"captureGroups": 60,
"digest": "2b4761e1dfe2d48ac25cfda7ccce95175607926b47db43726f38ad2a16acc6f1"
},
"csharp-method-enrichment/Animal.cs": {
"captureGroups": 17,
"digest": "b21133dbc6ecaaf9ba13de8d1b70c11c6a89d02f89696a137c8d79ae9e5e287a"
"captureGroups": 18,
"digest": "ad7890731caf219c98664630dfe886f00ed33a4a3f6a4a30f945c889835eae1e"
},
"csharp-method-enrichment/App.cs": {
"captureGroups": 14,
@ -332,8 +332,8 @@
"digest": "be74672b6af6192d0b7872dcad9a6ba4af08156aad5cc01c6fe3eb2ee02b4127"
},
"csharp-mixed-decl-chain/Program.cs": {
"captureGroups": 25,
"digest": "4f25b7d6327c0f9d1f4ca9e8d1af07ced3b6fedbfbb8e4996c73cbfb963ef24e"
"captureGroups": 28,
"digest": "466f29bc93a0293011c8c43f4655d87a26a57166261aaf85df3d08c2d38cf10b"
},
"csharp-namespace-as-root-no-trailing-newline/App/Program.cs": {
"captureGroups": 13,
@ -384,8 +384,8 @@
"digest": "d019925a0e3ab366b3fb8fbd1abac38f564b9f4877ea0dd0b802c181c07c8587"
},
"csharp-null-check-narrowing/Services/App.cs": {
"captureGroups": 30,
"digest": "5c41de929accffe5f747a6891ca323081e529e877171d4545bd7e47d6400a8cd"
"captureGroups": 36,
"digest": "5d840c524610b6a84a2f09981c15327e9f7ea5c2c4b11b09e959d880ac6b9bc9"
},
"csharp-null-conditional/App.cs": {
"captureGroups": 17,
@ -400,28 +400,28 @@
"digest": "064640e172244bee5078073d4eed69df177681cba8c0a8105a391a65864612f9"
},
"csharp-optional-params/Services/App.cs": {
"captureGroups": 15,
"digest": "d146e09259dd67a7bc33d1117d4df7a65e760645d024539fcee787db67ed0fdf"
"captureGroups": 17,
"digest": "5fb11a605cac8fe2abf017e1724bf8881eacbc147f9a29052e432bf3ca26b284"
},
"csharp-overload-dispatch/App.cs": {
"captureGroups": 12,
"digest": "c0ac495ae6dc34db5cdb9f508be050fab06920e8f110b14a8c9f095613a269c5"
},
"csharp-overload-dispatch/IRepository.cs": {
"captureGroups": 9,
"digest": "5211301516540daf9c54fb3c8ca3b43bddcce4f81a14034056f2bc897ae925a4"
"captureGroups": 13,
"digest": "9f7fc8cfd4c0e3b67581f96945760ffe96183f55ce93809d1fc9e0abb9d42b67"
},
"csharp-overload-dispatch/SqlRepository.cs": {
"captureGroups": 17,
"digest": "8b67299c96e0331a44bfd56edf13bc71f957e39bfa95888108d3180f454050a3"
"captureGroups": 22,
"digest": "d4d7156fc95fa515126011bffed810ecdcf94b2f4ea6e537b1d8dfa5d8d7abe9"
},
"csharp-overload-interface/App/Caller.cs": {
"captureGroups": 14,
"digest": "8b37ca49176cdc59f0ad0033b75b1936c80eb1c74db4475876923c098a52b496"
"captureGroups": 15,
"digest": "f1ea2e564c46dab5fb93fb19e81ab3411f458b172820b5dc0bdc57f49ffa88e0"
},
"csharp-overload-interface/App/Logger.cs": {
"captureGroups": 11,
"digest": "6e363259f8427756f497269ca0a3f4838ce16ad7518be6fe76289ee766cbc8e0"
"captureGroups": 14,
"digest": "71eb7a633b950c765dcfb50a28047b4156a121c653ee189a9288c1998c3c71a7"
},
"csharp-overload-interface/Greeting/EnGreeter.cs": {
"captureGroups": 10,
@ -436,8 +436,8 @@
"digest": "4a42025fdcb4db0bf1b77e1e4fa024af5c2a4dc36fe6806ce397b980047790d0"
},
"csharp-overload-param-types/Models/UserService.cs": {
"captureGroups": 28,
"digest": "b934c80be640f7d5707d5f03b352c059ed225272d494f93337805ad9b6ece895"
"captureGroups": 30,
"digest": "178e1a7dd5b07ba3361e1eb28ce73ca6a6075fa8ecb8f2ca40e8e87a410de552"
},
"csharp-parent-resolution/src/Models/BaseModel.cs": {
"captureGroups": 8,
@ -456,8 +456,8 @@
"digest": "b724c5e2a0742901a09a8c85e0fd5504905471bd8096e6541b05c2d2a1d65527"
},
"csharp-pattern-matching/Services/AnimalService.cs": {
"captureGroups": 12,
"digest": "209cee0681fa4f035a081f44e38f9cd39eda3d56936471396f78f5265d745e4b"
"captureGroups": 13,
"digest": "2623dcd94520473dc3dd830cfc21675349b6981185b779db3f5a47200b2f44a3"
},
"csharp-primary-ctor-heritage/src/BaseEntity.cs": {
"captureGroups": 6,
@ -492,8 +492,8 @@
"digest": "01bb40f83fca48db34d1c1c970317cf0fece5af7eb7e02836ef9e64304154b3a"
},
"csharp-proj/Interfaces/IRepository.cs": {
"captureGroups": 13,
"digest": "12e7cea9ae7c97e97789f2ef83e689530002c084b06a904a17994ce43f8b6af4"
"captureGroups": 14,
"digest": "902f871892b4b772b60d7e852d1a4a85b3ad4b12358d73a0a946e17c8943942c"
},
"csharp-proj/Models/BaseEntity.cs": {
"captureGroups": 9,
@ -504,8 +504,8 @@
"digest": "6c1b2e9adee1a7cf34ecd647be966c28aab7a8ac57c0fcb14373f2e562f3f456"
},
"csharp-proj/Services/UserService.cs": {
"captureGroups": 20,
"digest": "26ff7130ea2290fefd0160458c2b1fb7cd8572643f04545a634696a1f1773e5f"
"captureGroups": 21,
"digest": "8004a914844e0a5ff0630b019889cca8db09612678683ade419d4c40a9f60e15"
},
"csharp-qualified-base/src/Domain.cs": {
"captureGroups": 16,
@ -564,36 +564,36 @@
"digest": "80b9061d53bd0ceba23699c828100ebce5af9a7c006fff7e4a99d2f8e006b92e"
},
"csharp-recursive-pattern/Program.cs": {
"captureGroups": 14,
"digest": "c2fe0ca3b5bf9b26774ee896b7f2fbae1cdaaf3c27d1469897cde3547c297bae"
"captureGroups": 15,
"digest": "ef252310d50287971d87e96e72b64575b9ee691602b62c74ea5b23811d2080a6"
},
"csharp-return-type/Models/Repo.cs": {
"captureGroups": 8,
"digest": "4953327fdde44fada4df0c55bd1dab9c2e96b28f42a1fbfb224c374f75ce0fbb"
},
"csharp-return-type/Models/User.cs": {
"captureGroups": 20,
"digest": "7a41c40abf08b3fbc68b34e581636b51dcfb32c9e49dd195e5dfe29680d3cf2e"
"captureGroups": 23,
"digest": "6681e6830c71c25908e50273e4babb41611bc229395d1dbab70ac9f219b68ca8"
},
"csharp-return-type/Services/App.cs": {
"captureGroups": 16,
"digest": "6c22fca1da52147fdbfbe48f1563e95f291ef0af8856b5c6e82ed04a110d064e"
},
"csharp-same-arity-cross-file/App.cs": {
"captureGroups": 47,
"digest": "3b66f87310d7dae98a79a6983996abca827e8b78d37e5a7234e26e9520634c5b"
"captureGroups": 49,
"digest": "899ca0e32e844b1d347eb0e7558da99644892dc894fcf7e3f632071bbd9cfcd7"
},
"csharp-same-arity-cross-file/DbLookup.cs": {
"captureGroups": 12,
"digest": "870869bc7dec677c4f037cd1ef6da71ff6a180b40b76a313a4e6f8e6a2030ffa"
"captureGroups": 14,
"digest": "a33d9e4c3e1f7bc4f54e3cadbbf5cb044669ba5cc5912b19836cf58d8b9d5910"
},
"csharp-same-arity-cross-file/Formatter.cs": {
"captureGroups": 11,
"digest": "83c7bbf6774596aa06aa75480a136f442356bf44bf2a048e9f9a561be613d781"
"captureGroups": 13,
"digest": "e2305d9beeb8c7d4452bacfa7cc078dba02a17f34a993d2986cd55f03bdcfc41"
},
"csharp-same-arity-cross-file/ILookup.cs": {
"captureGroups": 7,
"digest": "ee3c7f29ba0638d2917d364fa39cd80220655b4ee0825eda1d2640e7d9e8c500"
"captureGroups": 9,
"digest": "f700d06ecdcb6d140c3c9514f85d30d04cfd19d131e570213ed166eaba691ec7"
},
"csharp-self-this-resolution/src/Models/Repo.cs": {
"captureGroups": 8,
@ -628,8 +628,8 @@
"digest": "2574c61dc312d531301a6d08c828ac743b1198e32946980c0f44bc115ec9bcdc"
},
"csharp-struct-overloads/src/Calc.cs": {
"captureGroups": 16,
"digest": "03b9f3273cdd46364956b6cf15d29a849c6be70567d7d26ffd23023f60a24591"
"captureGroups": 19,
"digest": "b45f1440bd589f070e2adfa99872d93f357a8c3831daecb1b9da31476115914e"
},
"csharp-super-resolution/src/Models/BaseModel.cs": {
"captureGroups": 8,
@ -652,16 +652,16 @@
"digest": "c51e7c7e0423087ddd3a439b99c95101a3bb8611bb6677d7bd1630a464a8cacb"
},
"csharp-switch-pattern/Program.cs": {
"captureGroups": 13,
"digest": "bee49e211ae6d5a6391a56a68044762500d600e2a8a5b9d78c1350e342dcab19"
"captureGroups": 14,
"digest": "a090eff3ff572948716c931da3d62eeb137e36f08397b60748d4177293b10dc1"
},
"csharp-using-static/App/Calculator.cs": {
"captureGroups": 10,
"digest": "9f1d4ed78f5653ce5da1604afb322ee10ad212fbc4f791612a77a52530a8fa07"
"captureGroups": 12,
"digest": "b6f9dd906e1309338f21633d71e663cdfd95a8707d38b9a8bf74813415ee5d13"
},
"csharp-using-static/Helpers/MathUtils.cs": {
"captureGroups": 7,
"digest": "f6db029267c1562d1b5defe08089f66f9bd183509b0a7a9df4f638d9170b36f1"
"captureGroups": 8,
"digest": "32c174cbaade4e2d6e0aa7e95a2b5addd138441deb43ced286c9cf5cd30750aa"
},
"csharp-var-foreach/Models/Repo.cs": {
"captureGroups": 8,
@ -672,27 +672,27 @@
"digest": "c51e7c7e0423087ddd3a439b99c95101a3bb8611bb6677d7bd1630a464a8cacb"
},
"csharp-var-foreach/Program.cs": {
"captureGroups": 28,
"digest": "f70572e67859f3e853b84a123d70836324509abb1e60e4e4179b354e3bb52988"
"captureGroups": 32,
"digest": "a9c7bf1f2425cece1ecb698abc0c6fa5e7a3bb24e3cc7d2dba50ef7d0651badc"
},
"csharp-variadic-resolution/Services/App.cs": {
"captureGroups": 10,
"digest": "892a1570c699c625fa1be6d4c53216589622a1b7df5e297a625edffd2fc31e14"
},
"csharp-variadic-resolution/Utils/Logger.cs": {
"captureGroups": 8,
"digest": "835f4084b55107b725ac675350df638be92436685d39eac1518b234fd9f618e3"
"captureGroups": 10,
"digest": "46905fc6f0d59035df05fe60d29c57217d321c5a8d3c3223466061b56c7f785d"
},
"csharp-write-access/Models.cs": {
"captureGroups": 9,
"digest": "0d65b1d41a42a5a79eecf3a2ff2c7b3585c52dadd479770c2572a146afdc6606"
},
"csharp-write-access/Service.cs": {
"captureGroups": 10,
"digest": "1782eca84697c9ebd7fe802d1e0e0952d29cef8d8834e826b359388561a41c06"
"captureGroups": 11,
"digest": "aa6d8a61ac39db413df10a6bc8b9bad3305327dcdce09e01cf01eff31f945537"
},
"synthetic:dao-20": {
"captureGroups": 223,
"digest": "05b2727c2e20430921fe536ad4d421764c9791e5f370fdce97c3fb0d07ada703"
"captureGroups": 263,
"digest": "f082745a8fcb7a0acb364f3e3739b70f96fd2cf821ada7c4e743090348a06dcc"
}
}

View file

@ -20,8 +20,8 @@
"digest": "0a988c48838a34eb7a6f29b324191c39c19087e728914fffa06bbb9221841d23"
},
"go-assignment-chain/cmd/main.go": {
"captureGroups": 50,
"digest": "570d1eb9d287345a00a5a05bde00724b609523b41953cb1c277f5c056980d7b5"
"captureGroups": 54,
"digest": "2bd7f8e2df13345ed77b4b438fa5af0b666aa238fe32be0d9ef39865b04a27e2"
},
"go-assignment-chain/models/repo.go": {
"captureGroups": 8,
@ -32,8 +32,8 @@
"digest": "fcee44ed373eda2ff00778ff951fc2fa1503c5187b9e7d43dea3ee8b63656907"
},
"go-call-result-binding/cmd/main.go": {
"captureGroups": 16,
"digest": "ecf35e5a0e148d55f98a0de636f743db026fb75ae6a609b3fa90bd285c322145"
"captureGroups": 17,
"digest": "fadab2167d8037193746a4bd13b50c63a7053c18a4810b75da16e931cfcdbb8a"
},
"go-call-result-binding/models/user.go": {
"captureGroups": 10,
@ -44,8 +44,8 @@
"digest": "deaea55087c2652aa2d39fefa30048d5393cec40f7af1ab13921331b035030d6"
},
"go-calls/internal/onearg/log.go": {
"captureGroups": 6,
"digest": "762e8143c4ab116272c38d46586106ec4d5e01153c7abe831ca434269da58d94"
"captureGroups": 7,
"digest": "d6a41da15dcba594b747b76b726738bcfbb86b9fb9d444571f6749c361bd4d59"
},
"go-calls/internal/zeroarg/log.go": {
"captureGroups": 5,
@ -84,8 +84,8 @@
"digest": "a1f9453bd71926d60e3f148f43b9af813cbd1cccc11b323896a55bdb443f8931"
},
"go-constructor-type-inference/cmd/main.go": {
"captureGroups": 18,
"digest": "849606bb6f94982923de95a9cacc2eeae567b1eb473937cc635dcc7789cc1c01"
"captureGroups": 19,
"digest": "3cfb964e7c0fa6d5b702a1312e4cd7dc5c6153d45e29f8d422afebcf131930f4"
},
"go-constructor-type-inference/models/repo.go": {
"captureGroups": 8,
@ -96,16 +96,16 @@
"digest": "bf814d4db5d2b0026caab6aa5ade9c173abd96fdf2c1e127b57a854d0203f9a5"
},
"go-deep-field-chain/cmd/main.go": {
"captureGroups": 13,
"digest": "ca9cc7ae0f75928b1ea338f42e58cf02502e0c93ce4ea8867258c90543f79d16"
"captureGroups": 14,
"digest": "2a2d67f1fd9813558ce13d969fd9f6a50368f303c7e925b4cf8df9b52e66d54f"
},
"go-deep-field-chain/models/models.go": {
"captureGroups": 33,
"digest": "3941943919b0066138d8a759cceadd95784c8a89978e7a345ea056ef1ca78c9c"
},
"go-field-types/cmd/main.go": {
"captureGroups": 9,
"digest": "3bc98cf5640568cdff3202f2594fb39d563ee595560cb28d1adb31f264670ceb"
"captureGroups": 10,
"digest": "358f4003af7b957c35bb049af4941f8a23c36981b7e777bf1b943bcf734c7d1b"
},
"go-field-types/models/models.go": {
"captureGroups": 22,
@ -124,16 +124,16 @@
"digest": "964cf1bd181cc7196726c3261a683884ac9c6fc3aee6170b47bd54427a6cb947"
},
"go-inc-dec-write-access/main.go": {
"captureGroups": 21,
"digest": "0414398239624e44b1589f6a68f2c19636bf4b3ce74990cee1bd7cbeeb0591e6"
"captureGroups": 23,
"digest": "fd35d917bc7a039ef4bcb0a6501c31ffbea0fe39bf3604938191597415aa33f7"
},
"go-local-shadow/cmd/main.go": {
"captureGroups": 12,
"digest": "1cda9982d9b4e4878208894523f0a9c7a806c6f08a5582d4771205716a1b2733"
"captureGroups": 14,
"digest": "ce7220df98df08741f772150b5572bec0364d576741bc8c76647615fab859f92"
},
"go-local-shadow/internal/utils/utils.go": {
"captureGroups": 6,
"digest": "51e941fa4c7765efc6e472d15a9d7ea31f59b67a6b606be868f4af47e5f54cb1"
"captureGroups": 7,
"digest": "9e4c8b37e90b1abeb8a0f8ca615793fc4a77bea5178ebc4875a3a595ec59f03d"
},
"go-make-builtin/main.go": {
"captureGroups": 18,
@ -144,8 +144,8 @@
"digest": "2af382fcd2e59cf01d9439bdb8709cba150bb239fae71b6f0d09c1fa7248fc5b"
},
"go-map-range/main.go": {
"captureGroups": 10,
"digest": "7f3580a0e7858e6eb3176e0b5e1bec4867a2d5d07f2b216a04a3de79ff96170b"
"captureGroups": 11,
"digest": "5e8b9ed5580cd51f988049c4a43e212bf9d5b655f5de85c80a956baf5892a6a0"
},
"go-map-range/models/repo.go": {
"captureGroups": 9,
@ -164,40 +164,40 @@
"digest": "fcee44ed373eda2ff00778ff951fc2fa1503c5187b9e7d43dea3ee8b63656907"
},
"go-method-chain-binding/cmd/main.go": {
"captureGroups": 21,
"digest": "884589d31ad9c4463d48323b7aa5f80c15a4d3eb1097210abd88c3d013d2fbef"
"captureGroups": 22,
"digest": "ede80f2009f68d880d957bc90af74898dc0b3f7d86a48bf26e4d9d3587dec2d4"
},
"go-method-chain-binding/models/user.go": {
"captureGroups": 24,
"digest": "d72468ce0567ce35393b570e7e3e86f0c004e37a9614dc5ee698aff6d3a4348a"
},
"go-method-enrichment/animal.go": {
"captureGroups": 16,
"digest": "7ef52b78d7c920a34961f3275ceb4a34d0172067459ab594287b7e4b7ec695a6"
"captureGroups": 17,
"digest": "090d1bba93a5271a8e8e75d2e9d9790e58e4c5a6679b7fb9290f318591eb619b"
},
"go-method-enrichment/app.go": {
"captureGroups": 15,
"digest": "ac0bdc2e6daf7d4e28fd255fe2edd143e8fab7ff316f4e32e2ca04a3b33f71c2"
},
"go-mixed-chain/cmd/main.go": {
"captureGroups": 20,
"digest": "3662b803da4f4fc1ac0552a45d3d262bd0db57f97b056614caacea8ad856250e"
"captureGroups": 22,
"digest": "486c3edfe09e6bc6ba3652f55e167c1e73cdac3b5419ef513811a1e6bfcdb9d5"
},
"go-mixed-chain/models/models.go": {
"captureGroups": 41,
"digest": "afb1eaf2869f534033c5cd844ca111a93b63df1c098cec9c9518c4480a47ddb0"
},
"go-multi-assign/app.go": {
"captureGroups": 16,
"digest": "66958a3e86caa54aedd795227dac274cfb3f4b39ab98964e8a7bcbdfc8a08ca6"
"captureGroups": 18,
"digest": "6ca3fdfb8708da1503367902ea458927bd2eb33582eeccbbb0a1dab585cc30c1"
},
"go-multi-assign/models.go": {
"captureGroups": 19,
"digest": "e071ebdd59b5bbebb42785ebf850a9c0ee7c2e3a8cc13fb0b17233480efa26ed"
},
"go-multi-return-inference/cmd/main.go": {
"captureGroups": 36,
"digest": "179fca92694ce3e1f2a9b38cde0c283e4bab00f240316a6116931115e079057a"
"captureGroups": 38,
"digest": "9f7320349e8fd04f40c23a9a04ab67826140034e98d9851538db1b8a6f9f2b7b"
},
"go-multi-return-inference/models/repo.go": {
"captureGroups": 10,
@ -236,24 +236,24 @@
"digest": "e1b57b0c5865d84b80518fe52e3807f813ae5589b4b558d0037e6ea136843f85"
},
"go-pkg/cmd/main.go": {
"captureGroups": 14,
"digest": "a7781d23802e876b9ca2951b7ebb317c5df5adf390ae9a57e8e2906af34f3255"
"captureGroups": 15,
"digest": "b763b372c88c64d31b4a746d24a422b490dfad0f33a55ac4e0f697979995b34f"
},
"go-pkg/internal/auth/service.go": {
"captureGroups": 17,
"digest": "dc481fe7319f4e69d6f53d04a10029bb36b5f964b5ecd5a367d6e081c0158fec"
"captureGroups": 21,
"digest": "8a5a2fef24f2803396b2314ffc951487b1adbc2a28a733bd2dcb65e2961fc479"
},
"go-pkg/internal/models/admin.go": {
"captureGroups": 14,
"digest": "6c307b22f7ddd3c2c34a525a61e4ed36602e22b5845fc6533be15515ef7d78fb"
"captureGroups": 17,
"digest": "087b8188a2e3d5d58f70d5a812b1a31a135dbd435de0ee7799d63199a38eb6cf"
},
"go-pkg/internal/models/repository.go": {
"captureGroups": 5,
"digest": "73a5c9bfcb59011f36c47e1b806edba89bad1ed32f81d46acbefdb8abdba67c7"
},
"go-pkg/internal/models/user.go": {
"captureGroups": 13,
"digest": "a9eeed64d83ab471847db27f8ce5c4ba65b6ac2664f372da67ee4fc974016224"
"captureGroups": 14,
"digest": "04a3005d9f97325b79c0ac524a424c84c004aed5af24318389a413462dee2c4f"
},
"go-pointer-constructor-inference/cmd/main.go": {
"captureGroups": 15,
@ -300,16 +300,16 @@
"digest": "fcee44ed373eda2ff00778ff951fc2fa1503c5187b9e7d43dea3ee8b63656907"
},
"go-return-type-inference/cmd/main.go": {
"captureGroups": 39,
"digest": "71b935bc70b194c2a8136f053319aaba86a4ba80a5d171ef39bc6e8d5c144865"
"captureGroups": 41,
"digest": "6133d61b247732eddad431c28c1374e46d85937ab42e88d7b1ab134aca1e593b"
},
"go-return-type-inference/models/repo.go": {
"captureGroups": 16,
"digest": "0dde6336b070f5e9919189de576fdad96d69eb993f9b29a7a48875bc044f229b"
"captureGroups": 17,
"digest": "1117b573967ac1c687fe336f476f3c4e94680925f9a3ff2a84a47a87bdf86208"
},
"go-return-type-inference/models/user.go": {
"captureGroups": 16,
"digest": "c133a3db65e1952277b7c986f0870db86d4bb762ff0043d2160ed772317e03ca"
"captureGroups": 17,
"digest": "a83045c31b12d9dc45b07315b081bef26ec380479d39706ee8c5d79e4efe5d88"
},
"go-same-package-factory/main.go": {
"captureGroups": 14,
@ -340,8 +340,8 @@
"digest": "827dc0208b47776976313a4560fbd250ab7fa521ae46581213a79fc15fd52ce8"
},
"go-struct-literals/app.go": {
"captureGroups": 10,
"digest": "97ec29ec0e0dc1f23a804ff6808023a32eafdeb00f194d3bea9b50ed54c7f258"
"captureGroups": 11,
"digest": "bcdf97254bb1518219d85ff75795f72ef7a41c4e43ddeb7a09648349d46ec347"
},
"go-struct-literals/user.go": {
"captureGroups": 10,
@ -352,8 +352,8 @@
"digest": "b5ef588c11391fa93237260baae4c4bb67ee4dfc381651444a9925c3f52adec3"
},
"go-structural-interface-cross-package/cmd/main.go": {
"captureGroups": 27,
"digest": "b44adcfaa2b965479abe17b406c6c74ddd51bb54e7d129e8a99a26a2a3f2fc16"
"captureGroups": 33,
"digest": "340f5f3f501be8e4f0fb7fdc63949ec2c6ee535eac1480d0090f9f5aab0dfd11"
},
"go-structural-interface-cross-package/contracts/read_closer.go": {
"captureGroups": 6,
@ -368,16 +368,16 @@
"digest": "4d6298ea05addadfa17f6dd8a2eb0fa2ea63b73de69355015594504ae4e6c1b1"
},
"go-structural-interface-cross-package/store/repository.go": {
"captureGroups": 33,
"digest": "9a497524610994e9a47555cd5874074b278d3e12850e3ee62a1afadb25fcabe8"
"captureGroups": 37,
"digest": "1b69c40092cd49588936e68bfb831d1ccfd5764bcb5bf42279bd66d9cbc57450"
},
"go-structural-interface-dispatch/repository.go": {
"captureGroups": 129,
"digest": "f31bc522991e59527834bb7dcd45b8738490c622c3d25181a8148cc85d27844b"
"captureGroups": 142,
"digest": "986d18b1db74887f5f6f91876aeb332111d85be658dd5d737d104a86204b5575"
},
"go-type-assertion/main.go": {
"captureGroups": 11,
"digest": "b8bd327d3965531802a93bc01e3c24969a027540397a1635119ef0b7da46c348"
"captureGroups": 12,
"digest": "046533d699a7a037ea6b891b0e5dbcdeda19cc1554c2c0aca14005151a72c7f5"
},
"go-type-assertion/models.go": {
"captureGroups": 18,
@ -388,15 +388,15 @@
"digest": "443f9736b9c67df30fe97d8353ddc2584de4c88ba698e62878683d5d77362d89"
},
"go-variadic-resolution/internal/logger/logger.go": {
"captureGroups": 4,
"digest": "83b987f527f95e360966793148d07e569fa02fdc5e4febc09daa51cfce16b114"
"captureGroups": 6,
"digest": "7c8738c8300962e5ade309013e229008499c6e34971eacf1d785bf17cf17af7f"
},
"go-write-access/main.go": {
"captureGroups": 20,
"digest": "92fdad46b6933fd78dcbb19677a720f037306a4f03fedcca9156b54c1c9bf994"
"captureGroups": 21,
"digest": "20c312ccec297b19022c681a486656b0f2d5d3562c791588df170043f4e45b09"
},
"synthetic:dao-20": {
"captureGroups": 481,
"digest": "611e1f8147dff51e8857e493e06089aadf17a042cee6a522520433025b2bc0da"
"captureGroups": 521,
"digest": "cd4e6169abfe6016f000a884acf0604f7fea0ea00d9e1f8f52c7d5ad9b97a01a"
}
}

View file

@ -1,23 +1,23 @@
{
"php-abstract-dispatch/src/Contracts/Repository.php": {
"captureGroups": 10,
"digest": "3459af9360d51aaaba72963fc49bb79edc188e97f9831421d6504c7919b61dc5"
"captureGroups": 12,
"digest": "e7bc6e98e30f2cca0cb4917e951a1b7713cd36c8565cd50e8d49819d6775c7bb"
},
"php-abstract-dispatch/src/Repositories/SqlRepository.php": {
"captureGroups": 14,
"digest": "584da0b8d38ba3b2e45513e24ee17e0366bcfe48e353c274daed367250a0ccd9"
"captureGroups": 16,
"digest": "d5a159f9082316966a3cd1e61db93ee513ea3a6eba287080a2fcfbf3eb9f8fe9"
},
"php-abstract-dispatch/src/app.php": {
"captureGroups": 10,
"digest": "af85999f2ddf2bf718cc419553efd09425919835460bd6739277cc570cb2d3c4"
},
"php-alias-imports/app/Models/Repo.php": {
"captureGroups": 14,
"digest": "4fd03e4a19a7a7c2da925f850563b6dccd21b898223c00278bc2753e8841d844"
"captureGroups": 16,
"digest": "e975f7f079a5ee0d00cdbc9834bb33fc5d86392a613e1dd8c9bc37cf78bbaaa0"
},
"php-alias-imports/app/Models/User.php": {
"captureGroups": 14,
"digest": "5a20d985b3f1c04e998feb586e8b9b382ab069829db69b156ab2636867096a93"
"captureGroups": 16,
"digest": "aefe482576d3a0e03ab62d69fc20ff08ddac8175ee6ff02b08e2422dccc39baf"
},
"php-alias-imports/app/Services/Main.php": {
"captureGroups": 15,
@ -48,28 +48,28 @@
"digest": "0628d6520b02da49ace196f582704c3ffe88aadb3ea20cff642699aa122d5f7f"
},
"php-app/app/Contracts/Loggable.php": {
"captureGroups": 7,
"digest": "46cf7e385db7656514b796908134a50f4a536f3adda64196e17d8c54f5cd5e3a"
"captureGroups": 8,
"digest": "a42a03745e80b9e7938ffe1ca9bd5839054903c3b9fc7afe0f516eaf7a4bac21"
},
"php-app/app/Contracts/Repository.php": {
"captureGroups": 10,
"digest": "40bbdb3fc0ec9b5a9bb8fbdf4a053099ac926951ecd746f2436569add3f0e3d5"
"captureGroups": 12,
"digest": "d495916b169cbba1486dd02e9a7d6b2b958a56cc4c31c7da3437d4f3e2808260"
},
"php-app/app/Enums/UserRole.php": {
"captureGroups": 10,
"digest": "203eb6943ff7527cc52ad93df040a4d15dbcf16beb9e78914401069c54f20bb8"
},
"php-app/app/Models/BaseModel.php": {
"captureGroups": 18,
"digest": "7d5a013f34f909846e3a91cef4b3daa8638034e5c6939d90ccd7dae34c8f9272"
"captureGroups": 20,
"digest": "0858aad2d5de0e09cf8cad1cdcd8cfcd6774aacb3673203e9ebedf6b5e220fb4"
},
"php-app/app/Models/User.php": {
"captureGroups": 27,
"digest": "718b9ba0238e2139ff34027d68cb135c4698a2fc401a4f85200864b1e27a9a78"
"captureGroups": 31,
"digest": "ce6edc7b23136e6b428901a380461cfb24f20eeebdb88800017bef8780e709a5"
},
"php-app/app/Services/UserService.php": {
"captureGroups": 38,
"digest": "a911c5af5042738b1ca9aeb71dad06f4918d8c41e84b6a27ffaa0eadfe56ca22"
"captureGroups": 44,
"digest": "98a1f7f498c0b8298f6ca23ace99f9a767b6b0ca402c9ed881b114ce64098eab"
},
"php-app/app/Traits/HasTimestamps.php": {
"captureGroups": 11,
@ -88,20 +88,20 @@
"digest": "5e6b0b6f2bbe05e8ee310e5a91163b087b846c38a07dc22ab3ba165b939d7691"
},
"php-assignment-chain/app/Services/AppService.php": {
"captureGroups": 15,
"digest": "0e9a9ef52a987dbd12c9478a75d5019c2a0059ab4b862924597d2ecd32f03aa7"
"captureGroups": 19,
"digest": "2fca0da2f6c317b6fae76267e9c0eb4cee0f3467e1f0ae856e9806ee2d932316"
},
"php-call-result-binding/App.php": {
"captureGroups": 23,
"digest": "48c7af06cd4c12f07f308d8804c1c3652c2af87d155831fc5ee915fd984737d2"
"captureGroups": 26,
"digest": "d3d3c6b3cb4c9a1dff62921d054ded2168f6400b22826e6484dba12afbb623c9"
},
"php-calls/app/Services/UserService.php": {
"captureGroups": 7,
"digest": "84e2e65cbb4026ef9e67ac2710d1304c8dffe335f2b1b1cb70123157a04acf50"
},
"php-calls/app/Utils/OneArg/log.php": {
"captureGroups": 5,
"digest": "bd3d0b4f7da34edfa86b3e5e9cd6664439baf7cf07318098ac562f48c40f2a72"
"captureGroups": 6,
"digest": "850fa336ae84361e86e208f14118429624758372716bbaa47deabe994151f774"
},
"php-calls/app/Utils/ZeroArg/log.php": {
"captureGroups": 4,
@ -120,20 +120,20 @@
"digest": "e7bbcd72127b97475851b17cf7fbc5a86786091b2117313dfb24deab8a0d6d09"
},
"php-constructor-calls/Models/User.php": {
"captureGroups": 14,
"digest": "a7d4f11e20fb687f537d1ca0735f8e85c9ea8a2b29867bb57326f02da3bb63d1"
"captureGroups": 16,
"digest": "420d4e556aa30e133013cd162039a002f94804dec4b84c7866e3b07985f7ce65"
},
"php-constructor-calls/app.php": {
"captureGroups": 8,
"digest": "0f3e6cf60248ae02ca0a744d3fa4f67b2d8421858c29243c4fcaf60763c7ac3d"
"captureGroups": 9,
"digest": "8850b2a9c3b4c13840321da4a7c7fb3921b19712e64aa6a57bec66368c1cfc30"
},
"php-constructor-promotion-fields/Models.php": {
"captureGroups": 22,
"digest": "ac49ef4de6665d63a0bef9449e1c685f2fd20fbb01c5d26c924ee0d469f614c5"
"captureGroups": 24,
"digest": "425bcdf14f3825cb78a864287ff9d9a1228a0dbb526aa5d05905945c24c101c7"
},
"php-constructor-promotion-fields/Service.php": {
"captureGroups": 8,
"digest": "d7093850a8c703a5aefbcd584e5bc8e7ac5668e14ff79e27b1db401f72e83073"
"captureGroups": 9,
"digest": "c5efa7143b83d8677edfdb3cdedf27b63b5587babd12b861cb90dd05bf739327"
},
"php-constructor-type-inference/app/Models/Repo.php": {
"captureGroups": 7,
@ -160,16 +160,16 @@
"digest": "f20d780f2bc95ed438047c8e9bb694f5a020454161d8a20f8d5e02be68e4a602"
},
"php-deep-field-chain/Service.php": {
"captureGroups": 9,
"digest": "0026ff9cbad4125b5c27ce356b7c954d369604c5129460d9a0f51cd1c46afbca"
"captureGroups": 10,
"digest": "f5bb845f2a06252e7d030cc1bb8adca213dece5162ea6240b52b2545a9c6bf4d"
},
"php-default-params/app.php": {
"captureGroups": 9,
"digest": "c5b100802ae7d78869f099ab886133d16f865a653e05bdc477e6b4e523accf91"
"captureGroups": 11,
"digest": "3181bc162c5f271be83d1e38a3d1ba6a6ab0421d4763e9c80dbef439305bd72e"
},
"php-dynamic-calls/app/Services/Dynamic.php": {
"captureGroups": 52,
"digest": "1a15600d585851ee1f05927c27e6ce6ccaf6a3f56aecf36e27f84a20a65bb9b2"
"captureGroups": 64,
"digest": "43e09d594d62b1586fc9f9450c9713132e1e2cd3682cbe14b5fd4105f421ef42"
},
"php-dynamic-calls/app/Services/OtherTargets.php": {
"captureGroups": 6,
@ -184,56 +184,56 @@
"digest": "23e6855d9ac081c156d7f273a6ca4076c878d900d97822b436d2f190e2272064"
},
"php-field-types/Service.php": {
"captureGroups": 8,
"digest": "d7093850a8c703a5aefbcd584e5bc8e7ac5668e14ff79e27b1db401f72e83073"
"captureGroups": 9,
"digest": "c5efa7143b83d8677edfdb3cdedf27b63b5587babd12b861cb90dd05bf739327"
},
"php-foreach-call-expr/Repo.php": {
"captureGroups": 17,
"digest": "f6082a43eb279f1de8f8ffea93c1a8ee05b7c5b39fdf2eb0a308c8170b7ab7d1"
"captureGroups": 19,
"digest": "819b69e2a722176f735fdcdcba9ff214a697649c94c927729ae7ce0b935e0157"
},
"php-foreach-call-expr/User.php": {
"captureGroups": 17,
"digest": "799e12a9ebddde34f030e0612724f8a31893d33d45421607e619070036e30937"
"captureGroups": 19,
"digest": "28d841e1f3ff97ccabbb3291294d91e1cabf6121809df95c1cc0c2c2f221614a"
},
"php-foreach-call-expr/main.php": {
"captureGroups": 11,
"digest": "ce332b01f1bbdfaf0e03a3d77f7f708e9f7e5fd41eaa795be8fd2584ab86711a"
},
"php-foreach-generic/App.php": {
"captureGroups": 16,
"digest": "6f5355bc0d315224e3ce2696f3fac3b067f90771f291ce73e2cfc9c5c9ece59c"
"captureGroups": 18,
"digest": "150b1244810ad7cc2f6125e3ac2ad945f40222797905c8db3a142da813a5a200"
},
"php-foreach-generic/Repo.php": {
"captureGroups": 13,
"digest": "0ba7bc5bc2387e1f07898a288a4d856349b777c9be12ace6ebd2ccf39124c446"
"captureGroups": 15,
"digest": "f166a1db89a938d16ebb4503e424df6f0e3fe5d706eb06408eeee8449dc55f45"
},
"php-foreach-generic/User.php": {
"captureGroups": 13,
"digest": "eb34a7e89368d13116de3cb7b0430bc2d1b9c982e3b3f7c9511dae8a73759af6"
"captureGroups": 15,
"digest": "3c646f44302a13d7714c30853063acbdea673eb78f5039be56062c7a453707eb"
},
"php-foreach-loop/App.php": {
"captureGroups": 10,
"digest": "0a648289cb93a52808d1d6ee8b519fa28c413227607deb0c3d3a9a0cdaa52e02"
"captureGroups": 11,
"digest": "64409c5307bc7d29f9107867358dc1c46a8de901c31fcc67d017353903331de4"
},
"php-foreach-loop/Repo.php": {
"captureGroups": 13,
"digest": "0ba7bc5bc2387e1f07898a288a4d856349b777c9be12ace6ebd2ccf39124c446"
"captureGroups": 15,
"digest": "f166a1db89a938d16ebb4503e424df6f0e3fe5d706eb06408eeee8449dc55f45"
},
"php-foreach-loop/User.php": {
"captureGroups": 13,
"digest": "eb34a7e89368d13116de3cb7b0430bc2d1b9c982e3b3f7c9511dae8a73759af6"
"captureGroups": 15,
"digest": "3c646f44302a13d7714c30853063acbdea673eb78f5039be56062c7a453707eb"
},
"php-foreach-member-access/App.php": {
"captureGroups": 14,
"digest": "44e6f810de40d566b5c909d91dcc50de5282fde2f597bf38302edbcb8d825ca3"
},
"php-foreach-member-access/Repo.php": {
"captureGroups": 13,
"digest": "0ba7bc5bc2387e1f07898a288a4d856349b777c9be12ace6ebd2ccf39124c446"
"captureGroups": 15,
"digest": "f166a1db89a938d16ebb4503e424df6f0e3fe5d706eb06408eeee8449dc55f45"
},
"php-foreach-member-access/User.php": {
"captureGroups": 13,
"digest": "eb34a7e89368d13116de3cb7b0430bc2d1b9c982e3b3f7c9511dae8a73759af6"
"captureGroups": 15,
"digest": "3c646f44302a13d7714c30853063acbdea673eb78f5039be56062c7a453707eb"
},
"php-fqn-cross-namespace/app/Models/User.php": {
"captureGroups": 7,
@ -244,8 +244,8 @@
"digest": "9989dd5b86b705df941fc63dfd7fcbe4520dd1378a16a50b5f3dfd0ebed7bd90"
},
"php-fqn-cross-namespace/app/Services/Service.php": {
"captureGroups": 15,
"digest": "e711548813d38a0db268d89a7edff1d31bbaa9ff0399898e09f8e93bdbc1c785"
"captureGroups": 17,
"digest": "9600f70b3d9eab344b61ddb8d9e278718377c72934dc5187036cc976b0660012"
},
"php-grandparent-resolution/app/Models/A.php": {
"captureGroups": 9,
@ -280,12 +280,12 @@
"digest": "aef9ddf30722a053664637b7fefdfcdd541baf09b24e65a3a0d80fe920189828"
},
"php-local-shadow/app/Services/Main.php": {
"captureGroups": 9,
"digest": "52549cd82f7e2e69fb8bb81bca4e5e3487e216f4305023b77579de92374b9ee3"
"captureGroups": 10,
"digest": "bb72a6453d4f54935a0cdab1eb35b65182340ddc9a335dc94cafdadab2a5f586"
},
"php-local-shadow/app/Utils/Logger.php": {
"captureGroups": 5,
"digest": "7434c0a1b2282daf8d60334a85523d603bffd256eaaf5c241509e72c0a6d1e5d"
"captureGroups": 6,
"digest": "2ffa672fe694cc3fd9a66b1b9456da9fbb92e9cf5a1c43a43116c8cba698f635"
},
"php-member-calls/app/Models/User.php": {
"captureGroups": 7,
@ -296,12 +296,12 @@
"digest": "dfe3ef69a8d6dbd95cf5a6bdc754914973b7208ad0c9575bf2595a0f84ac22cf"
},
"php-method-chain-binding/App.php": {
"captureGroups": 48,
"digest": "8cab0917ecbbd65f43046dcc3c03f7af2d8e1a51780d4dbf3f7bc29ccabd2fa2"
"captureGroups": 54,
"digest": "dcb82de39fad8dcba84622d28c304842950dd7bb1c16e0e4ac7cd2117481d226"
},
"php-method-enrichment/src/Models/Animal.php": {
"captureGroups": 12,
"digest": "f32de8a30558f2678e73c68d7db9c6eab827fd8d5f7655b906a76066c8421bef"
"captureGroups": 13,
"digest": "8b21a9220a2e735b637576e2d4dd351490859dd07a52beba097c3ee340f04817"
},
"php-method-enrichment/src/Models/Dog.php": {
"captureGroups": 9,
@ -312,16 +312,16 @@
"digest": "ebfa35aa3eb065977f922ae72f353d5bf31dd49956c3e9aaef349718e555b7a7"
},
"php-mro-arity-mismatch/app/Models/ChildModel.php": {
"captureGroups": 16,
"digest": "eabbdb94047d99d03b7e5cafcde4b0f89666695ca4c3b97ea7e68148999c1c00"
"captureGroups": 19,
"digest": "6e194ffcc5049d0654c60a182b27245171f1685e295f6aa2e15368f39139dd1a"
},
"php-mro-arity-mismatch/app/Models/Orphan.php": {
"captureGroups": 9,
"digest": "29d2e5732698a9951194a6be3d788e337a142c5ea174c5356d375cf9c9e9ff58"
"captureGroups": 11,
"digest": "418e7f982d402169e624986bacaa084e62774482e0ca53af7e0d689f1473f1d7"
},
"php-mro-arity-mismatch/app/Models/ParentModel.php": {
"captureGroups": 12,
"digest": "e16afaad3145133dce0f2de89d0f58eec33492b6a67741013eaf27e35ce6aa07"
"captureGroups": 14,
"digest": "d5960861a820159bd8ed99a4032b28a4ecc132f6f2a1c412a84ac3425a34177c"
},
"php-mro-arity-mismatch/app/Services/Caller.php": {
"captureGroups": 22,
@ -336,12 +336,12 @@
"digest": "7f7a357fb5d758f9d4686b072c4d5df82026701ed28bdce042c5c7950a893c41"
},
"php-namespace-fallback-isolation/src/App/Utils/Format.php": {
"captureGroups": 5,
"digest": "dc33b55ffcef13d75972ca695921ce75c88ea21f37960ad138ee04267f3ccba4"
"captureGroups": 6,
"digest": "bf58ab493d904cc45d38539dc270dfcbc75614d8fcdcc400f7c1e1953ef3ee3b"
},
"php-namespace-fallback-isolation/src/Vendor/Utils/Format.php": {
"captureGroups": 7,
"digest": "eff9d04520000bfc5fd7ae87721965e3000d1b478786f95b4f51e001ad877faf"
"captureGroups": 11,
"digest": "c372e232faf99874eff46877b71f70b533b496832b762833f2b266ca9939978f"
},
"php-nullable-receiver/app/Models/Repo.php": {
"captureGroups": 7,
@ -352,16 +352,16 @@
"digest": "cec22d127d637784baa722787a0a5e5402970ca2dc23a20f07e644762492d8bd"
},
"php-nullable-receiver/app/Services/AppService.php": {
"captureGroups": 13,
"digest": "52e6db35e8fa4174ce698fec802b9b034dcda8f49fd7ab98cd9e884a6bd9777f"
"captureGroups": 15,
"digest": "63e67fa5d12829c99658ea545620466b1bc071ec4e082d02f8e4f4a080f1d7cd"
},
"php-overload-dispatch/src/Services/Formatter.php": {
"captureGroups": 7,
"digest": "5a4979e230bd666c0dc35e8e9611adf67d770858d1897ea113610f0698ccea7d"
"captureGroups": 9,
"digest": "181754c8029dbcffebaee51dd89f7c383b321339fcf57721287ecc4a82c55487"
},
"php-overload-dispatch/src/Services/FormatterExtended.php": {
"captureGroups": 9,
"digest": "17dadd44959529ba4533df45561025d00be7ae2f0a2503cbde4a69e9b72614c1"
"captureGroups": 13,
"digest": "8c877481daca4ab21128fbf74e4942a71cb45809e5f2ac8f7fabd3285066068d"
},
"php-overload-dispatch/src/app.php": {
"captureGroups": 10,
@ -396,20 +396,20 @@
"digest": "8c5cd8fb19d4f3964c68533834db49d85eb5f93e04ba067a617f5a6c655a0046"
},
"php-phpdoc-attribute-return-type/Services.php": {
"captureGroups": 37,
"digest": "d857f9bc514191f222a2e0880c67ab958f2689fc1c6ac0a55af9c8e26fd9c0c0"
"captureGroups": 41,
"digest": "fa7b5e2b69d746e024ea60652923e37470f79a5510380f792e48b253e0f4b60c"
},
"php-phpdoc-return-type/Models.php": {
"captureGroups": 11,
"digest": "8c5cd8fb19d4f3964c68533834db49d85eb5f93e04ba067a617f5a6c655a0046"
},
"php-phpdoc-return-type/Services.php": {
"captureGroups": 37,
"digest": "17ba0088f086c768d5ab6a07de513669a41787ca5f26c33114e6647ef683c920"
"captureGroups": 41,
"digest": "4a62988be2b037a32584b9093ca667490419a401babe2fc52d84f25565c17731"
},
"php-property-promotion/UserService.php": {
"captureGroups": 19,
"digest": "872830776a740cf97c1eafac5e3413aae99e0e951b1b5b46d1aa0df821eac8bc"
"captureGroups": 21,
"digest": "6373f13321137c00923fdef141121e13013a8ef5ed31928d957dc146e95b87d2"
},
"php-receiver-resolution/app/Models/Repo.php": {
"captureGroups": 7,
@ -420,16 +420,16 @@
"digest": "cec22d127d637784baa722787a0a5e5402970ca2dc23a20f07e644762492d8bd"
},
"php-receiver-resolution/app/Services/AppService.php": {
"captureGroups": 13,
"digest": "5e92226af09405c7fc4a02d3aeafcedc8462f3e525b8f67b8509f38091488505"
"captureGroups": 15,
"digest": "29ae6c6b128776d8664e7172b4b5ece311b07fe715fd1fa9d1b9aae43a49b823"
},
"php-response-shapes/api/items.php": {
"captureGroups": 11,
"digest": "84e64331d581f16103b3b0c8c95819e41c36ca2e16b6186ccfa73a13729577a1"
"captureGroups": 15,
"digest": "fc39dbb91468db99b833e18aa08f2df2fd8760588e184170f76bfaf32a3f0c15"
},
"php-response-shapes/api/submit.php": {
"captureGroups": 17,
"digest": "f5d1af2ad313f4639fbad558eed65d3fa0c218eefc55843ea9e07156a1554883"
"captureGroups": 18,
"digest": "f10a59dd26b7002bcc406bb04aea7ad068b2cb691b3716cc62e681afa000c2c4"
},
"php-response-shapes/includes/auth.php": {
"captureGroups": 4,
@ -440,12 +440,12 @@
"digest": "f04e8cb824fba439cea5fdab461ade906b49953b7e8cd2777e724f8fa66e9b31"
},
"php-return-type/app/Models/User.php": {
"captureGroups": 14,
"digest": "5b41d9519e2d696436cb5e6a1337619def5882c2bc442af74ac15a8570b6e168"
"captureGroups": 16,
"digest": "954642025fa2ccd7e8182c95dbc932dcada58884b627603ca6477d3d12242b8a"
},
"php-return-type/app/Services/UserService.php": {
"captureGroups": 17,
"digest": "ac4851815d7a450ab92f7f68ccadabf219d2534b99ef1b65ced0a344188f2a69"
"captureGroups": 18,
"digest": "de72421c29529492bc553e6c049b926222afd7d455c6e43260781af234bd559e"
},
"php-self-this-resolution/app/Models/Repo.php": {
"captureGroups": 7,
@ -468,16 +468,16 @@
"digest": "56d657425e80ae4e58ffe139f90afab5a8527e191bc4b76662f5ce4cbbca4cbb"
},
"php-this-receiver-disambiguation/AdminService.php": {
"captureGroups": 15,
"digest": "db9ba7004ba7b98984307f2b3e1a71154b5a7d60403286d7f308c03ed351b18a"
"captureGroups": 16,
"digest": "c95d2acf2aaf17c610d182e56df28ddafc93adedab0f685f6903065c12a77ff6"
},
"php-this-receiver-disambiguation/Models.php": {
"captureGroups": 11,
"digest": "8c5cd8fb19d4f3964c68533834db49d85eb5f93e04ba067a617f5a6c655a0046"
},
"php-this-receiver-disambiguation/UserService.php": {
"captureGroups": 15,
"digest": "147924d3638edc63eef1a009943fae66a87ac85d02ae533428646c644e81c2bb"
"captureGroups": 16,
"digest": "2c6c2005dfc643487d7801d3b73e157327c89732da7922826728c953b07de2d3"
},
"php-transitive-traits/app/Models/Consumer.php": {
"captureGroups": 18,
@ -496,28 +496,28 @@
"digest": "9d55a120a79ead7605f2e05c80b36615c228acd5cf83db5417a5efe4c9ed9e7f"
},
"php-typed-properties/app/Models/UserRepo.php": {
"captureGroups": 11,
"digest": "19089a297be5f83a4a7610a2111a1fb1b84806cda5fa7d6dbb16c9dfc137b6d6"
"captureGroups": 12,
"digest": "42acb30febe31b52028f03d856a9b57a1c518bbe2aeafd1f5c0c3caa9785d64d"
},
"php-typed-properties/app/Services/UserService.php": {
"captureGroups": 12,
"digest": "9f0540a4f7f322ee96dad4437c4e41e087f29a6993d2659fc48bd26e3277e963"
"captureGroups": 13,
"digest": "01719eeb52ccd723add0f6132bfce85b93c2b6e53c08c1e1c358d09302355d60"
},
"php-typed-property-dedup/app/Models/UserRepo.php": {
"captureGroups": 7,
"digest": "4b0043ea779b33cabc12f79cb4aef32759284137f20838346e9f5ef82076be7b"
},
"php-typed-property-dedup/app/Services/Mixed.php": {
"captureGroups": 14,
"digest": "97d09b46f6e66bef700f4686e5988c98372853203885b9a2d99dafce6fdc8343"
"captureGroups": 15,
"digest": "25262e91a1f84e6b34c794c9bc4f61771f41325bc1c3d028901fbb8334284ca2"
},
"php-unresolved-receiver-arity/app/Models/Handler.php": {
"captureGroups": 21,
"digest": "0e45194dc4a1bf420d4325756a780b0574129517e70d83a82c5dd68b921a0f67"
"captureGroups": 28,
"digest": "0ba1e08801515cb80dec911710767f6ac030285080eb94a0c0ceec84ce106e90"
},
"php-unresolved-receiver-arity/app/Services/Caller.php": {
"captureGroups": 28,
"digest": "a3d316585e67b65ac85a63991735c1324911c709a6d9714f9c3eb4ca6a95f807"
"captureGroups": 34,
"digest": "474a7bac44ce6026b3cb4489d8d3d8e359959c32f43a10b32e006a1869ff0995"
},
"php-use-function-const/app/Config/constants.php": {
"captureGroups": 2,
@ -532,35 +532,35 @@
"digest": "b2206861c8550b6d2c7610ff167c42f4236c7249c87584b418e05f687233ad22"
},
"php-use-function-const/app/Utils/helpers.php": {
"captureGroups": 6,
"digest": "98ebf5b3427b39b619e78eaf11a46376eba5ce7fef202508ce169afd8c8e979e"
"captureGroups": 8,
"digest": "46dba5667a66e8b91d83738015ef3d708509dbbf7ecd0f8ef4ff5161719b8baf"
},
"php-variadic-arity-minimum/app/Services/Caller.php": {
"captureGroups": 29,
"digest": "4ac7be9ff21c52b76630cdcfde0eb41af43e126c2de5db327e9f82c46c3aabbc"
},
"php-variadic-arity-minimum/app/Utils/Logger.php": {
"captureGroups": 18,
"digest": "dbcc7f0bf265a950952c4d192bef14924b29b31326942f4f1600aa8bbe22e9a9"
"captureGroups": 30,
"digest": "a75622e071f4028125b47ebbde4624b8d0cfb2c6ed7210a019b2b1e301137cfb"
},
"php-variadic-resolution/app/Services/AppService.php": {
"captureGroups": 9,
"digest": "6591007d2f4ba85b25a59c11c3ae200452fbad23cacb1ebc04291a074b534dd8"
},
"php-variadic-resolution/app/Utils/Logger.php": {
"captureGroups": 7,
"digest": "8e075546630b3057577932dcf8a876b450950a5accd7df3f8a87079cdc401139"
"captureGroups": 9,
"digest": "a1ca177024aaad2d7d60b52efd48b8be0750fad2a645142897a803b21bc2803d"
},
"php-write-access/models.php": {
"captureGroups": 13,
"digest": "b7f00a2f5e9c64d9823d11279d831bf9320c697e69184f368d288348fc9ac39e"
},
"php-write-access/service.php": {
"captureGroups": 8,
"digest": "5ef6b1687663e4cd9acb880a570fc32388961e735ad38d8ea9906da173971362"
"captureGroups": 9,
"digest": "bfab872dccd371da947e06ba4114e2b0d92fe779256ee3076672e4190db64670"
},
"synthetic:dao-20": {
"captureGroups": 221,
"digest": "e29ee5132bb30bf939508cf3741159856b78881c01447acd290e18b1c5121225"
"captureGroups": 261,
"digest": "67e5362403ba445704d57df6265d928e0701f2bbef7108046d07d1cfaa5d3823"
}
}

View file

@ -1,103 +1,103 @@
{
"python-abstract-dispatch/app.py": {
"captureGroups": 11,
"digest": "8662c17b0f21fcfa650065abd62f0c9b7e1c65bf8a1f7dce6f2a16bba9df759f"
"captureGroups": 12,
"digest": "dbb94bff45b2e0601e2ba54a4e275c2668b99836e0565243406776b3a2268242"
},
"python-abstract-dispatch/base.py": {
"captureGroups": 18,
"digest": "f555f01c16bd9696b9854eccb0225ed6df3d271eebcac19812914ad38004786f"
"captureGroups": 22,
"digest": "0de787f2a48bc8089b0edc376f129b7e16d2b6f1c93677deda4c24dbd3513877"
},
"python-abstract-dispatch/impl.py": {
"captureGroups": 15,
"digest": "6c27015f13d32024ce06c1515ab29ca0436df7a4864bbb62188d5f79d685cc31"
"captureGroups": 19,
"digest": "adf88901cfc63fb9018cba28a675ce9b7c576752bd8d89b8664ed405e34347f5"
},
"python-alias-imports/app.py": {
"captureGroups": 13,
"digest": "ae1dc39a6d300a38bade534e58a1fcf5141eee06c6e903645a80dbce175b4949"
},
"python-alias-imports/models.py": {
"captureGroups": 11,
"digest": "abf5fcdf7cc473efa5a149319b0fc3d14b3ae91a2e9a14d621243c5ed7fafafa"
"captureGroups": 13,
"digest": "cec30bb7577edfb751bcd4f5026a0c9d77ed235071421a2a46f87580c7bcdf47"
},
"python-ambiguous/models/__init__.py": {
"captureGroups": 0,
"digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"python-ambiguous/models/handler.py": {
"captureGroups": 6,
"digest": "a148084ab160b53bd9a59f12a605447dee85eca1a46abfcca62fc48de56ca3a3"
"captureGroups": 7,
"digest": "65f156895b52d0f2f221edb0ae34294b2a4ded348ed859f47b601aee4d7e516b"
},
"python-ambiguous/other/__init__.py": {
"captureGroups": 0,
"digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"python-ambiguous/other/handler.py": {
"captureGroups": 6,
"digest": "41330f66b51bcedb14cdcd4cbb88601e3426e246d547ab7cf060da7c294bc8de"
"captureGroups": 7,
"digest": "37a41af1b2d5e8b2592e9e72ce2ca224a9a0ab4d9171819a80406931c16b9f86"
},
"python-ambiguous/services/__init__.py": {
"captureGroups": 0,
"digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"python-ambiguous/services/user_handler.py": {
"captureGroups": 8,
"digest": "5a4bb82d0e6a6a53fe012f197e42c572ac1169ff38e84db9c6281399f6739021"
"captureGroups": 9,
"digest": "cc2587ab88c688b5a87ac1500829b445197accdd52aa6a145b7e2dce2b115124"
},
"python-ancestor-import/a/b/c/deep.py": {
"captureGroups": 5,
"digest": "633dd357999ff9124b5c1faba69d0156f4a996693f4cb5dcd05e6cf530d897ea"
"captureGroups": 7,
"digest": "f183d93d9f0c70d893ff5062ce0621443e093b6bda8d223b15817dfb37ff3945"
},
"python-ancestor-import/a/utils.py": {
"captureGroups": 3,
"digest": "4d85820bac0e64fb70eceee1454424a75bc3db10f9fc9e7d822e3539ef50c8c1"
"captureGroups": 5,
"digest": "02af10663e3387191f6cf8b2c1872afc578c336a0117d539c8ad2295ffef0286"
},
"python-ancestor-import/backend/middleware.py": {
"captureGroups": 10,
"digest": "ed9769cc43e5e1b6dcaa5179e1c80f07ef15e75e728e6ae039091454b966fcec"
"captureGroups": 16,
"digest": "a53ebfd3ecd324bd63451675564f4842cd5b21f6e0c73619efd95d0ca83c4662"
},
"python-ancestor-import/backend/services/auth.py": {
"captureGroups": 7,
"digest": "73968a5d120b0a97e95e96b7c0c7433575e8178a832b735b42d5be34c72e2ba3"
"captureGroups": 15,
"digest": "6d8279e5a75475669231265317c58fd0ab2dc19c9f3eec168f377ad38e1de407"
},
"python-assignment-chain/app.py": {
"captureGroups": 27,
"digest": "26ab1b7aacc902373062b7d4a5c88e41619a3352664cf27e3d3cd92012f6a24a"
"captureGroups": 29,
"digest": "1c0ada0748ce42a77ab26dd1c35ea2246ee1c701158004c65a0944d1b28cae53"
},
"python-assignment-chain/repo.py": {
"captureGroups": 6,
"digest": "0d2704cfa14e431395973cc82b582f9fae43a8ce67d5548ab5df62b3fa8d1b7e"
"captureGroups": 7,
"digest": "29bd91eac43a803bb4afd96ee2d733f62a8c7a56c02cbca79e3d5394ff69ecb6"
},
"python-assignment-chain/user.py": {
"captureGroups": 6,
"digest": "4dd0a9d0f5797bae1c281a07145ddcc4162eae1ea16208e85edb74cd98e085f9"
"captureGroups": 7,
"digest": "3fa157d0e0576fe87c6abf5ce9a3509c82c04b2ecf1fb6d53290ddc1326af8e4"
},
"python-bare-import/models/user.py": {
"captureGroups": 6,
"digest": "3e5160418c4e3b4c0a896bed89e731711aff3a66872d99bb5ebfcbd624d9a3d2"
"captureGroups": 7,
"digest": "e023fa6d98558619733e75005e4c6dc4079967b4d43c2fc2ecd24136a4de6e1d"
},
"python-bare-import/services/auth.py": {
"captureGroups": 8,
"digest": "8b3659bfdc60eedb1a33fd803f5ad23f1458f134884ed600e3fb4a662e19dbf3"
},
"python-bare-import/services/user.py": {
"captureGroups": 6,
"digest": "0662b53fd75f4892afa665d4dfe51a8dc963971696a370a8b2c4ba124eeda2af"
"captureGroups": 7,
"digest": "89d7afbcc21096723cbbeaa5f12a5cec106158d9b426668cd73accdc08b337e2"
},
"python-call-result-binding/app.py": {
"captureGroups": 8,
"digest": "338c3922981604e71ddfc60ad61eba4b17f68ca654644e01add942c729b422cf"
},
"python-call-result-binding/models.py": {
"captureGroups": 12,
"digest": "a7f11709f76dcc9311554b4fe1dd4e95f5d6434e6232627b4c370f21e194bd69"
"captureGroups": 16,
"digest": "cbbb5168c28123820a70fe24016b0ed26ab02a6339d21ffe40fbccad940c1d70"
},
"python-call-result-binding/service.py": {
"captureGroups": 7,
"digest": "81f8d5783bfc95a9f83f02abd15f7bbc0869aaca964c62b40653cc05ab73ebcb"
"captureGroups": 9,
"digest": "40bdb4b59d0540330fcb861dca2f2b3fdb432e529af326ffc692cf69201f0af5"
},
"python-calls/one.py": {
"captureGroups": 3,
"digest": "4eee8157b23b84308163ffc2baa55c56aee3bd97c06365fe9b045f3c5066ad56"
"captureGroups": 4,
"digest": "b99c3406b4b8555cc521b27bab98eefed02a121dc2e878dd4f63133088603dc5"
},
"python-calls/service.py": {
"captureGroups": 6,
@ -112,16 +112,16 @@
"digest": "abc26d2f53c4dc0f091009b120e7869ca74b015bad637c5643fc4fdd69df946e"
},
"python-chain-call/models/repo.py": {
"captureGroups": 6,
"digest": "54e050e7cecc288e367e97dc59280fd4febb614c7099894591e122901261a130"
"captureGroups": 7,
"digest": "abc5c9d130a440235eaeea7fcb8fc01fe1f93a1eb548083ccba781a5dd143d16"
},
"python-chain-call/models/user.py": {
"captureGroups": 6,
"digest": "3e5160418c4e3b4c0a896bed89e731711aff3a66872d99bb5ebfcbd624d9a3d2"
"captureGroups": 7,
"digest": "e023fa6d98558619733e75005e4c6dc4079967b4d43c2fc2ecd24136a4de6e1d"
},
"python-chain-call/service.py": {
"captureGroups": 9,
"digest": "250f907deb4fec54761559db26f0938dc0accee480021cf95bcdd5167d33096f"
"captureGroups": 10,
"digest": "02ab55c19bc43c76da1b54e90e9176b6b0792438fd150dfcc4fd77f8452dd124"
},
"python-child-extends-parent/app.py": {
"captureGroups": 9,
@ -132,32 +132,32 @@
"digest": "d118691eb76c9432841743efee8556f1e7a1d136e9b403a12fd512f91d73ca61"
},
"python-child-extends-parent/parent.py": {
"captureGroups": 7,
"digest": "2f30a106769eeda283491fee883b448cd4bbcb5a8be2a587e7c72e2dca2a03f1"
"captureGroups": 8,
"digest": "8d56bb790ffb79c2c211799a331d343cef557f15e180bc5ed65c3a575280436e"
},
"python-class-annotations/repo.py": {
"captureGroups": 8,
"digest": "f53cefad4e2dd9951fe34eea13822f5c521d9a23c3185b559b390ada7c5e1f3d"
"captureGroups": 9,
"digest": "a35d836a4f2de8d78362cb72b81ac4d0918c11b78c0649317b12693b43590644"
},
"python-class-annotations/service.py": {
"captureGroups": 11,
"digest": "0b20f98ba8b035c4e065bcfa66df9c955ba98608ea7aece8b998cc62758e5d85"
},
"python-class-annotations/user.py": {
"captureGroups": 8,
"digest": "38e7b4fe8372de734b28227c926d0bc80f66d2539b7f8d0cc22c1d32bf69821b"
"captureGroups": 9,
"digest": "23419a3c41d655554aa7bdf47f9c95749c3c83788ba70b5f83bdc2a93c110d1a"
},
"python-class-attr-export-leak/app.py": {
"captureGroups": 9,
"digest": "e958f924b72ac35d92cb3a959c4bea866fa1020347e96a09e07b7087ce9a231c"
},
"python-class-attr-export-leak/mod.py": {
"captureGroups": 11,
"digest": "cf175a5a7b577bbf8e76acc004c5360f496d0a10cc5de0a3e31a212bd413b491"
"captureGroups": 12,
"digest": "1c11a05121d348a540b07761e0bb3c0e56898115bba2d6e64eab9924ed84743b"
},
"python-class-body-namespace-import/app.py": {
"captureGroups": 9,
"digest": "58843947b979524df15f256d240b789ac67356499013b9c96bcc9e14108f8eff"
"captureGroups": 10,
"digest": "ed0aa5fd5e211b41d541e28b3ebfce22e49e9fd482184a68ab16db835c36ae3d"
},
"python-class-body-namespace-import/mod.py": {
"captureGroups": 4,
@ -168,36 +168,36 @@
"digest": "e8621bf951ab14634a8ed38a5953ff59434d3406d59e9c16751b6ab9cc40e7e9"
},
"python-constructor-calls/models.py": {
"captureGroups": 11,
"digest": "3508b40afee5ce512fd9057be671629b8a122c88342ab358cbf69ac418a39e34"
"captureGroups": 15,
"digest": "201ce01b83b21d729aca89c6299570df55393a95f1899a2eaacd989873950177"
},
"python-constructor-type-inference/models/repo.py": {
"captureGroups": 12,
"digest": "a076029a7a594b3577cbe3017c60cbd8f376ae80bd1e5216b0469b165f1c9f76"
"captureGroups": 16,
"digest": "ad11823ee187cc3e1efab34a67a5013119b4ada87c5701b080921c0b0be09e62"
},
"python-constructor-type-inference/models/user.py": {
"captureGroups": 12,
"digest": "a7f11709f76dcc9311554b4fe1dd4e95f5d6434e6232627b4c370f21e194bd69"
"captureGroups": 16,
"digest": "cbbb5168c28123820a70fe24016b0ed26ab02a6339d21ffe40fbccad940c1d70"
},
"python-constructor-type-inference/services/app.py": {
"captureGroups": 13,
"digest": "5306b0960c7b88572e81aa5b203f7e10adbd5d6c8b4c54fc78314624d5864836"
},
"python-default-params/app.py": {
"captureGroups": 17,
"digest": "408a601fac98bb34b153210a3513e97a5d6d0537f5b02d01fffb4525d24395a3"
"captureGroups": 21,
"digest": "445664a07fde4405c6ad43cdcbde1ed0a634865c92183908454e2622166a94e2"
},
"python-dict-items-loop/app.py": {
"captureGroups": 8,
"digest": "98486424c256def8b89730644deb829ac1c1fc5c46d8f86e586ed2d8ab1a07b8"
"captureGroups": 9,
"digest": "dd51c32d705934b1384991ad2291869f446327752481abc20600d4ad9f553ea3"
},
"python-dict-items-loop/repo.py": {
"captureGroups": 11,
"digest": "a1f088dbd281662c7b0ba9797d07edaef8a2e5a82a6715242eb3b70c29db86aa"
"captureGroups": 15,
"digest": "8116cf4cbf4dca377e88f97ca645f40fab648a4e9a8e790b5b3761e4a3e17d7c"
},
"python-dict-items-loop/user.py": {
"captureGroups": 11,
"digest": "e9398cffc0a70490a3b5f90e3436292e306df36ee51955741d8ee91aa37b2e73"
"captureGroups": 15,
"digest": "15984fa30be4603f3e47c27342352dd602d104b33ac5224dc911d78a82d87926"
},
"python-django-app-imports/accounts/__init__.py": {
"captureGroups": 0,
@ -244,8 +244,8 @@
"digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"python-django-app-imports/billing/models.py": {
"captureGroups": 12,
"digest": "eedb2e1992f85a6743555947118784db9c78f3c113d2d598e53dd54cb80a0629"
"captureGroups": 14,
"digest": "9e85e8a35a603ceb7c7e108d0190b1a49fc5fc301a2fb6da2df8a02d4a1833d4"
},
"python-django-app-imports/billing/tests.py": {
"captureGroups": 2,
@ -264,68 +264,68 @@
"digest": "79729019cda1dd6f41848365b98416fc610aaa846464bed569573c1fde0892a3"
},
"python-django-app-imports/config/settings.py": {
"captureGroups": 20,
"digest": "776f9193ac3a1927c8b0900efceb5ac3f16400ddfa7fa5d9617153d0bcf6ea0f"
"captureGroups": 21,
"digest": "78bf737a725c170daaf7d6affc4bfb648600afeedca1d1ecf135952a8b8b3794"
},
"python-django-app-imports/config/urls.py": {
"captureGroups": 5,
"digest": "fb7fd5c6bac541e180e7f90bed248eeac937bc0cec7c2d8405f7f9bb20e87840"
"captureGroups": 6,
"digest": "3772e1a255d14196be0d4db2052bd34c4f2683b151550e9f3379011a93aa1718"
},
"python-django-app-imports/config/wsgi.py": {
"captureGroups": 7,
"digest": "2c57f45963ef158d2ad6a504c5d09fb4f3aa53d6198b91e91f8f824aae1de452"
},
"python-django-app-imports/manage.py": {
"captureGroups": 10,
"digest": "ba770ed72ce287e7739d1b3ec4165053bfd8aa49f9104682d1a7094c724e3039"
"captureGroups": 11,
"digest": "c5d3eb457808eb9c17502c751cd045dc89c469fb8e308498d1c21a6da2fcafa3"
},
"python-enumerate-loop/app.py": {
"captureGroups": 23,
"digest": "1f528f2084d1f6da954ec8ce7eef0e45f99a0b3ae58502adc262a6caee5aa661"
"captureGroups": 27,
"digest": "c76272c32fb41cfdc82784dc9d3e3f302cbb25df68e7d18cc7240c8e1c8c7b49"
},
"python-enumerate-loop/repo.py": {
"captureGroups": 7,
"digest": "1d6eb1cdc661f2463d8e1a499eaa5bfcd9367324f6090c44fe4d59ed02151215"
"captureGroups": 8,
"digest": "d1e23831dcae38034b278bfefa2b8c4e21ca722ab2c79bfb3126744338a2a401"
},
"python-enumerate-loop/user.py": {
"captureGroups": 12,
"digest": "a7f11709f76dcc9311554b4fe1dd4e95f5d6434e6232627b4c370f21e194bd69"
"captureGroups": 16,
"digest": "cbbb5168c28123820a70fe24016b0ed26ab02a6339d21ffe40fbccad940c1d70"
},
"python-field-type-disambig/address.py": {
"captureGroups": 8,
"digest": "03076501a92927ff778f3dbcb555aa59e40fc21b1fe367147fd50cc330637a92"
"captureGroups": 9,
"digest": "ea626af0637cfe9dbbf908917128a31375734743aab1e8b1b098cf0748aa7d1c"
},
"python-field-type-disambig/service.py": {
"captureGroups": 6,
"digest": "caabe7eb61e0393151c362f3ec18a38b1d23a77176c67814c41ed6a2cfa54005"
"captureGroups": 7,
"digest": "ebe4ba05a566f775df15b9eff96980b496068c8b5504036b41de29b203aeb755"
},
"python-field-type-disambig/user.py": {
"captureGroups": 11,
"digest": "d71983e74eb3c6ea73b4c8aa6457a6e6d6383538d5e5757aea9fcf18bcbe560b"
"captureGroups": 12,
"digest": "6faddc1fcc2f2ea3a78282fc3419ba8e5203f646555df335861f160ffc2c6d53"
},
"python-field-types/models.py": {
"captureGroups": 18,
"digest": "d1cf0a8e91042726967b6671ea720f2ddb57c5cfeb917cd942096ed0cb8bb0e8"
"captureGroups": 20,
"digest": "9b82907071315cb07aa6d4c97df8613d51f7371292afa124aa6338f90c204787"
},
"python-field-types/service.py": {
"captureGroups": 6,
"digest": "ad1c6a054f610dc609f3ec963746d49be87567d1bec643c811563bdcedc4469d"
"captureGroups": 7,
"digest": "03cf02243ec15b64694c0f5b33b80daf1689049123f91ade18ae9557aff1b028"
},
"python-for-call-expr/main.py": {
"captureGroups": 15,
"digest": "5c290b3b34f3f5e9dcdd6ee3ae72ba4223337cd64592330f9a8c6c6f13b2fd2d"
},
"python-for-call-expr/models.py": {
"captureGroups": 31,
"digest": "f809317e086279962b81038b7ef2053bd3bc96d35bb1dd84a1ad543bc8dfe068"
"captureGroups": 39,
"digest": "133a14c0543a41412d9d4fd5485d6270e1f4b0cd247f0ec0d71974850e4918c1"
},
"python-function-local-import-chain/app.py": {
"captureGroups": 9,
"digest": "ad4d45976ca10c3fc3b7bf498ee23397797368a5d2817311d0e95e943af89199"
},
"python-function-local-import-chain/svc.py": {
"captureGroups": 11,
"digest": "955591b75260b260c00d4ab4918c16ac344d23b5fffa9c47fbbacdc56c67ae47"
"captureGroups": 12,
"digest": "5463c3e1d3159af3d37f1e0341ea0a429c6f54cf99e117a466b210df05fe94ec"
},
"python-function-local-namespace-import/app.py": {
"captureGroups": 9,
@ -344,8 +344,8 @@
"digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"python-grandparent-resolution/models/a.py": {
"captureGroups": 9,
"digest": "5879a42d6655248623c9aee193fedcae51daa1887d8e597fdd944ad93af053a4"
"captureGroups": 10,
"digest": "c2a76b06792f8594c3734bf521cc192ecf18648eff96191ba78df6434915adf0"
},
"python-grandparent-resolution/models/b.py": {
"captureGroups": 5,
@ -356,28 +356,28 @@
"digest": "2ad9124422ca018e59855c55a054d5c37d15b448a90027842fc56dd6f61e4593"
},
"python-grandparent-resolution/models/greeting.py": {
"captureGroups": 7,
"digest": "90a9349f89eb7e7c76123c6b235d510a9f151c9cd7e0db07bd8ae9b99f38ee93"
"captureGroups": 8,
"digest": "6ce8f7f1b5ccd7951765eee06a538d2fe791f7ca955230fc8041b2cf46b9ca8d"
},
"python-local-shadow/app.py": {
"captureGroups": 8,
"digest": "b71ed90838b5898310a80843486871ebeb6e1f63d24e0565d4be947c7bb66dc4"
"captureGroups": 9,
"digest": "68c3ec5904bb2e2613c0b008a85e4bc598168a53fe7ae409bfc187d1f06373eb"
},
"python-local-shadow/utils.py": {
"captureGroups": 4,
"digest": "c11f7883ea696f30d188f8b8316cfd28746a10b4c7b898807ae21c67a8644ee5"
"captureGroups": 5,
"digest": "287289abcacd9dd0f3763632fe57579567df01b73ea4b48e68c59e2f8aded3da"
},
"python-match-case/app.py": {
"captureGroups": 7,
"digest": "9c1f82b03516fbb9e111f1abcc56fd329bbfe290d3e9c0f31e9c8a84d715a031"
"captureGroups": 8,
"digest": "d4155b08b28018e2d290923f93b197290f72ef4f2d026d11ff4d5b35d682c2a3"
},
"python-match-case/models/repo.py": {
"captureGroups": 6,
"digest": "54e050e7cecc288e367e97dc59280fd4febb614c7099894591e122901261a130"
"captureGroups": 7,
"digest": "abc5c9d130a440235eaeea7fcb8fc01fe1f93a1eb548083ccba781a5dd143d16"
},
"python-match-case/models/user.py": {
"captureGroups": 6,
"digest": "3e5160418c4e3b4c0a896bed89e731711aff3a66872d99bb5ebfcbd624d9a3d2"
"captureGroups": 7,
"digest": "e023fa6d98558619733e75005e4c6dc4079967b4d43c2fc2ecd24136a4de6e1d"
},
"python-mcp-tools/helper1.py": {
"captureGroups": 3,
@ -436,64 +436,64 @@
"digest": "25270485caa12492922ede0e4a2ed1e898f7d7862641e89cbc25ff0d31f77f18"
},
"python-mcp-tools/server.py": {
"captureGroups": 36,
"digest": "b65b1821625ca03577a06e5c90204a1513706f841386f8806310cd41eb67b9c1"
"captureGroups": 46,
"digest": "d37ba082866e1ba64ee27fd04ea3c6a7e6ac63b77900b1744caed57f9c31556b"
},
"python-member-access-for-loop/app.py": {
"captureGroups": 22,
"digest": "ec36378bb69d54cbe0d0a130e90f4122b7bc80191ec09e1af4437abca2d43ee6"
"captureGroups": 26,
"digest": "fe44c7af9a0e711f1d8cd448262f96e499a3feb759d20b148470c1f0ce62c4d4"
},
"python-member-access-for-loop/models/repo.py": {
"captureGroups": 6,
"digest": "54e050e7cecc288e367e97dc59280fd4febb614c7099894591e122901261a130"
"captureGroups": 7,
"digest": "abc5c9d130a440235eaeea7fcb8fc01fe1f93a1eb548083ccba781a5dd143d16"
},
"python-member-access-for-loop/models/user.py": {
"captureGroups": 6,
"digest": "3e5160418c4e3b4c0a896bed89e731711aff3a66872d99bb5ebfcbd624d9a3d2"
"captureGroups": 7,
"digest": "e023fa6d98558619733e75005e4c6dc4079967b4d43c2fc2ecd24136a4de6e1d"
},
"python-member-calls/app.py": {
"captureGroups": 8,
"digest": "3d5282754e81dc800cf1bf11b414e7986b0133ae6545f242030002ececab4840"
},
"python-member-calls/user.py": {
"captureGroups": 9,
"digest": "485f993536f52252ad9b06971e99adb391fa95d005217c3563e41c48359fdf56"
"captureGroups": 12,
"digest": "19f6f21b1d5d25dc3e1653537e986f65fdd3697efd48affa9325fe2ecffe188d"
},
"python-method-chain-binding/app.py": {
"captureGroups": 19,
"digest": "01fe4805f59723a5f163d26b7be3ed3e456eb3a093e3df3a8f034cecee22ebb9"
},
"python-method-chain-binding/models.py": {
"captureGroups": 34,
"digest": "062be597e84f6949477e56194ea5e875a6e1b8870e1af993d8c549d6f3de2d39"
"captureGroups": 45,
"digest": "ea3f745514a330d86447796734faff29f0c88ea49a7ef8781087c537426d29bf"
},
"python-method-enrichment/app.py": {
"captureGroups": 13,
"digest": "88dfd417951b8083184f83da8c1e0c2b19700cfbf1f1ff203a904ebc00f50b30"
},
"python-method-enrichment/models.py": {
"captureGroups": 27,
"digest": "c9f6e1678d8976665d7c7c3b1c649ff60ea5a4f397cf2fef2a559ccf4b5f08e3"
"captureGroups": 31,
"digest": "0645e71e2b604c65e2621ea00e19068ab4d3718f65e4f2900fcac187ae877b2c"
},
"python-module-export-vs-method-collision/app.py": {
"captureGroups": 14,
"digest": "d37707fc868b7b086fd2534356cef6f96d42f0ea4691847858d7a5ad03eabe34"
},
"python-module-export-vs-method-collision/mod.py": {
"captureGroups": 11,
"digest": "75158050a37ac0f4aa7427f87bfa1bb4d85933b92d023c9448d1fd21a787a3c3"
"captureGroups": 13,
"digest": "d3032a370833cf67e68672de0ec85b199756f43984488f93a37de81501cac1c1"
},
"python-module-import/app.py": {
"captureGroups": 15,
"digest": "29560a379fc78162c760c142019d480ddbdd56e807da5831bc39d2c559136f3e"
},
"python-module-import/auth.py": {
"captureGroups": 11,
"digest": "0b99a5f6add0359f530c27c82f14ceb884849b24461870b8b517ab38a2714ae1"
"captureGroups": 13,
"digest": "21f44ba75e592c14a94cd7820cfb04d2ccba7a9c97c40a778c6d727f286f8665"
},
"python-module-import/models.py": {
"captureGroups": 6,
"digest": "3e5160418c4e3b4c0a896bed89e731711aff3a66872d99bb5ebfcbd624d9a3d2"
"captureGroups": 7,
"digest": "e023fa6d98558619733e75005e4c6dc4079967b4d43c2fc2ecd24136a4de6e1d"
},
"python-multi-level-mro/app.py": {
"captureGroups": 9,
@ -504,16 +504,16 @@
"digest": "d118691eb76c9432841743efee8556f1e7a1d136e9b403a12fd512f91d73ca61"
},
"python-multi-level-mro/grandparent.py": {
"captureGroups": 7,
"digest": "f9f81d3a37c55b3e23bec3774405920afa29c5793c46860a98a06c5d0c7f0980"
"captureGroups": 8,
"digest": "fdb504c509c49a276524145bd2f5a9946f2e8ef8ea07be166abe5a3c15b748a0"
},
"python-multi-level-mro/parent.py": {
"captureGroups": 5,
"digest": "b68bfb8fdedb8f725c609264e604ccb674a5a775c0d87c008a9990234c70bde3"
},
"python-multi-segment-ancestor-import/backend/auth_utils.py": {
"captureGroups": 6,
"digest": "45f881e9deb28e29d912924a6d108378cc7f5046ed84b0302d9e4b36c5085f8c"
"captureGroups": 8,
"digest": "cb8c5013202cea31bd75d05907e2d45087060fa9b00c0983127701012c3dc6d2"
},
"python-multi-segment-ancestor-import/backend/routers/__init__.py": {
"captureGroups": 0,
@ -532,112 +532,112 @@
"digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"python-multi-segment-ancestor-import/backend/services/alerts.py": {
"captureGroups": 3,
"digest": "bcbe9f5923dda8b54c1cc1a07c38439ce93d5ce13101775c20ff6e4de9afb404"
"captureGroups": 4,
"digest": "a0c37af99021c9f0f08c7f80c89b8126581a78f3595a9dd54fe9b0de9e95723c"
},
"python-multi-segment-ancestor-import/backend/services/sync.py": {
"captureGroups": 5,
"digest": "3c8bb78fafe4c0797d9d1ca162911d96f7b6fd3a5a037c26952b7f0287610150"
"captureGroups": 7,
"digest": "c247e0961ca943badd8407134d12f9d0760020336bcd14b462500bcbbe2ca078"
},
"python-named-imports/app.py": {
"captureGroups": 5,
"digest": "179248a29a48abba318bece3fe6eeca436db9544126684e203ad27983dc41695"
},
"python-named-imports/format_prefix.py": {
"captureGroups": 3,
"digest": "199776174b6aa80ea46969db4b427dbd4d6be0d4d6a96d62f05d984556a2b09f"
"captureGroups": 5,
"digest": "7f321a84964a7f3c1c3612150331b94e635f40bac021d4b5e407be36b4740188"
},
"python-named-imports/format_upper.py": {
"captureGroups": 4,
"digest": "3f0a4801427bc126dff1d700a91d9133f3e59d3db4db26eccfb3276258cb4fea"
"captureGroups": 5,
"digest": "df334f13bf7c81ba01c861c2b9a4ddc969645ccecbd536088c3556a2a2fb1f2b"
},
"python-nullable-chain/app.py": {
"captureGroups": 31,
"digest": "d5a8af8929a21808c3570cdc175b8412a5925b41cdace728ffc0179cdb2bc484"
"captureGroups": 33,
"digest": "b27470a2f2dc030240c2f48010e489278e95859db80e8003745334ab32f0d455"
},
"python-nullable-chain/repo.py": {
"captureGroups": 7,
"digest": "8c79e31ea4557abf29e4e1ec087db258e48afa3f9cc1e637291d0d5d49914980"
"captureGroups": 8,
"digest": "12ae0c4f238838d13501844676855952c54c47a0fe5eca937355e918e1f041b1"
},
"python-nullable-chain/user.py": {
"captureGroups": 7,
"digest": "967ff5732d066c516ad0bfddd87696598ab9c2c4b55c4463641db14af4cdfb78"
"captureGroups": 8,
"digest": "72f02563ba72a1a1a595623a81341c694124054698afeb606e65662915b66a22"
},
"python-nullable-receiver/app.py": {
"captureGroups": 23,
"digest": "7f22953242a4402b31ebcd6219d4976fd706c2775ef60d85f0911900e08cd647"
},
"python-nullable-receiver/repo.py": {
"captureGroups": 6,
"digest": "0d2704cfa14e431395973cc82b582f9fae43a8ce67d5548ab5df62b3fa8d1b7e"
"captureGroups": 7,
"digest": "29bd91eac43a803bb4afd96ee2d733f62a8c7a56c02cbca79e3d5394ff69ecb6"
},
"python-nullable-receiver/user.py": {
"captureGroups": 6,
"digest": "4dd0a9d0f5797bae1c281a07145ddcc4162eae1ea16208e85edb74cd98e085f9"
"captureGroups": 7,
"digest": "3fa157d0e0576fe87c6abf5ce9a3509c82c04b2ecf1fb6d53290ddc1326af8e4"
},
"python-overload-dispatch/app.py": {
"captureGroups": 21,
"digest": "37fa5251fc1914d95fab2c6aae6d97b460a47f46c5b74415a86c955f4ed78626"
},
"python-overload-dispatch/service.py": {
"captureGroups": 28,
"digest": "a873c2cebeb1092fe21b75b2109052fbb66335a378dd5468b32a57a4644ad14b"
"captureGroups": 37,
"digest": "b5bbb6744e86850bbea1afe2e7b38f4e14e01b27bcf68777695dffd8065623bb"
},
"python-parent-resolution/models/__init__.py": {
"captureGroups": 0,
"digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"python-parent-resolution/models/base.py": {
"captureGroups": 7,
"digest": "f0384bd6ecb7d1a9ad2306358917b7295c71ea8f1bcea818fd39c56d2c28c7e9"
"captureGroups": 8,
"digest": "3e885e626c46c7c4b9779867991a939f136f3a5669f092094c3ef33542ddf7f1"
},
"python-parent-resolution/models/user.py": {
"captureGroups": 9,
"digest": "b06a66a108097eec9427a028dec38bbab284918ac39344e86e58bba89533c530"
"captureGroups": 10,
"digest": "4c239f6abff4d91c4db07436c0d23a6012dd38f2b900c184e06da38237a3e776"
},
"python-parsing-coverage/heritage.py": {
"captureGroups": 21,
"digest": "dc7df1006ed20c6f7fd17799eb090ce2ebdd4d064384949d612835f5f05a9158"
},
"python-pkg/models/base.py": {
"captureGroups": 9,
"digest": "4984ee01b7a9fefe622195f0e4925823c0e62a1714ca2dda5cd8250e5e45fa7c"
"captureGroups": 11,
"digest": "53dd613c8d7cd9e20ec44919455741b0cccea5e32efc907680c8078b16c7bf2d"
},
"python-pkg/models/user.py": {
"captureGroups": 8,
"digest": "9ee707b36f42a635fdb867ce20359b5f51ac4e13550cde801359dc8314e01a77"
"captureGroups": 9,
"digest": "d708868d257b88964e4b44646f0870363000985725f86a3ecd2c2b063b42b07c"
},
"python-pkg/services/auth.py": {
"captureGroups": 9,
"digest": "3c9e44e7870643e475a67f562c15a42d58ade2b47a6c43ad665ef73e53e765ff"
"captureGroups": 11,
"digest": "6fc0ba60b6b2b9ad5825ab0109fb0de979937d06316983b30226e1b788d5bcc5"
},
"python-pkg/utils/helpers.py": {
"captureGroups": 7,
"digest": "9a12c0b8c2523d4dfd5919e04cfe2bf4e9f6b84fe1a83a146f58996b7d453bb5"
"captureGroups": 8,
"digest": "c3b1fa72e2d2bb8b8e2398b0d79c4b0a660949efbde7a1303bf5d2811a57f1e6"
},
"python-plain-import-alias/app.py": {
"captureGroups": 17,
"digest": "a419c716b0d13787ce1f3cc2adb5ab2ab77604cb8348a9b27aa56443984ab0b2"
},
"python-plain-import-alias/auth.py": {
"captureGroups": 6,
"digest": "51a9f92e1dae910b08b86f506c174ad2f60dfc1d2d5f66d22838978d7d424b48"
"captureGroups": 7,
"digest": "d8be5670ece6958c45dc48149f1bf0a826adf285262ab93745e34e61fe80e60b"
},
"python-plain-import-alias/models.py": {
"captureGroups": 11,
"digest": "abf5fcdf7cc473efa5a149319b0fc3d14b3ae91a2e9a14d621243c5ed7fafafa"
"captureGroups": 13,
"digest": "cec30bb7577edfb751bcd4f5026a0c9d77ed235071421a2a46f87580c7bcdf47"
},
"python-qualified-base/a/__init__.py": {
"captureGroups": 0,
"digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"python-qualified-base/a/b.py": {
"captureGroups": 7,
"digest": "725adf1074b272c8d2ce664549106b07286a92cfb08ab4d605068102317f8baa"
"captureGroups": 8,
"digest": "2c3fdd9f12bcb1dfc9165b65d4dc4768519a26a1bcfa7ec8cacc102d7ba1e069"
},
"python-qualified-base/base_mod.py": {
"captureGroups": 12,
"digest": "7fc34dae23f54cdee030a2d4a1d80c0bf226ea3b37335ecf48551c4707d32e20"
"captureGroups": 14,
"digest": "e3033d124fafff6e4bf7f62307359f2070c4fc876d3a986a8476397975aa98ba"
},
"python-qualified-base/service.py": {
"captureGroups": 16,
@ -648,20 +648,20 @@
"digest": "9e4d51c06d75721e3c07898124ef85139d52d9f20d6df6bae557aff98a3ed207"
},
"python-qualified-constructor/models.py": {
"captureGroups": 13,
"digest": "94e738bd516d6e1fa1ca130fc231157071c04aa3b2ad74db6dabbacd7b7842a7"
"captureGroups": 18,
"digest": "6b350aa1579d58e036053a16e8e94b91d8a8f6906f78d4f44ef675887a48bf5d"
},
"python-receiver-resolution/app.py": {
"captureGroups": 15,
"digest": "806934204693a06760f3c74e8c68145eda5cf19d7903130646e152ea14fc7a98"
},
"python-receiver-resolution/repo.py": {
"captureGroups": 6,
"digest": "0d2704cfa14e431395973cc82b582f9fae43a8ce67d5548ab5df62b3fa8d1b7e"
"captureGroups": 7,
"digest": "29bd91eac43a803bb4afd96ee2d733f62a8c7a56c02cbca79e3d5394ff69ecb6"
},
"python-receiver-resolution/user.py": {
"captureGroups": 6,
"digest": "4dd0a9d0f5797bae1c281a07145ddcc4162eae1ea16208e85edb74cd98e085f9"
"captureGroups": 7,
"digest": "3fa157d0e0576fe87c6abf5ce9a3509c82c04b2ecf1fb6d53290ddc1326af8e4"
},
"python-reexport-chain/app.py": {
"captureGroups": 13,
@ -672,107 +672,107 @@
"digest": "ee0d8506fbaaf9cbf3a2f78bd34b7e688eb01aed590408cc2d6c59a4ee3bb4d8"
},
"python-reexport-chain/models/base.py": {
"captureGroups": 13,
"digest": "4ea6fa3504e37231c8d395259009df5d4adda31e3ea0bfc28cd3c3eabdd68280"
"captureGroups": 15,
"digest": "e1e9f98ffb690ffabecd7287bf7f640e3fa53ecdda37dba41692b6914a37c591"
},
"python-return-type-inference/app.py": {
"captureGroups": 8,
"digest": "741f690b6330491303b9b58cb31027a33600973265b59428facfefbabf0cf7e1"
},
"python-return-type-inference/models.py": {
"captureGroups": 12,
"digest": "a7f11709f76dcc9311554b4fe1dd4e95f5d6434e6232627b4c370f21e194bd69"
"captureGroups": 16,
"digest": "cbbb5168c28123820a70fe24016b0ed26ab02a6339d21ffe40fbccad940c1d70"
},
"python-return-type-inference/service.py": {
"captureGroups": 7,
"digest": "81f8d5783bfc95a9f83f02abd15f7bbc0869aaca964c62b40653cc05ab73ebcb"
"captureGroups": 9,
"digest": "40bdb4b59d0540330fcb861dca2f2b3fdb432e529af326ffc692cf69201f0af5"
},
"python-same-file-method-collision/app.py": {
"captureGroups": 17,
"digest": "cd1e6bd1cea7de20c9317fa650bb0b3492df81d736d24142a0a3f082834c507a"
},
"python-same-file-method-collision/models.py": {
"captureGroups": 21,
"digest": "3c5ef9ae13442f9c77a61c4fc6b76ebc580cdd5857810cc484f6cb768fa9d727"
"captureGroups": 25,
"digest": "b892bec2982f22ac15e5db0decc934be3a59464961fdde657fb1a80110df074e"
},
"python-same-name-collision/metrics.py": {
"captureGroups": 7,
"digest": "57482d976bc3d4f232064b55f809507a9e00ec4f75ee519691034227c666312e"
"captureGroups": 8,
"digest": "5bede48bac9564a854a5e6073b9b099c54da7ae4ab44e33ce8b337125326fd67"
},
"python-same-name-collision/router.py": {
"captureGroups": 10,
"digest": "e9248113f2dbae22dff443551daa131136c69f00ea0fb912a6419fcb1c1e7833"
},
"python-self-this-resolution/models/repo.py": {
"captureGroups": 7,
"digest": "1d6eb1cdc661f2463d8e1a499eaa5bfcd9367324f6090c44fe4d59ed02151215"
"captureGroups": 8,
"digest": "d1e23831dcae38034b278bfefa2b8c4e21ca722ab2c79bfb3126744338a2a401"
},
"python-self-this-resolution/models/user.py": {
"captureGroups": 12,
"digest": "b23ad4122f7fdb5b3f9b047582229a9ea001a29452035974112d4f425ef86cf4"
"captureGroups": 14,
"digest": "c7ed155a14a8ca915ec5db84e5eed21d783a3a8ef8ed6a74a456cfe7456eac09"
},
"python-static-class-methods/app.py": {
"captureGroups": 14,
"digest": "b397708c05d101d0d3978313f65184c9f181a179b803d242b1e585ac2605fe4a"
},
"python-static-class-methods/service.py": {
"captureGroups": 32,
"digest": "91730faa59b93ee5a06c8f7050831d32f39ad89c624cb665a4eeb38449aa2c3e"
"captureGroups": 39,
"digest": "0c734dc7f683469f9c1a9eb97fd8be2af68e29aedd20cd8c6f0c86c17f7ce13e"
},
"python-super-resolution/models/__init__.py": {
"captureGroups": 0,
"digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"python-super-resolution/models/base.py": {
"captureGroups": 7,
"digest": "f0384bd6ecb7d1a9ad2306358917b7295c71ea8f1bcea818fd39c56d2c28c7e9"
"captureGroups": 8,
"digest": "3e885e626c46c7c4b9779867991a939f136f3a5669f092094c3ef33542ddf7f1"
},
"python-super-resolution/models/repo.py": {
"captureGroups": 7,
"digest": "1d6eb1cdc661f2463d8e1a499eaa5bfcd9367324f6090c44fe4d59ed02151215"
"captureGroups": 8,
"digest": "d1e23831dcae38034b278bfefa2b8c4e21ca722ab2c79bfb3126744338a2a401"
},
"python-super-resolution/models/user.py": {
"captureGroups": 11,
"digest": "8a66f8962fcf960b66c1106d1d67da21f7f6bac323b961e5fc0623b3c93dbc38"
"captureGroups": 12,
"digest": "fbd079ff1c9d18b88c3ab32ff58c3ffefecb40e75fbb6fd5e7a21049490dfe5c"
},
"python-variadic-resolution/app.py": {
"captureGroups": 5,
"digest": "62b16647599de4db600ece073131025de23bf692d1585d8b7f1bfc3fae2f3dcb"
},
"python-variadic-resolution/logger.py": {
"captureGroups": 5,
"digest": "5995f1a01f8354c3d854d4f4dc51fb4a2919bdcb22e90eb0a64431eb03e5804c"
"captureGroups": 7,
"digest": "7d460bb9d28620ce178db652e15427a7cf7839cdf9ef651a9c30209ed3d22d82"
},
"python-walrus-chain/app.py": {
"captureGroups": 33,
"digest": "07590672df6cd4d1069ec346033fa29c116d42c841ec68da8d22afd44effb938"
"captureGroups": 37,
"digest": "6453e4257122110a3efc9a868dcdc6c08dbe614370f59cd66bd2356fb9220bf9"
},
"python-walrus-chain/repo.py": {
"captureGroups": 7,
"digest": "8c79e31ea4557abf29e4e1ec087db258e48afa3f9cc1e637291d0d5d49914980"
"captureGroups": 8,
"digest": "12ae0c4f238838d13501844676855952c54c47a0fe5eca937355e918e1f041b1"
},
"python-walrus-chain/user.py": {
"captureGroups": 7,
"digest": "967ff5732d066c516ad0bfddd87696598ab9c2c4b55c4463641db14af4cdfb78"
"captureGroups": 8,
"digest": "72f02563ba72a1a1a595623a81341c694124054698afeb606e65662915b66a22"
},
"python-walrus-operator/main.py": {
"captureGroups": 7,
"digest": "4df7ea089c43552ca4ea5a51f8e985d11d351b86949efb2f7ebefdf6a9ffd689"
},
"python-walrus-operator/models.py": {
"captureGroups": 16,
"digest": "ccd7c8c00bc2fe3bd2bad667e2448591c3508e9eb91a0005a9148a2d3734ac98"
"captureGroups": 21,
"digest": "cf014d1bad66ea61e327c146fd1a52159a96faaf264555bb164230a5218e6948"
},
"python-write-access/models.py": {
"captureGroups": 11,
"digest": "d1b714cb6654f83df3bb40830ced44f2950a122270c9e379f732943d57688151"
},
"python-write-access/service.py": {
"captureGroups": 9,
"digest": "0aa940e3428c35b1324544e9bcc75c166732b63b3e65e6faf460bc3d3a9571ec"
"captureGroups": 10,
"digest": "6e3690ec68d8de54f376bb6f5f7a29da829a8a6a24001eabae9ec66a327f3409"
},
"synthetic:dao-20": {
"captureGroups": 493,
"digest": "3ef7932339801c2cfa24609b741b472bed2f1037b413edafaadf8b8c2edd8f7c"
"captureGroups": 733,
"digest": "c540f2143882137e6c6f7996bf9b87a0117f41915b1d0989d3d6bb9db5a5a1ab"
}
}

View file

@ -36,20 +36,20 @@
"digest": "6567c827276575ef3a087757b5725e381fbedb421ffecfe6c5904d7d62f6bcd4"
},
"ruby-call-result-binding/app.rb": {
"captureGroups": 17,
"digest": "42a4ffb39fa090608cb0f60e8c31d2c51c7515a4ee6ae038e9accbac0d2e5488"
"captureGroups": 18,
"digest": "194c1ca21a7d5d8d85cf9aecfdc4edd881c5d3447de1ad574b56cff5a411bab1"
},
"ruby-calls/lib/one_arg.rb": {
"captureGroups": 7,
"digest": "a04d2c55b5738a824bfa4139925cade1d437bf0a1a91ebf044c5098e2e5f9b58"
"captureGroups": 8,
"digest": "61300083e7a2e6d9a116c3975ddd3b78a593a9a0e397c252bedb160d481c20b7"
},
"ruby-calls/lib/service.rb": {
"captureGroups": 11,
"digest": "7a4d964547d3ac645274c65fcbceba3f6852286537af2452c87d75bbde3495ab"
},
"ruby-calls/lib/two_args.rb": {
"captureGroups": 7,
"digest": "533578cb1111c137d4cc660d1667ba43f649799a9fbd6139a20ac389d16477bf"
"captureGroups": 9,
"digest": "0b781a930116eed742de7bfbc0852ecdf4056d5ccc4f49c2b779da49c5ca04e9"
},
"ruby-chain-call/lib/app.rb": {
"captureGroups": 13,
@ -112,16 +112,16 @@
"digest": "afa655d9a3c7ebea02b529d33904575692a53531a619c3fd34634f0bb43855e4"
},
"ruby-default-params/app.rb": {
"captureGroups": 7,
"digest": "84779fa164cbc83863f6ae1552ca8ed19f21a18cb410b26ea581132c21d88a6a"
"captureGroups": 9,
"digest": "5724292fa04411256cc8c43c0bd3b3a58be5b5e457fdead417da379199a18e31"
},
"ruby-field-type-disambig/address.rb": {
"captureGroups": 8,
"digest": "cfb08d28049718393f211454b0c0148aaa82d7093915f0d8650a224d8ff687e3"
},
"ruby-field-type-disambig/service.rb": {
"captureGroups": 8,
"digest": "bb2449fe1f87d23fca7db7507987d32daae36cbee8166fe9990006934b9d05a5"
"captureGroups": 9,
"digest": "beafde5670e8b6f2f2f7966bcfe71c9d3adec92ec0fa188f8b47587e2d559a4a"
},
"ruby-field-type-disambig/user.rb": {
"captureGroups": 13,
@ -132,20 +132,20 @@
"digest": "6673d1c2b299edee80c5d7ff0dddb8741e6b03070fbc119a5c1f14598ed8b005"
},
"ruby-field-types/service.rb": {
"captureGroups": 8,
"digest": "9dcde083c602b85bf41561623dc7e6af16718cd1c8bacf00ac4b8d917a1557cd"
"captureGroups": 9,
"digest": "209a8f57b1f5a55c293ce3617c3040fe55d348168bc56e96a416e8ed8220cf58"
},
"ruby-for-in-loop/app.rb": {
"captureGroups": 8,
"digest": "b584394fac8f939f3d4d1170c373da4332bfeffa641e64e997d108e85a4c5ec3"
"captureGroups": 9,
"digest": "63e0c857c40f59580432d18045a2e07bc628edb5464850fd277bb4af2bbb1996"
},
"ruby-for-in-loop/repo.rb": {
"captureGroups": 9,
"digest": "fb4c12f847eca414066c5219a68c2dd48167a174d28788da118814ee8931ccfd"
"captureGroups": 11,
"digest": "06ccf6d3f7c370efe1aab899e20eaeae0552c2d068bb2fbeee3ae88a55b0bc60"
},
"ruby-for-in-loop/user.rb": {
"captureGroups": 9,
"digest": "b6f4c241293057f3d4505cfec9d63376d1f598599dc6bf0a38ec0bef242dda5a"
"captureGroups": 11,
"digest": "5ee42d0becb14846a96ff4bd83832c30cf29bbff31713cc1f3fee484cff721b8"
},
"ruby-grandparent-resolution/lib/app.rb": {
"captureGroups": 10,
@ -188,8 +188,8 @@
"digest": "54fc82a9a0a67ccff1d5ca3055c93044e79e6cd2972d943f9c2e90f1f2198716"
},
"ruby-method-enrichment/lib/animal.rb": {
"captureGroups": 28,
"digest": "f694a63268d14ab07db08332809823351abc680a7dc53f4797651f0e7b167384"
"captureGroups": 32,
"digest": "692d351da11c65998ebce97ab2c494df6b7aaf50af759caeefbb19d8f2726475"
},
"ruby-method-enrichment/lib/app.rb": {
"captureGroups": 14,
@ -200,8 +200,8 @@
"digest": "8f6555980417465c870a16742eb2d49fcb8a31e26777c11b926180674aa93e97"
},
"ruby-namespaced-constructor/models/user_service.rb": {
"captureGroups": 12,
"digest": "c10f36dbbbe2be16fc3fccbebb7ee79668ec7a77b75adbd5281ded31893d49de"
"captureGroups": 13,
"digest": "5e0334b33bf3971198e58d8d7637bab148381744ce3b44bb7f17846c863b690d"
},
"ruby-namespaced/namespaced.rb": {
"captureGroups": 16,
@ -224,8 +224,8 @@
"digest": "288d5386cf37fb76b52a94bc7da6bf8e7843830ebbb01fcd8100d1590c0e3f72"
},
"ruby-overload-dispatch/lib/formatter.rb": {
"captureGroups": 11,
"digest": "3e210571081b6977ea7d8b23f1678b57bedb2414cf5f6730466bf7b56829857c"
"captureGroups": 14,
"digest": "48a0c401c3716da69fce6beb17f9be49c7994074a2fb44599aa0fce2a8c6c2b1"
},
"ruby-parent-resolution/lib/models/base_model.rb": {
"captureGroups": 6,
@ -264,12 +264,12 @@
"digest": "0db7356580a8ac5addec69a3259e5a78028e01b44f5e36316a6f76172929f829"
},
"ruby-return-type/models.rb": {
"captureGroups": 14,
"digest": "b731080e81701e48bbe0eff87dcb0286d266626037dc1e422cc268ef1a644722"
"captureGroups": 18,
"digest": "e176965a226cc1c84bfd10a2d22083facea402184e9907c6d7858fcd9bc459e5"
},
"ruby-return-type/repo.rb": {
"captureGroups": 14,
"digest": "70875215143713279e9595e89b8c5d7d2c642d0660365772b64daf5b8b487b4f"
"captureGroups": 18,
"digest": "c23aa57b5f96ea094dc201daa0b2d02d7a7438b53488977eadc6ab2ebe63a68b"
},
"ruby-self-this-resolution/lib/models/repo.rb": {
"captureGroups": 6,
@ -288,8 +288,8 @@
"digest": "f56ff0b87abb7463dcf5fdbe18c1cf74c11595853ff04ff8d929cc8d5cb71ca5"
},
"ruby-sequential-mixin/lib/logger_mixin.rb": {
"captureGroups": 7,
"digest": "c6edbab52ae7a58d89bd11da498d99a7cf0eaaeab531c56774b1a3fee1c39d1e"
"captureGroups": 9,
"digest": "b37cb0302afa4e380fb02828fcd4219edfd6aabbe65c74e4a5f5de9a65b33003"
},
"ruby-sequential-mixin/lib/prepended_override.rb": {
"captureGroups": 9,
@ -320,27 +320,27 @@
"digest": "106fe23a380801055a2ef642f0b92d4552ce074321ebe831601f2cb89a8d8529"
},
"ruby-write-access/service.rb": {
"captureGroups": 18,
"digest": "b39aef2dd2721626e0c1718ea150455bc54522022ba45ce0d235f2e33e861be9"
"captureGroups": 21,
"digest": "444cd08dd9f29508da3298f7a14a91dd6edab354ab4ff6345b3744afa65136d7"
},
"ruby-yard-annotations/models.rb": {
"captureGroups": 14,
"digest": "1906ddedab8b6f352468a13cefd3d018889492b8dc983f854ccbe7a2b84a938f"
"captureGroups": 15,
"digest": "47b1dc604ac3070fcb24575b06e452f9c6ba95424dd8cdc98c65a25b2a635251"
},
"ruby-yard-annotations/service.rb": {
"captureGroups": 13,
"digest": "f0864dc29de46bc9ae3c15cae5714490aac6536d8da0704a353f2300df75bb63"
"captureGroups": 15,
"digest": "1778fe41038f4a05ecca406ad5cffb0e6b33e69b81265702611a6d498aa68b06"
},
"ruby-yard-generics/models.rb": {
"captureGroups": 17,
"digest": "3a0722a48ac72ad10a492d10c4728a805990d07789e5b20a239a6c95a914d23f"
},
"ruby-yard-generics/service.rb": {
"captureGroups": 18,
"digest": "040e4c3a4091238c8b59089f4b4e92d59879dbe5053131395a5e1ac6ad5f1d09"
"captureGroups": 21,
"digest": "f2452a20687ba8adb2ac86a23d4c6e3af51df62f65c28212dfbb0f1244643cb3"
},
"synthetic:dao-20": {
"captureGroups": 161,
"digest": "99da729084d5a08425f04078fb633b35b7256e861b67bce095af59cb42e151fe"
"captureGroups": 201,
"digest": "62db9b6265ac37ece2271596da05196463ae9bbb7d9af40798f55bfe1af1768e"
}
}

View file

@ -1,11 +1,11 @@
{
"rust-abstract-dispatch/src/lib.rs": {
"captureGroups": 28,
"digest": "f1fad77028347c5102a39dbe6522786fa7863e5d4fcaf675294dffc23555d23b"
"captureGroups": 30,
"digest": "88309004d1ab00054f81bc55c1d058fc4ca25781162d6b94b7e7ce631a5d61b2"
},
"rust-abstract-dispatch/src/main.rs": {
"captureGroups": 19,
"digest": "f0483e7bed3772013ed48efea6071e4959603e5ceb9b3786e4d334c7a1ccf76a"
"captureGroups": 21,
"digest": "9e37c9806579acc6ad6c43e98b7c1320d411020fb0c0e88f5b2c8bbed2053d7d"
},
"rust-alias-imports/src/main.rs": {
"captureGroups": 19,
@ -40,8 +40,8 @@
"digest": "225b1a2de0f98873a99c669275ecce6a7f6f0d04f2b50f6a15f7546c06f2e295"
},
"rust-assignment-chain/src/main.rs": {
"captureGroups": 35,
"digest": "ba2d21b5126b59c0cfe9d99427ee6726eab0e3f2d19022e4b946bddf11730b42"
"captureGroups": 37,
"digest": "d9dc8c233465fb4d160c8547380fa05a0e2d1cdc4dae1205a33f6e96506f0df6"
},
"rust-assignment-chain/src/repo.rs": {
"captureGroups": 10,
@ -68,24 +68,24 @@
"digest": "db75d1b522e5d3574037b692e51423f5f71b6bba011aa120e33d0c6fbc6c1033"
},
"rust-call-result-binding/src/models.rs": {
"captureGroups": 19,
"digest": "550f189e1374dc52599482fb63c8363a7839b4a7514aa6e911c354b3a92926a9"
"captureGroups": 20,
"digest": "768d02914dc9e5b495b091ae3f6a27f90ac7b043eeb54e1401c07db4126a5e36"
},
"rust-calls/src/main.rs": {
"captureGroups": 9,
"digest": "2ec004ce1d77e3aee80ae1332bc0ab64fb366deffbac84f84ff1861286edc4e5"
},
"rust-calls/src/onearg/mod.rs": {
"captureGroups": 6,
"digest": "5a2b4645b0dcebfb9eada1a5b168b99830316859854d1f9c55654a3ab0211f0c"
"captureGroups": 7,
"digest": "32c1951a3ae761c4451f78883ee1e520d1e3fb3e11100c9052668547eca20e69"
},
"rust-calls/src/zeroarg/mod.rs": {
"captureGroups": 5,
"digest": "aae68fe5023fa93180b05ce8034654bfa1d2d95bc7eea67625e2e3a3cff6ebee"
},
"rust-chain-call/src/main.rs": {
"captureGroups": 23,
"digest": "c99d5d1f0aaf873eec47268f852fc265e4f1e2d2be08b76be653e49f43ffc5df"
"captureGroups": 24,
"digest": "eae71a411927bfc015e03800fda779c065c2513e6a6aa130211216d9bee61a35"
},
"rust-chain-call/src/models/mod.rs": {
"captureGroups": 3,
@ -104,8 +104,8 @@
"digest": "4af705aa98a718b54c1e8a2037989c747c3e4e6bb8ba833cdf6672a1f8983cec"
},
"rust-child-extends-parent/src/main.rs": {
"captureGroups": 17,
"digest": "21698d0650c872cb7a2f0849aef47d266ffb6a851d01286174d76f74a25acd1e"
"captureGroups": 18,
"digest": "0c87c741f9b05ff5e01eaf6a27c285ba6ee76b753d6609fb43f8d78e5f66fc0a"
},
"rust-child-extends-parent/src/parent.rs": {
"captureGroups": 7,
@ -128,8 +128,8 @@
"digest": "ff4606a898325894a9ef71fa5d16446fba7f24976bb88d7fcf5e6e62f76f99d3"
},
"rust-coverage/patterns.rs": {
"captureGroups": 8,
"digest": "107045c7d648c89b825214f56bdaffee80fccbc063b69a126b95d5aca27618b6"
"captureGroups": 9,
"digest": "b551630a14753534a8f1fe3ea3bffbcfe077569440c2dcc9016c85489dc53806"
},
"rust-coverage/union.rs": {
"captureGroups": 5,
@ -156,8 +156,8 @@
"digest": "fe28a5861492fc4b20d911e44abc246ec33e4f3305fe4bd7e95c952972f17564"
},
"rust-deep-field-chain/service.rs": {
"captureGroups": 15,
"digest": "143fcbb0bda81b945ecb1b3a692fb35ccc1e7b2ee1eea64f20ec548b432a53fb"
"captureGroups": 16,
"digest": "49d6a6bff4bfde6d29d8e63c2899f49688bee98273c9f743d9189dbfdf19ce2c"
},
"rust-default-constructor/src/main.rs": {
"captureGroups": 34,
@ -176,8 +176,8 @@
"digest": "798c8e01c6e54792ba69e845248efc8abf0cba38fa3d16fb8e0d1f6dd2ad2b7e"
},
"rust-err-unwrap/src/main.rs": {
"captureGroups": 24,
"digest": "e4c452ce79e6e09f24d77acb9537f38b46afb4c59661192d0a4bc496a2b753bb"
"captureGroups": 26,
"digest": "d7da37b0af98c116dba470de505610c4fbb06f152078b684c0094b1e81aff7e4"
},
"rust-err-unwrap/src/repo.rs": {
"captureGroups": 9,
@ -192,8 +192,8 @@
"digest": "f6c64476fd1b30699d0c35cf7b64cd4210b3ae2158f29ad0faae614b1b218c83"
},
"rust-field-types/service.rs": {
"captureGroups": 10,
"digest": "03f3b9175fc383450ab97e7e82a3e0feff4ffebdd78f4804717c8f64045c080c"
"captureGroups": 11,
"digest": "5bba21d0f792eb98025ce0531c575dac215492027cd877df09b19877e80da4ad"
},
"rust-for-call-expr/src/main.rs": {
"captureGroups": 24,
@ -208,8 +208,8 @@
"digest": "7331591b986aa2a9b0ee57020c1f0285ad0da10d5044c326b4beaaeb26019aa8"
},
"rust-for-loop/src/main.rs": {
"captureGroups": 24,
"digest": "9622a9b47737283123a93511231529524ec82a54c5a97c17dc81dcc6e8f4912c"
"captureGroups": 26,
"digest": "1f80bd8d194a6e0e843e0a2a76e910d25bab0712bd9208f69c045b6be25cbaed"
},
"rust-for-loop/src/repo.rs": {
"captureGroups": 9,
@ -224,8 +224,8 @@
"digest": "494ee20e8d5ebff07a8358938b706e15a48c9a9fb21e0ce361c2a8c391080352"
},
"rust-grouped-imports/src/helpers/mod.rs": {
"captureGroups": 14,
"digest": "32bc4f57a1dc0ffe8e9cbf0f0d6ce2ac5b1f2f93e2e3de3f217c92636480de63"
"captureGroups": 16,
"digest": "4363614087aacd43e98bef6c90e33a49edf7a4c4bee2a20a9e1dd8301c84310a"
},
"rust-grouped-imports/src/main.rs": {
"captureGroups": 14,
@ -244,8 +244,8 @@
"digest": "abf2026b72dacf36eca93895d2601c900424f30ff7f59f904f0dcf1fa377829c"
},
"rust-if-let-unwrap/src/main.rs": {
"captureGroups": 15,
"digest": "3a45c99bbfc3ba2117d333127b3031697623bb044a1f04ce5381ff1366fe7468"
"captureGroups": 16,
"digest": "fa486e6573e6f8372fdff4ffbce6c726e354f1961f27e4f91be5aa0ab49b0496"
},
"rust-if-let-unwrap/src/repo.rs": {
"captureGroups": 9,
@ -264,8 +264,8 @@
"digest": "d8c1eb57431b915dd5c9055d8451054a454e340c4842628e38e4d46f69471abd"
},
"rust-iter-for-loop/src/main.rs": {
"captureGroups": 28,
"digest": "7443b6c9231e54de5afd5f6fb52fd0a9709299a650615119a5be68de43287a88"
"captureGroups": 30,
"digest": "529fda7f9f188814ce6044e2c99d6b005ec4a9b98835fe9609530897998b9f32"
},
"rust-iter-for-loop/src/repo.rs": {
"captureGroups": 9,
@ -276,20 +276,20 @@
"digest": "a8562a331eb945b4c099a7a9ec6c9b5eed8ff603c9359897b0445ce316a1424e"
},
"rust-local-shadow/src/main.rs": {
"captureGroups": 16,
"digest": "66d3a2678fb33301bbaa419684d208a0dc5c914d6e3bcf20e1c99e9666ed6fa2"
"captureGroups": 17,
"digest": "fa630d884c95e92a2d9c9b372f88e752a24aa4feb8089c3e43726793f3d12188"
},
"rust-local-shadow/src/utils.rs": {
"captureGroups": 6,
"digest": "493dbeab554e28bed4a03cb62b951cad4f43bce5b5b522a192cd04159b3b2d9c"
"captureGroups": 7,
"digest": "bd341f6eb3527abdd63b3ff6a337a502858c72d75623f78a4e46646ba51d5915"
},
"rust-macro/lib.rs": {
"captureGroups": 11,
"digest": "19b650be99256aa211356edc5a3dde83aff21e5d763e2c079322a24045bf69c9"
},
"rust-match-unwrap/src/main.rs": {
"captureGroups": 24,
"digest": "1d9ead4663799e035816ed06a17d37e9a98d0fd8090f9dea256ec6f019788648"
"captureGroups": 26,
"digest": "ffec7f4401caefc75df450004d4b0828b053799dcbf7247ff2b431ea6ac5a46b"
},
"rust-match-unwrap/src/repo.rs": {
"captureGroups": 9,
@ -300,24 +300,24 @@
"digest": "a8562a331eb945b4c099a7a9ec6c9b5eed8ff603c9359897b0445ce316a1424e"
},
"rust-member-calls/src/main.rs": {
"captureGroups": 14,
"digest": "9de5ab8411808df29a924b2e854048c6babb1a09b996279aba5c3f7916d03ae0"
"captureGroups": 15,
"digest": "2ef8b5f8ad187b1476af8ceff54051dcf9aa71236aa2b7485a28b3ff97e58e1e"
},
"rust-member-calls/src/user.rs": {
"captureGroups": 10,
"digest": "a5545efb669e40adf8428af48057cd93bd31c226f7d0952d4dae76e1ee5228f7"
},
"rust-method-chain-binding/src/main.rs": {
"captureGroups": 17,
"digest": "81b41ca25c672098dadbd7911c5a981706035b1bdca83eb2ca5f6bdbf759ab6b"
"captureGroups": 18,
"digest": "b4767bb9c5e6856309d04e0a3dff90940d391a64b7a6f7efa9109d419af6891a"
},
"rust-method-chain-binding/src/models.rs": {
"captureGroups": 34,
"digest": "cd836a2a9c15ab240961d2e15f192f7e33d65eb5ebf2e1a8af2f620a47fe66ae"
},
"rust-method-enrichment/src/lib.rs": {
"captureGroups": 39,
"digest": "8e60a44f5e18d1e26eea4bb21129494d2e3be697fec373dd4b6044f989176d9e"
"captureGroups": 40,
"digest": "71627a8218e32514b6945e4e310686eb631ce37451c90c9644e3f5336a37820b"
},
"rust-method-enrichment/src/main.rs": {
"captureGroups": 18,
@ -332,8 +332,8 @@
"digest": "2fc1fe1eb4e8727a89ab283ae34a0ae8df0c421551a7bd5e6e7ffb9d4aa54189"
},
"rust-nullable-receiver/src/main.rs": {
"captureGroups": 37,
"digest": "283d8606eb837f2a9e5fdf95a30e3da5b73d4c14d74b94deb03e924dd2b2fde1"
"captureGroups": 39,
"digest": "2fdecb2bce41738daf30b86b8b145b4ca3438e72387ab25e5b3ba7a5af2c1090"
},
"rust-nullable-receiver/src/repo.rs": {
"captureGroups": 10,
@ -344,8 +344,8 @@
"digest": "a5545efb669e40adf8428af48057cd93bd31c226f7d0952d4dae76e1ee5228f7"
},
"rust-option-receiver/src/main.rs": {
"captureGroups": 24,
"digest": "02298528e6fd679850ecaf6ed06017d29b7cac0f55ba2308692c87bd8904f040"
"captureGroups": 27,
"digest": "497145ee37b17cf2eacb3fab98b71b3a04162d011d1e887c588fb9f77d4283be"
},
"rust-option-receiver/src/repo.rs": {
"captureGroups": 8,
@ -380,8 +380,8 @@
"digest": "ee34385539f7e9398123c056738c6a662a80dac41fc038db96fab0da5c84c8ac"
},
"rust-receiver-resolution/src/main.rs": {
"captureGroups": 21,
"digest": "283b9d58dc76d8f48ab2fce4598f5173910f8c59f27c2aa9c1d80a3c86878be8"
"captureGroups": 23,
"digest": "89d03ee72c223fde12b407ff50ae512dedb1a63aa4239e6f5dd9a54949566786"
},
"rust-receiver-resolution/src/repo.rs": {
"captureGroups": 10,
@ -416,8 +416,8 @@
"digest": "2ab4961fb869cf40db597b422b24c7ba7a7b63d97487cea7013e065a32e497f3"
},
"rust-return-type/src/models.rs": {
"captureGroups": 17,
"digest": "0e3826200f2e6f5b948313369e85ee3a08f18bc8d51fbd1f7283b19a8e01aac8"
"captureGroups": 18,
"digest": "c589e22d34215100a8e69f0c0a3a801814203edfb28d46a0342d1b1f813af06b"
},
"rust-scoped-impl/lib.rs": {
"captureGroups": 17,
@ -432,12 +432,12 @@
"digest": "7346b2cf62e4946b261ed0eb46f2623fb1882f46d832abdc2068012917c7b16a"
},
"rust-scoped-multi-file/src/models/repo.rs": {
"captureGroups": 19,
"digest": "25a3d44bc451ccaf8c6875b226fbda22ed1a18dcc884d4c6d9914ed83fc555b0"
"captureGroups": 20,
"digest": "9c41e8dea4bec804dc08ebb2af3ee06f6cbed2d4e72217331f4a42dd227ea6a9"
},
"rust-scoped-multi-file/src/models/user.rs": {
"captureGroups": 19,
"digest": "77dc9858229322d8e1abbf209230bfbf6fca00627a5864888e91f1841b1f0cbb"
"captureGroups": 20,
"digest": "ab12913053296035ad20ebd2af9f2f9988f8b89d32af41f3915f8706f00fa600"
},
"rust-self-struct-literal/main.rs": {
"captureGroups": 11,
@ -456,8 +456,8 @@
"digest": "9354dad2a222a66b46f00ceede6777ba8c0634294c41313f393bc9ebcbbb6aa7"
},
"rust-struct-destructuring/main.rs": {
"captureGroups": 15,
"digest": "6d96109296b7f5bd709e2704c14c332bfa0bca12453ae289b38a74da2c38124d"
"captureGroups": 17,
"digest": "79ce4f3fa933214532c96161865231c51c5505773794e0c87d5c995a2684cd4b"
},
"rust-struct-destructuring/point.rs": {
"captureGroups": 6,
@ -476,16 +476,16 @@
"digest": "dedd93d6214ff00ee0ee267140918403b7f59e0ab010cb9058af40cb3b08bb81"
},
"rust-struct-literals/app.rs": {
"captureGroups": 12,
"digest": "46c3ea5d181fc61b6ea891adf439310ca9f941c0846a6a62c3a9ae9ac0c112f1"
"captureGroups": 13,
"digest": "2a1a44788e40cf855c235849c932b4e82f6551a7b5e747e1f6f91b250ead6ec4"
},
"rust-struct-literals/user.rs": {
"captureGroups": 11,
"digest": "60fc4ac44f58ae67d462e243655b0571392a18de85b9e200e9391b50c941a6c9"
},
"rust-traits/src/impls/button.rs": {
"captureGroups": 34,
"digest": "80fc76cdf20e3e0594a1ed933ce25682519151632157d18ef011dba60a2245d1"
"captureGroups": 36,
"digest": "30b0e4e92385e7f7eb83ad97e78ed65b0eced81bbcb20bf52a846924c75ab5bb"
},
"rust-traits/src/main.rs": {
"captureGroups": 11,
@ -508,11 +508,11 @@
"digest": "660f755fd70cd1796f9da02ad7d65f599dea8029665ee45ecd18cd27919741f3"
},
"rust-write-access/service.rs": {
"captureGroups": 16,
"digest": "7edc72e15b18ec6cf0b48a592ccb3570f46ac25463640fc6a97d71fa1795b643"
"captureGroups": 17,
"digest": "4c5055277bdbb762af4f58623c3aba323c9618e900ca731ead19171d4a3a9495"
},
"synthetic:dao-20": {
"captureGroups": 381,
"digest": "4b0262ff8d728d50f3f22772a1691001ee362936f7dd5395d458539cd6ca3411"
"captureGroups": 421,
"digest": "5ec55a7a5a07266b385669e663d1438c0ad223c5eb521a74f77b5b03840d2892"
}
}

View file

@ -1,27 +1,27 @@
{
"swift-abstract-dispatch/Sources/App.swift": {
"captureGroups": 9,
"digest": "56b82214cca3ed89312d84d6b04a48ee13cbe748ea6978e9bffa20ce46ac0930"
"captureGroups": 10,
"digest": "0ec4bb27184ce68dca2ef6bef58c23353b363be9aa483d0bbea5321bccf85ae2"
},
"swift-abstract-dispatch/Sources/Repository.swift": {
"captureGroups": 24,
"digest": "3a3fa6017b6937de81e5f70bcab727910edb00053c7e5bd30da67ed1fb84c71f"
"captureGroups": 26,
"digest": "36d177980181b97f016d834a9a57b7e559e61558a9f4e1c45809604886b18aa7"
},
"swift-await-try/App.swift": {
"captureGroups": 13,
"digest": "7ba2042b0cb0a6e5c5d459dc460c509ace477b0ce97bec0cc8aaf981f320e69a"
},
"swift-await-try/Models.swift": {
"captureGroups": 20,
"digest": "59f0a1aaa2884d12ff60c615cf73b3385894afba7b2d3d0cda74ff8648802d0b"
"captureGroups": 21,
"digest": "eb9e2b04b9dd55d161c58b5004adde0f881a138b790985fd523e110b87035ffd"
},
"swift-call-result-binding/App.swift": {
"captureGroups": 7,
"digest": "979534a46ee420a7a5dfd6b029c9fe5d67ca1b00f3bdc5e8977d72a1c316b75d"
},
"swift-call-result-binding/Models.swift": {
"captureGroups": 14,
"digest": "177c25e87bb0507385cb1da50ebb08305c83c62af637311fad53928ba1d491bc"
"captureGroups": 15,
"digest": "7f068c35624ff48635c1f51ec79e871511200a044eb4920bedd6673c4f690911"
},
"swift-child-extends-parent/Sources/App.swift": {
"captureGroups": 10,
@ -36,8 +36,8 @@
"digest": "ee1086e1a0cbfc57f381401b81b2e150f422a64cd3b937f083a1fbbd85e5a27e"
},
"swift-class-func-receiver/Service.swift": {
"captureGroups": 24,
"digest": "04431e0287afed4c37779fb8fbf1d8cbe00ff917d6512fefcec5d7da1db69de8"
"captureGroups": 27,
"digest": "6e9c6bd2ebb65624a5079a1ce18b5b8c6a73af5e3216580bb558f0e22a0ec689"
},
"swift-constructor-fallback/App.swift": {
"captureGroups": 7,
@ -48,12 +48,12 @@
"digest": "685538adb8aede40f7969572a4c8ba3d491413de22798e109800a121d8d88223"
},
"swift-constructor-type-inference/Models/Repo.swift": {
"captureGroups": 14,
"digest": "a1e1b75aa5a637bdf1e47ffd5cea61b2f6b139078db766d4926d0d99086abe34"
"captureGroups": 15,
"digest": "9f932b70d43c83b403a456b5854586f29238cfb573dff7e1183aea20d7e7f18d"
},
"swift-constructor-type-inference/Models/User.swift": {
"captureGroups": 14,
"digest": "107563b39540473845aeafc30aae96191eae60ba56bed8d8b13dd98aaef72f0a"
"captureGroups": 15,
"digest": "1fcd2298bb1b7d0d6ff42248cedd66d31a2e3e0de8645162ee3eb31924ac9987"
},
"swift-constructor-type-inference/Services/App.swift": {
"captureGroups": 12,
@ -76,16 +76,16 @@
"digest": "f7062b53f33ec548a2ba5836ef5507b4c662d74ac4935dc654e6f95081f6fcbd"
},
"swift-extension-dedup/Product.swift": {
"captureGroups": 14,
"digest": "86c0c65ace3809f2f13cca1dabe54328c354923cde0e29e526542a9291a66522"
"captureGroups": 15,
"digest": "81ef7e833c46fdedfd8f632fe0918b768e88d4490e886a763760f26549346009"
},
"swift-extension-dedup/ProductExtensions.swift": {
"captureGroups": 8,
"digest": "1e072eb427bb17ea1a225bcc85e0defe74297e3e0ae05c7dc9c04a7e870b0508"
},
"swift-field-types/App.swift": {
"captureGroups": 6,
"digest": "2cb023f94129e0bd07e1116892bad2597eb0f3aa02079deb029d5e3604311999"
"captureGroups": 7,
"digest": "651c5cbbf9afdc0bf4d96217bc1a01cf0bf07a0190ca12a3a6786bb08a67b3ad"
},
"swift-field-types/Models.swift": {
"captureGroups": 20,
@ -116,24 +116,24 @@
"digest": "9d0a738f1fdd31c1e204b20f1ccc76428f9f300eed82191da63f70799ff18f09"
},
"swift-init-cross-file/User.swift": {
"captureGroups": 17,
"digest": "238af425526a62a088a1c4b3ba75c9249e375ffbeedb991c1b7481fa20e11543"
"captureGroups": 18,
"digest": "4e7df3b46d39126ab8d6a21a6a4103453f38e1b8a2fe159447abaa85381b6295"
},
"swift-init-cross-file/main.swift": {
"captureGroups": 8,
"digest": "3007e4849cf77cc6cb360a1dbf70abec414a408076020f4730d01b8a80c1ddd3"
},
"swift-member-write-access/App.swift": {
"captureGroups": 11,
"digest": "43c147d979761375f88bccd980f9db2274a937a3157ecaf93ca337fa54d0ae39"
"captureGroups": 14,
"digest": "f3c28a41c270cea237598bd20c688ba6ab791e3701e85c66bfe3aff7ecfb1ec8"
},
"swift-member-write-access/Models.swift": {
"captureGroups": 23,
"digest": "e830e3dca7d6c6260181b78d4af5bc61e031bdce3a451588989f17223a5eac1c"
"captureGroups": 26,
"digest": "25be33df18e7eb8fcb9005c8a963205a3d93f37d0950ca187e9caf569fc5dfa4"
},
"swift-method-enrichment/Sources/Animal.swift": {
"captureGroups": 23,
"digest": "999c14cd327ad7e1cc90bae25e9c7e79a8ade5dd274d834d87a61846c4c91259"
"captureGroups": 24,
"digest": "a49732a1d0fdf5d33b1f0c321a0413163d5af3a353b83510dae5d801109788c5"
},
"swift-method-enrichment/Sources/App.swift": {
"captureGroups": 10,
@ -188,8 +188,8 @@
"digest": "b5ed80965325806c715d1c3a65163b014e0bbba5ca7e2df9d7c81b084fada15e"
},
"swift-overload-dispatch/SqlRepository.swift": {
"captureGroups": 23,
"digest": "e5abcf7249afe3fb58fad54ec17da29195a95a05e6f1275375b9dc395030d9dc"
"captureGroups": 28,
"digest": "40caf38a1bbe958294ffaacb921f1ef08131445ed959f549fe9be86ea0cdb289"
},
"swift-parent-resolution/Sources/Models/BaseModel.swift": {
"captureGroups": 7,
@ -220,16 +220,16 @@
"digest": "ec0a14090e0240bdd52a955cf7c95c2bc994085ca1482911dd882c5cdaa17c81"
},
"swift-return-type-inference/Models.swift": {
"captureGroups": 27,
"digest": "3d1a5a4fe1deb91dc2ba74f71e7bc0b42dbe1dde3b374891fbb3fcf8d265f3a8"
"captureGroups": 29,
"digest": "fd0c6c7970e237bed4a305a84ee33d00dbc7c06d97c2e076911ae6ca741cad98"
},
"swift-return-type/App.swift": {
"captureGroups": 7,
"digest": "979534a46ee420a7a5dfd6b029c9fe5d67ca1b00f3bdc5e8977d72a1c316b75d"
},
"swift-return-type/Models.swift": {
"captureGroups": 18,
"digest": "dcb5562e378f8672c85ade10d2dc38b499af2cc02efeb4c0f80ac8ce83ce7d02"
"captureGroups": 21,
"digest": "d92ce7a1ec19c86a1848fa1d72d151c8059714c9129b70e8d260135a6a4a4993"
},
"swift-self-this-resolution/Sources/Models/Repo.swift": {
"captureGroups": 7,
@ -240,7 +240,7 @@
"digest": "c0c8e583896b1d9c889b43682d53b0d6b8194f54a27ae16a4d89a5ad79354fdc"
},
"synthetic:dao-20": {
"captureGroups": 321,
"digest": "abd793dd5a0196a9795ff62cee1b1b0905e69ea45092b6efd0decdc9bc84270c"
"captureGroups": 361,
"digest": "d38aec3ca6fad59b943b3515691adc169dc4b40b70711139e9f0301be844e6fa"
}
}

View file

@ -0,0 +1,74 @@
/**
* Literal-validation gate for `*_CALLABLE_CAPTURE_OPTIONS` blocks (#2522
* review): the #1920 gate validates tree-sitter QUERY literals and exported
* config objects, but the callable-flow option Sets are module-private
* object-literal properties consumed via the shared synthesizer — a typo'd
* node type there silently captures nothing (PHP shipped a dead
* 'optional_parameter'). This gate extracts every `<key>NodeTypes: new
* Set([...])` literal from each language's captures.ts and validates it
* against that language's grammar.
*/
import { describe, it, expect } from 'vitest';
import { readFileSync, existsSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { SupportedLanguages } from '../../src/config/supported-languages.js';
import { loadGrammarModel, validateNodeType } from '../helpers/grammar-introspection.js';
const LANGUAGES_DIR = join(
dirname(fileURLToPath(import.meta.url)),
'..',
'..',
'src',
'core',
'ingestion',
'languages',
);
interface OptionLiteral {
readonly language: SupportedLanguages;
readonly key: string;
readonly literal: string;
}
function collectOptionLiterals(): OptionLiteral[] {
const out: OptionLiteral[] = [];
for (const language of Object.values(SupportedLanguages)) {
const file = join(LANGUAGES_DIR, language, 'captures.ts');
if (!existsSync(file)) continue;
const source = readFileSync(file, 'utf8');
const block = source.match(/_CALLABLE_CAPTURE_OPTIONS\s*=\s*\{([\s\S]*?)\n\}\s*as const/);
if (block === null) continue;
// Only keys whose name says they hold tree-sitter NODE TYPES —
// callableProtocolMethods (method names) and memberPointerOperators
// (operator tokens) are deliberately outside this contract.
const setRegex = /(\w+NodeTypes)\??:\s*new Set(?:<string>)?\(\[([\s\S]*?)\]\)/g;
for (const match of block[1]!.matchAll(setRegex)) {
for (const entry of match[2]!.matchAll(/'([^']+)'/g)) {
out.push({ language, key: match[1]!, literal: entry[1]! });
}
}
}
return out;
}
describe('callable-capture-options literal gate (#2522)', () => {
it('collects a plausible literal surface', () => {
const literals = collectOptionLiterals();
expect(literals.length).toBeGreaterThan(50);
expect(literals.map((l) => l.language)).toContain(SupportedLanguages.JavaScript);
});
it('every node-type literal in a *_CALLABLE_CAPTURE_OPTIONS Set exists in its grammar', () => {
const literals = collectOptionLiterals();
const models = new Map<SupportedLanguages, ReturnType<typeof loadGrammarModel>>();
const dead: string[] = [];
for (const { language, key, literal } of literals) {
if (!models.has(language)) models.set(language, loadGrammarModel(language));
const verdict = validateNodeType(language, models.get(language) ?? null, literal);
if (verdict === 'dead') dead.push(`${language}: ${key} → '${literal}'`);
}
expect(dead).toEqual([]);
});
});

View file

@ -8,6 +8,7 @@ import { getProvider } from '../../../src/core/ingestion/languages/index.js';
import { SupportedLanguages } from '../../../src/config/supported-languages.js';
import type { CfgVisitor } from '../../../src/core/ingestion/cfg/types.js';
import type { SyntaxNode } from '../../../src/core/ingestion/utils/ast-helpers.js';
import { extractParsedFile } from '../../../src/core/ingestion/scope-extractor-bridge.js';
// U3 — the worker→main boundary + cache coherence for the CFG side-channel.
// These pin the contracts that make the disk-store + warm/durable parse cache
@ -148,6 +149,48 @@ describe('U3 — CFG side-channel JSON round-trip (no AST leakage, no field loss
});
});
describe('callable-flow ParsedFile round-trip', () => {
it('preserves discriminants, scopes, ranges, indexes, modes, and invocation positions', () => {
const provider = getProvider(SupportedLanguages.TypeScript);
const parsed = extractParsedFile(
provider,
`
function target(): void {}
function invoke(cb: () => void): void { cb(); }
const first = target;
invoke(first);
`,
'callable.ts',
() => {},
);
expect(parsed.callableFlowSites?.map((site) => site.kind)).toEqual([
'formal',
'invoke',
'seed',
'argument',
]);
const round = JSON.parse(JSON.stringify(parsed, mapReplacer), mapReviver);
expect(round.callableFlowSites).toEqual(parsed.callableFlowSites);
expect(round.callableFlowSites[0]).toMatchObject({
kind: 'formal',
parameterIndex: 0,
passingMode: 'value',
binding: { name: 'cb', addressOf: false, indirection: 0 },
});
expect(round.callableFlowSites[1]).toMatchObject({
kind: 'invoke',
callee: { name: 'cb' },
callSite: { startLine: 3 },
});
expect(round.callableFlowSites[3]).toMatchObject({
kind: 'argument',
directCalleeName: 'invoke',
parameterIndex: 0,
});
});
});
describe('U3 — parse-cache key folds the --pdg flag (R4, #2038-class guard)', () => {
const entries = [
{ filePath: 'b.ts', contentHash: 'h2' },

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,359 @@
/**
* TypeScript/JavaScript: value-position function references (#2437).
*
* Registration (`{ emitScopeCaptures: emitHook }`, shorthand `{ emitHook }`)
* emits a reference-class USES edge — a registration is not an invocation.
* Invocation is recovered by the field-based property-dispatch pass:
* member-call sites `x.emitScopeCaptures()` gain synthesized CALLS edges
* (reason `property-dispatch`, discounted confidence) to every function
* registered under that property name. Non-callable values emit nothing
* (resolution is MethodRegistry-gated), and promiscuous keys past the
* fan-out cap are dropped entirely.
*/
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import path from 'path';
import fs from 'node:fs';
import os from 'node:os';
import {
getRelationships,
runPipelineFromRepo,
type PipelineResult,
type RelEdge,
} from './helpers.js';
import {
MAX_PROPERTY_DISPATCH_FANOUT,
PROPERTY_DISPATCH_CONFIDENCE,
} from '../../../src/core/ingestion/scope-resolution/passes/property-dispatch.js';
import { _captureLogger, type PinoLogRecord } from '../../../src/core/logger.js';
function writeFixtureRepo(root: string, files: Record<string, string>): void {
for (const [relPath, content] of Object.entries(files)) {
const fullPath = path.join(root, relPath);
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
fs.writeFileSync(fullPath, content, 'utf8');
}
}
function edgesFrom(edges: RelEdge[], sourceFile: string): RelEdge[] {
return edges.filter((c) => c.sourceFilePath === sourceFile);
}
const VALUE_REF_REASON = 'scope-resolution: value-ref';
const DISPATCH_REASON = 'property-dispatch';
describe('value-position function references (#2437)', () => {
let repoDir: string;
let result: PipelineResult;
let calls: RelEdge[];
let uses: RelEdge[];
let logRecords: PinoLogRecord[];
beforeAll(async () => {
repoDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gn-ts-value-refs-'));
const cappedFunctions = Array.from(
{ length: MAX_PROPERTY_DISPATCH_FANOUT + 1 },
(_, i) =>
`export function capped${i}(): void {}\nexport const holder${i} = { common: capped${i} };`,
).join('\n');
writeFixtureRepo(repoDir, {
// Same-file provider object: hook value, plain value, factory call.
'src/provider.ts': `
export function emitHook(): void {}
export const DEFAULT_PORT = 8080;
export function createVisitor(): () => void {
return () => {};
}
export function targetCallback(): void {}
export function invokeCallback(callback: () => void): void { callback(); }
export const provider = {
emitScopeCaptures: emitHook,
runWithCallback: invokeCallback,
port: DEFAULT_PORT,
cfgVisitor: createVisitor(),
};
`,
// Dispatch through the property — the scope-extractor-bridge shape.
'src/bridge.ts': `
import { provider, targetCallback } from './provider';
export function runBridge(): void {
provider.emitScopeCaptures();
provider.runWithCallback(targetCallback);
provider.toString();
}
`,
// Cross-file registration: the c-cpp.ts shape.
'src/impl.ts': `
export function emitCrossHook(): void {}
`,
'src/registry.ts': `
import { emitCrossHook } from './impl';
export const registry = { emitScopeCaptures: emitCrossHook };
`,
// Aliased import as pair value.
'src/alias-registry.ts': `
import { emitCrossHook as hookImpl } from './impl';
export const aliasRegistry = { emitScopeCaptures: hookImpl };
`,
// Shorthand registration + dispatch through it.
'src/shorthand.ts': `
import { emitCrossHook } from './impl';
export const shorthandRegistry = { emitCrossHook };
`,
'src/dispatch-shorthand.ts': `
import { shorthandRegistry } from './shorthand';
export function runShorthand(): void {
shorthandRegistry.emitCrossHook();
}
`,
// Destructuring shorthand must NOT produce a value-ref.
'src/destructure.ts': `
export function pickHook(): void {}
const holder = { pickHook };
const { pickHook: picked } = holder;
export const keep = picked;
`,
// Fan-out cap: one key registered by MAX+1 distinct functions.
'src/capped.ts': `
${cappedFunctions}
`,
'src/dispatch-capped.ts': `
import { holder0 } from './capped';
export function runCap(): void {
holder0.common();
}
`,
// JavaScript twins (separate query file, same patterns).
'src/js/provider.js': `
export function emitJsHook() {}
export const DEFAULT_LIMIT = 5;
export const jsProvider = { emitScopeCaptures: emitJsHook, limit: DEFAULT_LIMIT };
`,
'src/js/shorthand.js': `
import { emitJsHook } from './provider';
export const jsShorthand = { emitJsHook };
`,
'src/js/bridge.js': `
import { jsProvider } from './provider';
export function runJsBridge() {
jsProvider.emitScopeCaptures();
}
`,
});
const loggerCapture = _captureLogger();
try {
result = await runPipelineFromRepo(repoDir, () => {}, {});
logRecords = loggerCapture.records();
} finally {
loggerCapture.restore();
}
calls = getRelationships(result, 'CALLS');
uses = getRelationships(result, 'USES');
}, 120000);
afterAll(() => {
if (repoDir !== undefined) fs.rmSync(repoDir, { recursive: true, force: true });
});
// ── Registration → USES ──────────────────────────────────────────────────
it('emits USES from the provider file to a same-file longhand hook value', () => {
const edge = edgesFrom(uses, 'src/provider.ts').find((c) => c.target === 'emitHook');
expect(edge).toBeDefined();
expect(edge!.sourceLabel).toBe('File');
expect(edge!.rel.reason).toBe(VALUE_REF_REASON);
});
it('does not emit CALLS at the registration site', () => {
expect(
edgesFrom(calls, 'src/provider.ts').find(
(c) => c.target === 'emitHook' && c.rel.reason === VALUE_REF_REASON,
),
).toBe(undefined);
});
it('emits nothing for a non-callable pair value', () => {
expect(edgesFrom(calls, 'src/provider.ts').find((c) => c.target === 'DEFAULT_PORT')).toBe(
undefined,
);
expect(
edgesFrom(uses, 'src/provider.ts').find(
(c) => c.target === 'DEFAULT_PORT' && c.rel.reason === VALUE_REF_REASON,
),
).toBe(undefined);
});
it('keeps a factory-call pair value as a single call-site CALLS edge', () => {
const factory = edgesFrom(calls, 'src/provider.ts').filter((c) => c.target === 'createVisitor');
expect(factory).toHaveLength(1);
expect(factory[0]!.rel.reason).not.toBe(DISPATCH_REASON);
});
it('emits USES across files for an imported hook (the c-cpp.ts shape)', () => {
const edge = edgesFrom(uses, 'src/registry.ts').find((c) => c.target === 'emitCrossHook');
expect(edge).toBeDefined();
expect(edge!.targetFilePath).toBe('src/impl.ts');
expect(edge!.rel.reason).toBe(VALUE_REF_REASON);
});
it('resolves an aliased import pair value to the original definition', () => {
const edge = edgesFrom(uses, 'src/alias-registry.ts').find((c) => c.target === 'emitCrossHook');
expect(edge).toBeDefined();
expect(edge!.targetFilePath).toBe('src/impl.ts');
});
it('emits USES for a shorthand property referencing an imported hook', () => {
const edge = edgesFrom(uses, 'src/shorthand.ts').find((c) => c.target === 'emitCrossHook');
expect(edge).toBeDefined();
expect(edge!.targetFilePath).toBe('src/impl.ts');
});
it('emits exactly one value-ref USES per site (object shorthand), none for destructuring', () => {
const destructure = edgesFrom(uses, 'src/destructure.ts').filter(
(c) => c.target === 'pickHook' && c.rel.reason === VALUE_REF_REASON,
);
expect(destructure).toHaveLength(1);
});
// ── Dispatch → synthesized CALLS ─────────────────────────────────────────
it('synthesizes CALLS from the dispatch site to the registered hook', () => {
const edge = calls.find(
(c) =>
c.source === 'runBridge' && c.target === 'emitHook' && c.rel.reason === DISPATCH_REASON,
);
expect(edge).toBeDefined();
expect(edge!.targetFilePath).toBe('src/provider.ts');
expect(edge!.rel.confidence).toBe(PROPERTY_DISPATCH_CONFIDENCE);
});
it('propagates a callback through a property-dispatched wrapper before indirect resolution', () => {
const edge = calls.find(
(candidate) =>
candidate.source === 'invokeCallback' &&
candidate.target === 'targetCallback' &&
candidate.rel.reason === 'callable-value-flow',
);
expect(edge).toBeDefined();
expect(edge!.sourceFilePath).toBe('src/provider.ts');
expect(edge!.targetFilePath).toBe('src/provider.ts');
});
it('dispatch fans out to every same-language registration of the property name', () => {
// `emitScopeCaptures` is registered in TypeScript by emitHook
// (provider.ts) and emitCrossHook (registry.ts + alias-registry.ts,
// deduped by def) — a TS call through the key reaches both. emitJsHook
// is NOT reached: scope resolution runs per language provider, so JS
// registrations are visible only to JS dispatch sites (see runJsBridge).
const targets = calls
.filter((c) => c.source === 'runBridge' && c.rel.reason === DISPATCH_REASON)
.map((c) => c.target)
.sort();
expect(targets).toEqual(['emitCrossHook', 'emitHook', 'invokeCallback']);
});
it('dispatches through a shorthand-registered key', () => {
const edge = calls.find(
(c) =>
c.source === 'runShorthand' &&
c.target === 'emitCrossHook' &&
c.rel.reason === DISPATCH_REASON,
);
expect(edge).toBeDefined();
});
it('does not synthesize CALLS for unregistered member calls', () => {
expect(calls.find((c) => c.target === 'toString' && c.rel.reason === DISPATCH_REASON)).toBe(
undefined,
);
});
it('drops keys past the fan-out cap entirely', () => {
const cappedDispatch = calls.filter(
(c) => c.rel.reason === DISPATCH_REASON && c.target.startsWith('capped'),
);
expect(cappedDispatch).toEqual([]);
const capWarnings = logRecords.filter(
(record) =>
record.msg ===
'property-dispatch: keys over the fan-out cap were dropped (no CALLS synthesized for them)',
);
expect(capWarnings).toHaveLength(1);
expect(capWarnings[0]).toMatchObject({
level: 40,
lang: 'typescript',
skippedKeys: 1,
fanoutCap: MAX_PROPERTY_DISPATCH_FANOUT,
});
});
it('every property-dispatch edge targets a registered hook', () => {
const dispatchTargets = new Set(
calls.filter((c) => c.rel.reason === DISPATCH_REASON).map((c) => c.target),
);
expect([...dispatchTargets].sort()).toEqual([
'emitCrossHook',
'emitHook',
'emitJsHook',
'invokeCallback',
]);
});
// ── JavaScript twins ─────────────────────────────────────────────────────
it('emits USES for JavaScript longhand and shorthand hook values', () => {
const longhand = edgesFrom(uses, 'src/js/provider.js').find((c) => c.target === 'emitJsHook');
expect(longhand).toBeDefined();
expect(longhand!.rel.reason).toBe(VALUE_REF_REASON);
const shorthand = edgesFrom(uses, 'src/js/shorthand.js').find((c) => c.target === 'emitJsHook');
expect(shorthand).toBeDefined();
});
it('synthesizes CALLS from a JavaScript dispatch site', () => {
const edge = calls.find(
(c) =>
c.source === 'runJsBridge' && c.target === 'emitJsHook' && c.rel.reason === DISPATCH_REASON,
);
expect(edge).toBeDefined();
});
it('emits nothing for a non-callable JavaScript pair value', () => {
expect(edgesFrom(calls, 'src/js/provider.js').find((c) => c.target === 'DEFAULT_LIMIT')).toBe(
undefined,
);
});
});
// ---------------------------------------------------------------------------
// Declarator-named arrows: `const handler = () => {}` referenced as a pair
// value. Pins whether the arrow def is callable-labeled (USES emitted) —
// kept as its own suite so the pinned behavior is visible in isolation.
// ---------------------------------------------------------------------------
describe('value-position reference to a const-arrow handler (#2437)', () => {
let repoDir: string;
let result: PipelineResult;
beforeAll(async () => {
repoDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gn-ts-value-refs-arrow-'));
writeFixtureRepo(repoDir, {
'src/arrow.ts': `
const handler = (): void => {};
export const arrowRegistry = { onEvent: handler };
`,
});
result = await runPipelineFromRepo(repoDir, () => {}, {});
}, 120000);
afterAll(() => {
if (repoDir !== undefined) fs.rmSync(repoDir, { recursive: true, force: true });
});
it('emits USES to the arrow function definition', () => {
const edge = getRelationships(result, 'USES').find(
(c) => c.sourceFilePath === 'src/arrow.ts' && c.target === 'handler',
);
expect(edge).toBeDefined();
expect(edge!.rel.reason).toBe(VALUE_REF_REASON);
});
});

View file

@ -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 6 (uniform 0-based line storage re-index window)', () => {
expect(INCREMENTAL_SCHEMA_VERSION).toBe(6);
it('INCREMENTAL_SCHEMA_VERSION is bumped to 7 (callable-value-flow edges re-index window)', () => {
expect(INCREMENTAL_SCHEMA_VERSION).toBe(7);
});
it('a pre-current stamp fails the `=== INCREMENTAL_SCHEMA_VERSION` reuse gate → forces full re-analyze', () => {
@ -95,7 +95,11 @@ describe('CALL_SUMMARY incremental reuse gate (U-C5)', () => {
// COBOL/JCL/markdown/scope rows are still 1-based, so an incremental top-up
// would mix bases → must NOT reuse.
expect(passesReuseGate(5)).toBe(false);
// A pre-v7 (v6) index predates the callable-value-flow edges (#2437/#2522)
// — new edges between unchanged files would never enter the incremental
// write set → must NOT reuse.
expect(passesReuseGate(6)).toBe(false);
// A current-version stamp passes the gate (incremental top-up eligible).
expect(passesReuseGate(6)).toBe(true);
expect(passesReuseGate(7)).toBe(true);
});
});

View file

@ -40,6 +40,19 @@ const makeParsedFile = (filePath: string): ParsedFile =>
],
}) as unknown as ParsedFile;
/**
* Store payload with arbitrary (possibly corrupt) field overrides. The one
* controlled escape hatch for building malformed serialization-boundary
* fixtures lives HERE instead of double-casts scattered through the tests
* (#2522 review).
*/
function makeStoreEntry(filePath: string, overrides: Record<string, unknown>): ParsedFile {
return {
...(makeParsedFile(filePath) as unknown as Record<string, unknown>),
...overrides,
} as unknown as ParsedFile;
}
describe('parsedfile-store', () => {
it('round-trips ParsedFiles (incl. Scope Maps) and filters by requested paths', async () => {
const dir = await mkdtemp(path.join(tmpdir(), 'pfstore-'));
@ -102,6 +115,122 @@ describe('parsedfile-store', () => {
}
});
it('round-trips validated callable-flow operand and signature metadata', async () => {
const dir = await mkdtemp(path.join(tmpdir(), 'pfstore-'));
try {
const pf = makeStoreEntry('flow.cpp', {
callableFlowSites: [
{
kind: 'seed',
destination: {
name: 'member',
inScope: 'scope:entry',
atRange: { startLine: 3, startCol: 2, endLine: 3, endCol: 8 },
indirection: 0,
addressOf: false,
expressionKind: 'binding',
},
targetName: 'run',
targetQualifiedName: 'Base.run',
targetRange: { startLine: 3, startCol: 12, endLine: 3, endCol: 21 },
expectedSignature: {
parameterCount: 1,
parameterTypes: ['int'],
isConst: true,
},
},
],
});
await persistParsedFileChunk(dir, 'flow', [pf]);
const loaded = await loadParsedFilesForPaths(dir, new Set(['flow.cpp']));
expect(loaded.get('flow.cpp')?.callableFlowSites).toEqual(pf.callableFlowSites);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
it('drops a malformed callable-flow site but retains the file and its other sites (per-site sanitation, #2522)', async () => {
const dir = await mkdtemp(path.join(tmpdir(), 'pfstore-'));
try {
const operand = {
name: 'callback',
inScope: 'scope:entry',
atRange: { startLine: 2, startCol: 2, endLine: 2, endCol: 10 },
indirection: 17,
addressOf: false,
expressionKind: 'binding',
};
const invalid = makeStoreEntry('invalid.c', {
callableFlowSites: [
{
kind: 'invoke',
callSite: { startLine: 2, startCol: 2, endLine: 2, endCol: 12 },
inScope: 'scope:entry',
callee: operand,
invocationKind: 'indirect',
arity: 0,
},
],
});
await persistParsedFileChunk(dir, 'invalid', [invalid, makeParsedFile('valid.c')]);
const loaded = await loadParsedFilesForPaths(dir, new Set(['invalid.c', 'valid.c']));
// The file survives with the offending site dropped — a per-file
// rejection here caused a permanent, silent warm-cache reparse loop.
expect(loaded.get('invalid.c')?.callableFlowSites).toEqual([]);
expect(loaded.has('valid.c')).toBe(true);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
it('accepts empty-string parameterTypes entries ("" = unknown type, real C++ extractor output)', async () => {
const dir = await mkdtemp(path.join(tmpdir(), 'pfstore-'));
try {
const pf = makeStoreEntry('cv.cpp', {
callableFlowSites: [
{
kind: 'seed',
destination: {
name: 'fp',
inScope: 'scope:entry',
atRange: { startLine: 1, startCol: 0, endLine: 1, endCol: 8 },
indirection: 0,
addressOf: false,
expressionKind: 'binding',
},
targetName: 'handler',
targetRange: { startLine: 1, startCol: 12, endLine: 1, endCol: 19 },
expectedSignature: { parameterCount: 2, parameterTypes: ['int', ''] },
},
],
});
await persistParsedFileChunk(dir, 'cv', [pf]);
const loaded = await loadParsedFilesForPaths(dir, new Set(['cv.cpp']));
expect(loaded.get('cv.cpp')?.callableFlowSites).toEqual(pf.callableFlowSites);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
it('rejects the whole file only when callableFlowSites is non-array garbage', async () => {
const dir = await mkdtemp(path.join(tmpdir(), 'pfstore-'));
try {
const garbage = makeStoreEntry('garbage.c', {
callableFlowSites: 'not-an-array',
});
await persistParsedFileChunk(dir, 'garbage', [garbage, makeParsedFile('ok.c')]);
const loaded = await loadParsedFilesForPaths(dir, new Set(['garbage.c', 'ok.c']));
expect(loaded.has('garbage.c')).toBe(false);
expect(loaded.has('ok.c')).toBe(true);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
// #1983 parallel serialization: the sync worker writer and the async writer
// share one serialization core and MUST produce byte-identical shards (the
// loader's deep-equals masks byte drift, so assert raw bytes).
@ -178,10 +307,9 @@ describe('parsedfile-store', () => {
dependentPackBaseClasses: ['Mix'],
},
};
const pf = {
...(makeParsedFile('app.cpp') as unknown as Record<string, unknown>),
const pf = makeStoreEntry('app.cpp', {
captureSideChannel: sideChannel,
} as unknown as ParsedFile;
});
persistParsedFileShardSync(dir, 'w1-0', [pf]);
const loaded = await loadParsedFilesForPaths(dir, new Set(['app.cpp']));
@ -204,10 +332,9 @@ describe('parsedfile-store', () => {
kind: 'kotlin',
companionScopes: ['scope:Logger.companion', 'scope:Animal.companion'],
};
const pf = {
...(makeParsedFile('App.kt') as unknown as Record<string, unknown>),
const pf = makeStoreEntry('App.kt', {
captureSideChannel: sideChannel,
} as unknown as ParsedFile;
});
persistParsedFileShardSync(dir, 'w1-0', [pf]);
const loaded = await loadParsedFilesForPaths(dir, new Set(['App.kt']));
@ -228,10 +355,9 @@ describe('parsedfile-store', () => {
const dir = await mkdtemp(path.join(tmpdir(), 'pfstore-'));
try {
const sideChannel = { kind: 'c', staticNames: ['compute', 'helper'] };
const pf = {
...(makeParsedFile('local.c') as unknown as Record<string, unknown>),
const pf = makeStoreEntry('local.c', {
captureSideChannel: sideChannel,
} as unknown as ParsedFile;
});
persistParsedFileShardSync(dir, 'w1-0', [pf]);
const loaded = await loadParsedFilesForPaths(dir, new Set(['local.c']));

View file

@ -622,3 +622,17 @@ describe('callee-id accumulator — delete (R6 per-file release)', () => {
expect(snapshot(acc, 'b.ts')).toEqual({ [calleeIdPosKey(5, 0)]: ['fn:b'] });
});
});
describe('callee-id accumulator — selective callable-flow capture', () => {
it('retains only positions accepted by the capture filter', () => {
const acc = createCalleeIdAccumulator(
(filePath, line, col) => filePath === 'wanted.ts' && line === 7 && col === 3,
);
acc.add('wanted.ts', 7, 3, 'Function:wanted');
acc.add('wanted.ts', 8, 3, 'Function:other-line');
acc.add('other.ts', 7, 3, 'Function:other-file');
expect(snapshot(acc, 'wanted.ts')).toEqual({ '7:3': ['Function:wanted'] });
expect(acc.get('other.ts')).toBeUndefined();
});
});

View file

@ -374,3 +374,15 @@ describe('emitCScopeCaptures — static storage class', () => {
expect(isStaticName('a.c', 'get_buffer')).toBe(true);
});
});
describe('emitCScopeCaptures — callable-flow signatures', () => {
it('variadic function-pointer signatures carry the "..." sentinel and no fixed arity (#2522)', () => {
const match = findMatch(
'int vlog(const char *fmt, ...) { return 0; }\nint entry(void) { int (*emit)(const char *, ...) = vlog; return 0; }',
(tags) => tags.includes('@callable-flow.seed'),
);
expect(match).toBeDefined();
expect(match?.['@callable-flow.expected-types']?.text).toBe('["char","..."]');
expect(match?.['@callable-flow.expected-arity']).toBeUndefined();
});
});

View file

@ -0,0 +1,117 @@
/**
* Direct unit tests for `synthesizeCallableFlowCaptures` — the shared
* AST-walking capture synthesizer behind every language's
* `*_CALLABLE_CAPTURE_OPTIONS` block (#2522 review: the 1,100-line producer
* had no test naming it; only downstream consumers were covered).
*
* Runs over the JavaScript grammar with a minimal options object so the
* assertions pin the SYNTHESIZER's semantics (assignment forms, member
* paths, subscripts, formals, produced-value guards), not any language's
* option tuning.
*/
import { describe, it, expect } from 'vitest';
import { synthesizeCallableFlowCaptures } from '../../../src/core/ingestion/utils/callable-flow-captures.js';
import { getJsParser } from '../../../src/core/ingestion/languages/javascript/query.js';
const OPTIONS = {
functionNodeTypes: new Set(['function_declaration', 'arrow_function', 'function_expression']),
callNodeTypes: new Set(['call_expression']),
parameterListNodeTypes: new Set(['formal_parameters', 'arguments']),
parameterNodeTypes: new Set(['identifier', 'rest_pattern', 'assignment_pattern']),
bindingNodeTypes: new Set(['variable_declarator']),
assignmentNodeTypes: new Set(['assignment_expression']),
identifierNodeTypes: new Set(['identifier', 'property_identifier']),
} as const;
function factsFor(src: string): Array<Record<string, string>> {
const tree = getJsParser().parse(src);
if (tree === null) throw new Error('parse failed');
return synthesizeCallableFlowCaptures(tree.rootNode, OPTIONS).map((match) => {
const out: Record<string, string> = {};
for (const [tag, cap] of Object.entries(match)) {
if (cap !== undefined) out[tag] = cap.text;
}
return out;
});
}
function byTag(facts: Array<Record<string, string>>, tag: string): Array<Record<string, string>> {
return facts.filter((fact) => fact[tag] !== undefined);
}
describe('synthesizeCallableFlowCaptures (shared synthesizer, #2522)', () => {
it('emits a seed for a declared-function initializer and an invoke at the call through it', () => {
const facts = factsFor('function target() {}\nconst h = target;\nh(1);\n');
expect(byTag(facts, '@callable-flow.seed')).toMatchObject([
{
'@callable-flow.destination': 'h',
'@callable-flow.target-name': 'target',
},
]);
expect(byTag(facts, '@callable-flow.invoke')).toMatchObject([
{
'@callable-flow.callee': 'h',
'@callable-flow.invocation-kind': 'indirect',
'@callable-flow.arity': '1',
},
]);
});
it('emits one formal fact per parameter with its index', () => {
const facts = factsFor('function take(a, b) {}\n');
expect(byTag(facts, '@callable-flow.formal')).toMatchObject([
{ '@callable-flow.binding': 'a', '@callable-flow.parameter-index': '0' },
{ '@callable-flow.binding': 'b', '@callable-flow.parameter-index': '1' },
]);
});
it('emits an argument fact for a function passed by name', () => {
const facts = factsFor('function target() {}\nfunction wire(cb) { cb(); }\nwire(target);\n');
expect(byTag(facts, '@callable-flow.argument')).toMatchObject([
{ '@callable-flow.source': 'target', '@callable-flow.parameter-index': '0' },
]);
});
it('binds subscripted destinations and callees to the container, not the index (#2522 fix)', () => {
const facts = factsFor(
'function target() {}\nfunction entry(i) {\n const tbl = [];\n tbl[i] = target;\n tbl[i]();\n}\n',
);
expect(byTag(facts, '@callable-flow.seed')).toMatchObject([
{ '@callable-flow.destination': 'tbl', '@callable-flow.target-name': 'target' },
]);
expect(byTag(facts, '@callable-flow.invoke')).toMatchObject([
{ '@callable-flow.callee': 'tbl' },
]);
});
it('emits a member-call invoke only when the member cell has a visible store (#2522 fix)', () => {
const stored = factsFor('function target() {}\nfunction go(o) { o.run = target; o.run(); }\n');
expect(byTag(stored, '@callable-flow.invoke')).toMatchObject([
{ '@callable-flow.callee': 'run' },
]);
const unstored = factsFor('function go(map) { map.get("x"); }\n');
expect(byTag(unstored, '@callable-flow.invoke')).toEqual([]);
});
it('treats call results as produced values, not callable designators', () => {
const facts = factsFor('function make() {}\nconst h = make();\n');
expect(byTag(facts, '@callable-flow.seed')).toEqual([]);
expect(byTag(facts, '@callable-flow.copy')).toEqual([]);
});
it('suppresses bare-name sources entirely under bareNamesAreCalls (#2522 Ruby fix)', () => {
const src = 'function target() {}\nconst h = target;\n';
const withDefault = factsFor(src);
expect(byTag(withDefault, '@callable-flow.seed')).toHaveLength(1);
const tree = getJsParser().parse(src);
if (tree === null) throw new Error('parse failed');
const suppressed = synthesizeCallableFlowCaptures(tree.rootNode, {
...OPTIONS,
bareNamesAreCalls: true,
});
expect(suppressed.filter((match) => match['@callable-flow.seed'] !== undefined)).toEqual([]);
});
});

View file

@ -0,0 +1,202 @@
import { describe, expect, it } from 'vitest';
import {
buildDefIndex,
buildMethodDispatchIndex,
buildModuleScopeIndex,
buildQualifiedNameIndex,
buildScopeTree,
type BindingRef,
type CallableFlowOperand,
type ParsedFile,
type Range,
type Scope,
type ScopeId,
type SymbolDefinition,
} from 'gitnexus-shared';
import { createKnowledgeGraph } from '../../../src/core/graph/graph.js';
import type { ScopeResolutionIndexes } from '../../../src/core/ingestion/model/scope-resolution-indexes.js';
import { buildGraphNodeLookup } from '../../../src/core/ingestion/scope-resolution/graph-bridge/node-lookup.js';
import { createCalleeIdAccumulator } from '../../../src/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.js';
import { emitCallableValueFlow } from '../../../src/core/ingestion/scope-resolution/passes/callable-value-flow.js';
const FILE = 'chain.ts';
const MODULE = 'scope:module' as ScopeId;
const ENTRY_SCOPE = 'scope:entry' as ScopeId;
const range = (line: number): Range => ({
startLine: line,
startCol: 0,
endLine: line,
endCol: 8,
});
const targetDef: SymbolDefinition = {
nodeId: 'def:chain.ts#1:0:Function:target',
filePath: FILE,
type: 'Function',
qualifiedName: 'target',
parameterCount: 0,
};
const entryDef: SymbolDefinition = {
nodeId: 'def:chain.ts#2:0:Function:entry',
filePath: FILE,
type: 'Function',
qualifiedName: 'entry',
parameterCount: 0,
};
function scope(
id: ScopeId,
parent: ScopeId | null,
kind: Scope['kind'],
ownedDefs: readonly SymbolDefinition[],
bindings: ReadonlyMap<string, readonly BindingRef[]> = new Map(),
): Scope {
return {
id,
parent,
kind,
range: range(1),
filePath: FILE,
bindings,
ownedDefs,
imports: [],
typeBindings: new Map(),
};
}
function indexes(scopes: readonly Scope[]): ScopeResolutionIndexes {
const defs = [targetDef, entryDef];
return {
scopeTree: buildScopeTree([...scopes]),
defs: buildDefIndex(defs),
qualifiedNames: buildQualifiedNameIndex(defs),
moduleScopes: buildModuleScopeIndex([{ filePath: FILE, moduleScopeId: MODULE }]),
methodDispatch: buildMethodDispatchIndex({
owners: [],
computeMro: () => [],
implementsOf: () => [],
}),
imports: new Map(),
bindings: new Map(),
bindingAugmentations: new Map(),
workspaceFqnBindings: new Map(),
workspaceTypeBindings: new Map(),
namespaceFqnBindings: new Map(),
namespaceTypeBindings: new Map(),
accessibleNamespacesByScope: new Map(),
referenceSites: [],
sccs: [],
stats: {
totalFiles: 1,
totalEdges: 0,
linkedEdges: 0,
unresolvedEdges: 0,
sccCount: 1,
largestSccSize: 1,
},
};
}
function operand(name: string, line: number): CallableFlowOperand {
return {
name,
inScope: ENTRY_SCOPE,
atRange: range(line),
indirection: 0,
addressOf: false,
expressionKind: 'binding',
};
}
function runReverseChain(length: number) {
const moduleBindings = new Map<string, readonly BindingRef[]>([
['target', [{ def: targetDef }]],
['entry', [{ def: entryDef }]],
]);
const scopes = [
scope(MODULE, null, 'Module', [targetDef, entryDef], moduleBindings),
scope(ENTRY_SCOPE, MODULE, 'Function', [entryDef]),
];
const callSite = range(length + 10);
const copies = Array.from({ length }, (_, index) => index)
.reverse()
.map((index) => ({
kind: 'copy' as const,
source: operand(`value${index}`, index + 3),
destination: operand(`value${index + 1}`, index + 3),
}));
const parsed: ParsedFile = {
filePath: FILE,
moduleScope: MODULE,
scopes,
parsedImports: [],
localDefs: [targetDef, entryDef],
referenceSites: [
{
name: `value${length}`,
atRange: callSite,
inScope: ENTRY_SCOPE,
kind: 'call',
callForm: 'free',
},
],
callableFlowSites: [
{
kind: 'seed',
destination: operand('value0', 3),
targetName: 'target',
targetRange: range(1),
},
...copies,
{
kind: 'invoke',
callSite,
inScope: ENTRY_SCOPE,
callee: operand(`value${length}`, callSite.startLine),
invocationKind: 'indirect',
arity: 0,
},
],
};
const graph = createKnowledgeGraph();
graph.addNode({
id: 'Function:chain.ts:target',
label: 'Function',
properties: { name: 'target', qualifiedName: 'target', filePath: FILE },
});
graph.addNode({
id: 'Function:chain.ts:entry',
label: 'Function',
properties: { name: 'entry', qualifiedName: 'entry', filePath: FILE },
});
const result = emitCallableValueFlow({
graph,
scopes: indexes(scopes),
parsedFiles: [parsed],
nodeLookup: buildGraphNodeLookup(graph),
calleeIds: createCalleeIdAccumulator(),
language: 'typescript',
});
return { graph, result };
}
describe('callable-value-flow dependency worklist', () => {
it('scales linearly for reverse-ordered chains and resolves the terminal call', () => {
const n = runReverseChain(128);
const twoN = runReverseChain(256);
expect(n.result.resolvedInvokes).toBe(1);
expect(twoN.result.resolvedInvokes).toBe(1);
expect(n.result.emitted).toBe(1);
expect(twoN.result.emitted).toBe(1);
expect(twoN.result.iterations).toBeLessThanOrEqual(n.result.iterations * 2 + 4);
expect(twoN.result.iterations).toBeLessThan(1_024);
expect(
[...twoN.graph.iterRelationshipsByType('CALLS')].map(
(relationship) => twoN.graph.getNode(relationship.targetId)?.properties.name,
),
).toEqual(['target']);
});
});

View file

@ -527,3 +527,36 @@ describe('emitCppScopeCaptures — file-local linkage', () => {
expect(isFileLocal('test.cpp', 'helper')).toBe(false);
});
});
describe('emitCppScopeCaptures — callable-flow passing modes (#2522 review, M5)', () => {
function modesFor(src: string): Record<string, string | undefined> {
const out: Record<string, string | undefined> = {};
for (const m of emitCppScopeCaptures(src, 'modes.cpp')) {
const binding = m['@callable-flow.binding']?.text;
if (m['@callable-flow.formal'] === undefined || binding === undefined) continue;
out[`${m['@callable-flow.owner']?.text}.${binding}`] = m['@callable-flow.passing-mode']?.text;
}
return out;
}
it('classifies by the outermost declarator chain, never nested parameter lists', () => {
const modes = modesFor(
[
'void reg(void (*cb)(int& out)) {}',
'void take(int& v) {}',
'void raw(int* p) {}',
'void val(int v) {}',
'void refptr(void (*&cb)(int)) {}',
].join('\n'),
);
expect(modes).toMatchObject({
// by-value pointer copy — the nested `int&` must not make it an alias
'reg.cb': 'pointer',
'take.v': 'reference',
'raw.p': 'pointer',
'val.v': 'value',
// reference-to-pointer IS an alias
'refptr.cb': 'reference',
});
});
});

View file

@ -0,0 +1,56 @@
/**
* Pins the `hasFileLocalCallableLinkage` contract (#2522 review, M2): the
* hook must answer language-level internal linkage ONLY. The name-keyed
* file-local set is populated from every `static` declaration in a file, so
* a class member sharing a name with a static free function was over-marked
* — but class members have EXTERNAL linkage even when declared `static`
* (in-class `static` means "no instance"), and refusing their cross-file
* declaration/definition join under-links member-pointer seeds.
*/
import { describe, it, expect, beforeEach } from 'vitest';
import type { SymbolDefinition } from 'gitnexus-shared';
import { cppScopeResolver } from '../../../../src/core/ingestion/languages/cpp/scope-resolver.js';
import {
clearFileLocalNames,
markFileLocal,
} from '../../../../src/core/ingestion/languages/cpp/file-local-linkage.js';
describe('cppScopeResolver.hasFileLocalCallableLinkage (#2522 review, M2)', () => {
beforeEach(() => {
clearFileLocalNames();
});
it('treats a static namespace-scope free function as file-local', () => {
markFileLocal('w.cpp', 'helper');
const def: SymbolDefinition = {
nodeId: 'Function:w.cpp:helper#0',
filePath: 'w.cpp',
type: 'Function',
qualifiedName: 'helper',
};
expect(cppScopeResolver.hasFileLocalCallableLinkage?.(def)).toBe(true);
});
it('never treats a class member as file-local, even when a same-named static free function over-marked the file', () => {
markFileLocal('w.cpp', 'helper');
const method: SymbolDefinition = {
nodeId: 'Method:w.cpp:Widget.helper#0',
filePath: 'w.cpp',
type: 'Method',
qualifiedName: 'Widget.helper',
};
expect(cppScopeResolver.hasFileLocalCallableLinkage?.(method)).toBe(false);
});
it('never treats a constructor as file-local', () => {
markFileLocal('w.cpp', 'Widget');
const ctor: SymbolDefinition = {
nodeId: 'Constructor:w.cpp:Widget.Widget#0',
filePath: 'w.cpp',
type: 'Constructor',
qualifiedName: 'Widget.Widget',
};
expect(cppScopeResolver.hasFileLocalCallableLinkage?.(ctor)).toBe(false);
});
});

View file

@ -117,3 +117,32 @@ describe('emitJavaScopeCaptures — explicit constructor invocations (F38 #1928)
expect(refs.some((r) => r.name === 'E' && r.arity === '1')).toBe(true);
});
});
describe('emitJavaScopeCaptures — callable-flow protocol methods (#2522 review)', () => {
function invokeFactsFor(src: string): number {
return emitJavaScopeCaptures(src, 'C.java').filter(
(m) => m['@callable-flow.invoke'] !== undefined,
).length;
}
it('does not emit invoke facts for ordinary container accessors', () => {
const src = `
import java.util.HashMap;
class C {
static Object entry(HashMap<String, Object> map) {
return map.get("x");
}
}
`;
expect(invokeFactsFor(src)).toBe(0);
});
it('still emits invoke facts for functional-interface dispatch', () => {
const src = `
class C {
static void invoke(Runnable callback) { callback.run(); }
}
`;
expect(invokeFactsFor(src)).toBe(1);
});
});

View file

@ -29,6 +29,18 @@ function countTag(src: string, tag: string): number {
}
describe('emitJsScopeCaptures — #1876 array-method-callback narrowing', () => {
it('keeps callable-value bindings lexical across sibling functions', () => {
const invokes = matchesFor(
[
'function target() {}',
'function unrelated(target) { target(); }',
'function entry() { target(); }',
].join('\n'),
).filter((match) => match['@callable-flow.invoke'] !== undefined);
expect(invokes.map((match) => match['@callable-flow.invoke']!.range.startLine)).toEqual([2]);
});
it('does not emit @declaration.function for `const x = arr.map(a => …)`', () => {
const src = 'const exportData = accountsList.map(account => ({ id: account.id }));';
expect(hasDecl(src, '@declaration.const', 'exportData')).toBe(true);

View file

@ -213,6 +213,25 @@ describe('Pass 1: scope tree', () => {
// ─── §Pass 2: declarations + local bindings ────────────────────────────────
describe('Pass 2: declarations + local bindings', () => {
it('routes one multi-topic match through both scope and declaration passes', () => {
const result = extract(
[
scopeMatch('module', 1, 0, 100, 0),
{
'@scope.function': cap('@scope.function', 5, 0, 20, 0, 'render'),
'@declaration.function': cap('@declaration.function', 5, 0, 20, 0, 'render'),
'@declaration.name': cap('@declaration.name', 5, 0, 5, 6, 'render'),
},
],
'a.ts',
mockProvider(),
);
expect(result.scopes.some((scope) => scope.kind === 'Function')).toBe(true);
expect(result.localDefs).toHaveLength(1);
expect(result.localDefs[0]!.qualifiedName).toBe('render');
});
it('attaches a Class declaration to its enclosing Module scope', () => {
const result = extract(
[
@ -473,6 +492,172 @@ describe('Pass 5: reference sites', () => {
});
});
// ─── §Pass 6: callable-value-flow facts ───────────────────────────────────
describe('Pass 6: callable-value-flow facts', () => {
it('omits callableFlowSites when the provider emits no flow captures', () => {
const result = extract([scopeMatch('module', 1, 0, 100, 0)], 'a.ts', mockProvider());
expect(result.callableFlowSites).toBeUndefined();
});
it('materializes every normalized fact shape with lexical scopes and JSON-safe metadata', () => {
const matches: CaptureMatch[] = [
scopeMatch('module', 1, 0, 100, 0),
scopeMatch('function', 10, 0, 60, 0),
{
'@callable-flow.seed': cap('@callable-flow.seed', 20, 2, 20, 20),
'@callable-flow.destination': cap('@callable-flow.destination', 20, 2, 20, 4, 'fp'),
'@callable-flow.target': cap('@callable-flow.target', 20, 8, 20, 14, 'target'),
'@callable-flow.target-name': cap('@callable-flow.target-name', 20, 8, 20, 14, 'target'),
'@callable-flow.target-qualified-name': cap(
'@callable-flow.target-qualified-name',
20,
8,
20,
14,
'Ns.target',
),
'@callable-flow.expected-arity': cap('@callable-flow.expected-arity', 20, 2, 20, 2, '1'),
'@callable-flow.expected-types': cap(
'@callable-flow.expected-types',
20,
2,
20,
2,
'["int"]',
),
'@callable-flow.expected-type-classes': cap(
'@callable-flow.expected-type-classes',
20,
2,
20,
2,
'[{"base":"int","cv":"none","indirection":"value","pointerDepth":0}]',
),
},
{
'@callable-flow.copy': cap('@callable-flow.copy', 21, 2, 21, 10),
'@callable-flow.source': cap('@callable-flow.source', 21, 8, 21, 10, 'fp'),
'@callable-flow.destination': cap('@callable-flow.destination', 21, 2, 21, 5, 'fp2'),
},
{
'@callable-flow.alias': cap('@callable-flow.alias', 22, 2, 22, 10),
'@callable-flow.source': cap('@callable-flow.source', 22, 8, 22, 10, 'fp'),
'@callable-flow.destination': cap('@callable-flow.destination', 22, 2, 22, 5, 'ref'),
},
{
'@callable-flow.address': cap('@callable-flow.address', 23, 2, 23, 12),
'@callable-flow.source': cap('@callable-flow.source', 23, 9, 23, 11, 'fp'),
'@callable-flow.destination': cap('@callable-flow.destination', 23, 2, 23, 6, 'slot'),
},
{
'@callable-flow.store': cap('@callable-flow.store', 24, 2, 24, 14),
'@callable-flow.source': cap('@callable-flow.source', 24, 10, 24, 14, 'next'),
'@callable-flow.pointer': cap('@callable-flow.pointer', 24, 3, 24, 7, 'slot'),
'@callable-flow.pointer-indirection': cap(
'@callable-flow.pointer-indirection',
24,
3,
24,
3,
'1',
),
},
{
'@callable-flow.load': cap('@callable-flow.load', 25, 2, 25, 14),
'@callable-flow.pointer': cap('@callable-flow.pointer', 25, 10, 25, 14, 'slot'),
'@callable-flow.destination': cap('@callable-flow.destination', 25, 2, 25, 5, 'out'),
},
{
'@callable-flow.formal': cap('@callable-flow.formal', 10, 0, 60, 0),
'@callable-flow.owner': cap('@callable-flow.owner', 10, 0, 60, 0, 'invoke'),
'@callable-flow.binding': cap('@callable-flow.binding', 10, 15, 10, 17, 'cb'),
'@callable-flow.parameter-index': cap(
'@callable-flow.parameter-index',
10,
15,
10,
15,
'0',
),
'@callable-flow.passing-mode': cap(
'@callable-flow.passing-mode',
10,
15,
10,
15,
'reference',
),
},
{
'@callable-flow.argument': cap('@callable-flow.argument', 30, 2, 30, 12),
'@callable-flow.source': cap('@callable-flow.source', 30, 9, 30, 11, 'fp'),
'@callable-flow.parameter-index': cap('@callable-flow.parameter-index', 30, 9, 30, 9, '0'),
'@callable-flow.direct-callee-name': cap(
'@callable-flow.direct-callee-name',
30,
2,
30,
8,
'invoke',
),
},
{
'@callable-flow.invoke': cap('@callable-flow.invoke', 40, 2, 40, 14),
'@callable-flow.callee': cap('@callable-flow.callee', 40, 8, 40, 10, 'cb'),
'@callable-flow.receiver': cap('@callable-flow.receiver', 40, 3, 40, 6, 'obj'),
'@callable-flow.invocation-kind': cap(
'@callable-flow.invocation-kind',
40,
2,
40,
2,
'member-pointer',
),
'@callable-flow.arity': cap('@callable-flow.arity', 40, 2, 40, 2, '0'),
},
// Malformed facts are ignored defensively.
{ '@callable-flow.seed': cap('@callable-flow.seed', 50, 2, 50, 8) },
];
const result = extract(matches, 'a.ts', mockProvider());
const sites = result.callableFlowSites!;
expect(sites.map((site) => site.kind)).toEqual([
'seed',
'copy',
'alias',
'address',
'store',
'load',
'formal',
'argument',
'invoke',
]);
const fnScope = result.scopes.find((scope) => scope.kind === 'Function')!;
expect(sites[0]).toMatchObject({
destination: { name: 'fp', inScope: fnScope.id, indirection: 0 },
targetName: 'target',
targetQualifiedName: 'Ns.target',
expectedSignature: { parameterCount: 1, parameterTypes: ['int'] },
});
expect(sites[4]).toMatchObject({ pointer: { name: 'slot', indirection: 1 } });
expect(sites[6]).toMatchObject({
ownerName: 'invoke',
parameterIndex: 0,
passingMode: 'reference',
binding: { name: 'cb', inScope: fnScope.id },
});
expect(sites[7]).toMatchObject({ directCalleeName: 'invoke' });
expect(sites[8]).toMatchObject({
invocationKind: 'member-pointer',
arity: 0,
callee: { name: 'cb' },
receiver: { name: 'obj' },
});
expect(JSON.parse(JSON.stringify(sites))).toEqual(sites);
});
});
// ─── §End-to-end fixture ──────────────────────────────────────────────────
describe('end-to-end fixture (all 5 passes together)', () => {

View file

@ -688,3 +688,42 @@ describe('emitTsScopeCaptures — #1876 array-method-callback narrowing', () =>
).toBe(true);
});
});
describe('emitTsScopeCaptures — value-position references (#2437)', () => {
it('captures a longhand pair value identifier as @reference.value-ref with its key', () => {
const match = findMatch(
'function emitHook(): void {}\nexport const provider = { emitScopeCaptures: emitHook };',
(t) => t.includes('@reference.value-ref'),
);
expect(match).toBeDefined();
expect(match!['@reference.name'].text).toBe('emitHook');
expect(match!['@reference.property-key'].text).toBe('emitScopeCaptures');
});
it('captures a shorthand property as @reference.value-ref with key = name', () => {
const match = findMatch(
'function emitHook(): void {}\nexport const provider = { emitHook };',
(t) => t.includes('@reference.value-ref'),
);
expect(match).toBeDefined();
expect(match!['@reference.name'].text).toBe('emitHook');
expect(match!['@reference.property-key'].text).toBe('emitHook');
});
it('does not fire on destructuring shorthand', () => {
expect(
countMatches('const o = { a: 1 };\nconst { a } = o;', (t) =>
t.includes('@reference.value-ref'),
),
).toBe(0);
});
it('does not fire on a pair whose value is a call expression', () => {
expect(
countMatches(
'function make(): number { return 1; }\nexport const provider = { cfgVisitor: make() };',
(t) => t.includes('@reference.value-ref'),
),
).toBe(0);
});
});