Skip to content

fix: eliminate super-dispatch self-loops and align CHA confidence#1514

Open
carlos-alm wants to merge 4 commits into
fix/js-extractor-nodes-1471from
fix/jelly-micro-resolution-1472
Open

fix: eliminate super-dispatch self-loops and align CHA confidence#1514
carlos-alm wants to merge 4 commits into
fix/js-extractor-nodes-1471from
fix/jelly-micro-resolution-1472

Conversation

@carlos-alm

Copy link
Copy Markdown
Contributor

Summary

After the node extraction gaps fixed in #1471, addresses the root CHA post-pass bugs causing jelly-micro parity divergences:

  • Self-loops eliminated: runChaPostPass in helpers.ts was expanding class A's implementors without guarding against methodNode.id === source_id. When B.m called super.m() producing a B.m → A.m edge, the CHA pass walked A's children (including B) and emitted B.m → B.m self-loops. One-line guard added.
  • Native path aligned: runPostNativeThisDispatch in native-orchestrator.ts also lacked this self-loop guard. Added for consistency.
  • runPostNativeCha confidence aligned: Uses computeConfidence(callerFile, targetFile) - CHA_DISPATCH_PENALTY instead of hardcoded 0.8, matching the WASM path. Gate B now checks the same kind set as Gate A.

parity-compare.mjs --langs jelly-micro --hybrid: 22 wasm/native diffs → 18 (4 self-loops eliminated), 9 wasm/hybrid diffs unchanged.

Remaining 4 divergences deferred to separate issues:

…ing, prototype params to JS extractor

Closes #1471

Four extraction gaps in the WASM JS extractor caused 25 node diffs
against the native engine in the jelly-micro parity fixture:

1. Computed property method names (e.g. `['property7']`): the query
   patterns in both parser.ts and wasm-worker-entry.ts only matched
   `property_identifier` and `private_property_identifier` as the
   `name` field of `method_definition`; add a third pattern for
   `computed_property_name`.

2. Class expressions inside functions (`return class PostMixin …`):
   wasm-worker-entry.ts JS_CLASS_PATTERN was a single string matching
   only `class_declaration`; rename to JS_CLASS_PATTERNS array and add
   the `(class name: …)` pattern for anonymous class-expression nodes.
   Also add the matching `(class name: (type_identifier) …)` pattern
   to TS_EXTRA_PATTERNS so TS class expressions are covered too.

3. Array destructuring constants (`const [x, y] = …`): both
   `extractDestructuredBindingsWalk` (query path) and
   `handleVariableDecl` (walk path) only handled `object_pattern`; add
   an `array_pattern` branch that emits a single `constant` node whose
   name is the full array pattern text — matching native behaviour.

4. Parameters of prototype arrow/function methods
   (`Arit.prototype.sum = (x, y) => …`): `emitPrototypeMethod` emitted
   the definition node without calling `extractParameters`, so children
   were always absent; add the call and wire the result through.

After this fix `parity-compare.mjs --langs jelly-micro` reports
0 node diffs (was 25). 22 pre-existing edge diffs remain (tracked
in #1506) — those are out of scope.
runChaPostPass (helpers.ts, WASM path) was emitting self-loop edges like
B.m → B.m when expanding class A's implementors: it found B.m via
findMethodStmt but had no guard against the case where methodNode.id equals
the source_id (the calling method itself). Add the missing guard.

runPostNativeThisDispatch (native-orchestrator.ts) similarly lacked a self-loop
guard in its targets loop — add it to keep the native path consistent.

Also aligns runPostNativeCha to use file-pair-aware confidence (computeConfidence
- CHA_DISPATCH_PENALTY) instead of a hardcoded 0.8, matching the WASM path.
Gate B now checks the same kind set as Gate A for future-proofing.

Remaining jelly-micro divergences filed as separate issues:
- #1510: receiver-callee-mixup attribution (findCaller tie-break for same-line defs)
- #1511: dynamic pts cross-file edges (fun.js, classes2.js apply/call)
- #1512: native missing f.h→f.g this-dispatch for func-prop methods
- #1513: prototypes.js receiver edge non-determinism

docs check acknowledged: no language, feature, or architecture changes.

Closes #1472
@claude

claude Bot commented Jun 13, 2026

Copy link
Copy Markdown

Claude finished @carlos-alm's task —— View job


PR Review — fix: eliminate super-dispatch self-loops and align CHA confidence

  • Gather context and understand the request
  • Read changed files
  • Phase 1: Root Cause Analysis
  • Phase 2: Configuration Change Detection
  • Phase 3: Test Integrity
  • Phase 4: Code Quality
  • Phase 5: Backlog Compliance
  • Final Assessment

Root Cause Analysis

Problem Being Solved: The CHA (Class Hierarchy Analysis) post-pass emitted B.m → B.m self-loop edges. When B.m called super.m() producing a B.m → A.m edge, the CHA pass expanded A's implementors (including B) and emitted B.m → B.m without guarding against methodNode.id === source_id. Four such self-loops existed in the jelly-micro fixture. This PR also aligns the native path's CHA confidence computation to match the WASM path.

Why This Approach: The fix is a one-line guard inserted at the correct place in both CHA post-pass implementations (runChaPostPass in helpers.ts and runPostNativeThisDispatch in native-orchestrator.ts). The confidence alignment replaces a hardcoded 0.8 with computeConfidence(...) - CHA_DISPATCH_PENALTY, matching the WASM path. This is the minimal correct fix — no architectural changes needed.

Risk Assessment: Low. The self-loop guard is a pure correctness improvement. The confidence alignment uses the same function already used on the WASM path. Gate B kind-set expansion is forward-compatible. The test additions cover the new extraction behaviors from #1471.


Phase 2: Configuration Change Detection

No linting rules relaxed, no test timeouts increased, no build config modified, no TypeScript strict mode flags disabled. ✓


Phase 3: Test Integrity

The test file adds 5 new describe blocks (lines 1269–1381) covering:

  • Computed method name extraction from object literals
  • Class expressions inside functions (extends, methods, field properties)
  • Array destructuring constant extraction
  • Prototype method parameter extraction

All assertions are additive — they verify new extraction behaviors introduced by #1471, not weakened. No existing assertions were removed or relaxed. ✓

One potential gap: there are no tests for the self-loop guard fix itself. A test like:

it('does not emit B.m → B.m self-loop from super.m() CHA expansion', () => { ... })

would be a valuable regression guard at the unit level. The parity-compare script catches it at integration level, but that script isn't in the automated test suite.


Phase 4: Code Quality

helpers.ts:514 — Self-loop guard: if (methodNode.id === source_id) continue; // skip self-loops
Correct placement: before the seen dedup check. The comment is clear and defensible. ✓

native-orchestrator.ts:909 — Same guard for runPostNativeThisDispatch:

if (t.id === callerRow.id) continue; // skip self-loops

This is correct but note a subtle asymmetry: in runChaPostPass (WASM path), source_id is the caller node; here callerRow.id is also the caller. The guard is correct in both cases. ✓

native-orchestrator.ts:662–666 — Confidence alignment:

const conf =
  computeConfidence(caller_file ?? '', methodNode.method_file ?? '', null) -
  CHA_DISPATCH_PENALTY;
if (conf <= 0) continue;

The ?? '' fallback for null files is reasonable — computeConfidence('') returns a lower base that, minus CHA_DISPATCH_PENALTY, may drop to ≤ 0 (skipped). This correctly avoids emitting low-confidence cross-null-file edges. ✓

Gate B kind-set expansion (native-orchestrator.ts:541):

AND tgt.kind IN ('class', 'interface', 'trait', 'struct', 'record')

Previously Gate B only checked class. Expanding to match Gate A's kind set is more correct but introduces a subtle behavior change: previously, incremental builds with new StructImpl() in a changed file would NOT trigger a full CHA scan; now they do. This is technically a correctness improvement (scoping the scan could silently miss expansions for struct/record kinds), and the comment explains the reasoning. ✓ However, this is a silent incremental behavior change that could affect build performance in monorepos with frequent struct/record instantiations — worth monitoring.

wasm-worker-entry.tsJS_CLASS_PATTERN string renamed to JS_CLASS_PATTERNS array with class expression pattern added. Pattern duplication between parser.ts and wasm-worker-entry.ts is a pre-existing architectural concern (the file notes it explicitly at line 20: "does NOT import from ./parser.js"). The PR maintains consistency correctly. No new divergence introduced.

javascript.ts extractor — Three additions:

  1. Array destructuring const [x, y] = ...constant kind node (lines 510, 1029)
  2. Prototype method parameter extraction (line 3378)

Both correctly mirror native engine behavior per the PR description. The array pattern branch is correctly guarded with !hasFunctionScopeAncestor(node) to match the existing object pattern branch. ✓

Minor concern: runChaPostPass (WASM path, helpers.ts) still uses hardcoded 0.8 confidence (line 518), while runPostNativeCha now uses computeConfidence - CHA_DISPATCH_PENALTY. The PR aligns the native path to WASM, not vice versa. If computeConfidence - CHA_DISPATCH_PENALTY produces a value different from 0.8 for same-file edges, the two paths will still diverge for intra-file CHA edges. This is a pre-existing divergence that this PR doesn't make worse, but the commit title says "align CHA confidence" — it would be more accurate to say "align native CHA confidence to WASM formula" rather than implying full parity.


Backlog Compliance

  • Zero-dep: ✓ — no new runtime dependencies added
  • Foundation-aligned: ✓ — local graph analysis, no cloud calls, no new infrastructure
  • Problem-fit: 5 — directly reduces false-positive edges (self-loops inflate blast radius, corrupt AI call chains), eliminates hallucinated B.m → B.m dependencies
  • Breaking: No — purely additive edge correction (removes incorrect edges, doesn't change API surface or DB schema)
  • Tier: 1

Critical Concerns

None blocking. One minor observation:

The runChaPostPass (WASM/helpers path) at helpers.ts:518 still uses hardcoded 0.8. The PR description says "Uses computeConfidence(...) - CHA_DISPATCH_PENALTY instead of hardcoded 0.8" — this is true for the native path only. The WASM path (helpers.ts) still uses 0.8. This is not a regression (it was 0.8 before), but the description should be read as "native path now matches WASM formula" rather than "both paths now use the formula." The remaining 4 parity divergences are deferred with issues opened — good discipline.


Final Recommendation

  • Rating: ⭐⭐⭐⭐ (4/5)
  • Action: APPROVE
  • Reasoning: The root cause (CHA self-loops) is correctly identified and fixed with minimal, targeted changes. The confidence alignment is architecturally sound. Tests are additive and correctly cover the Engine parity: JS extractor misses 25 nodes the native extractor finds (jelly-micro) #1471 extraction additions. The only suggested improvement is a unit test for the self-loop guard at the DB layer, but its absence doesn't block merge — the parity-compare integration test already catches regressions at that level. Gate B kind-set expansion is a correctness improvement with minor performance implications worth noting in release notes.

@greptile-apps

greptile-apps Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes three CHA post-pass bugs that caused jelly-micro parity divergences after the node-extraction work in #1471. The primary fix eliminates super-dispatch self-loops by guarding all three CHA expansion sites, and also aligns runPostNativeCha to use file-pair-aware confidence (computeConfidence - CHA_DISPATCH_PENALTY) instead of a hardcoded 0.8.

  • Self-loop guard added in runChaPostPass (helpers.ts), runPostNativeCha, and runPostNativeThisDispatch (native-orchestrator.ts): when B.m → A.m triggers a CHA walk that finds B as a child of A, methodNode.id === source_id / t.id === callerRow.id now skips the would-be B.m → B.m self-loop.
  • Gate B kind set in runPostNativeCha widened from kind = 'class' to match Gate A's IN ('class', 'interface', 'trait', 'struct', 'record'), preventing false negatives in the incremental-scope gate for future node kinds.
  • Parser/extractor additions (computed method names, class expressions, array destructuring, prototype method parameters) extracted from the parent Engine parity: JS extractor misses 25 nodes the native extractor finds (jelly-micro) #1471 work to close remaining native/WASM parity gaps.

Confidence Score: 5/5

Safe to merge — all three CHA expansion sites now guard against self-loops, and the Gate B alignment is a strictly conservative widening.

The changes are narrow, targeted, and well-tested. Each fix is a one-line guard (or a constant-set alignment) with clear explanations, matching logic already present in sibling functions. The previously flagged missing self-loop guard in runPostNativeCha is fully addressed, and the confidence-asymmetry gap is explicitly tracked with a TODO. No new unsafe paths are introduced.

No files require special attention. The acknowledged confidence asymmetry between runChaPostPass (hardcoded 0.8) and runPostNativeCha (dynamic computeConfidence) is tracked with a TODO in helpers.ts.

Important Files Changed

Filename Overview
src/domain/graph/builder/helpers.ts Self-loop guard added before key computation; hardcoded 0.8 confidence preserved with a TODO noting the asymmetry with runPostNativeCha.
src/domain/graph/builder/stages/native-orchestrator.ts Self-loop guards added in both runPostNativeCha and runPostNativeThisDispatch; Gate B kind set aligned to Gate A; confidence now file-pair-aware with conf<=0 guard.
src/domain/parser.ts Adds computed_property_name pattern to COMMON_QUERY_PATTERNS, mirroring the same addition in wasm-worker-entry.ts.
src/domain/wasm-worker-entry.ts Adds computed method name pattern and class-expression patterns (JS_CLASS_PATTERNS array); TS_EXTRA_PATTERNS extended with class-expression support for TypeScript.
src/extractors/javascript.ts Array-destructuring constant nodes added to both query and walk paths; emitPrototypeMethod now extracts parameters; changes are consistent across extractDestructuredBindingsWalk and handleVariableDecl (separate, mutually exclusive paths).
tests/parsers/javascript.test.ts Good test coverage added for computed methods, class expressions, array destructuring, and prototype method parameters.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["B.m calls super.m()"] --> B["Edge: B.m → A.m inserted"]
    B --> C["runChaPostPass / runPostNativeCha\nBFS over A's implementors"]
    C --> D{"methodNode.id\n=== source_id?"}
    D -->|"YES (self-loop) NEW GUARD"| E["continue — skip B.m → B.m"]
    D -->|"NO"| F{"conf <= 0?"}
    F -->|"YES"| G["continue — skip low-confidence edge"]
    F -->|"NO"| H["Insert CHA dispatch edge"]

    subgraph "runPostNativeCha Gate Logic"
        I["changedFiles provided?"] --> J{"Gate A: any class/interface/trait/struct/record node in changed files?"}
        J -->|"YES"| K["Full scan (scopeToChangedFiles=false)"]
        J -->|"NO"| L{"Gate B aligned: calls to class/interface/trait/struct/record targets in changed files?"}
        L -->|"YES"| K
        L -->|"NO"| M["Scoped scan (scopeToChangedFiles=true)"]
    end

    subgraph "runPostNativeThisDispatch"
        N["resolve this/super dispatch"] --> O{"t.id === callerRow.id? NEW GUARD"}
        O -->|"YES"| P["continue — skip self-loop"]
        O -->|"NO"| Q["Insert dispatch edge"]
    end
Loading

Reviews (5): Last reviewed commit: "fix(comments): correct inaccurate WASM-p..." | Re-trigger Greptile

@github-actions

github-actions Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Codegraph Impact Analysis

3 functions changed6 callers affected across 5 files

  • runChaPostPass in src/domain/graph/builder/helpers.ts:379 (3 transitive callers)
  • runPostNativeCha in src/domain/graph/builder/stages/native-orchestrator.ts:420 (4 transitive callers)
  • runPostNativeThisDispatch in src/domain/graph/builder/stages/native-orchestrator.ts:706 (4 transitive callers)

…ence alignment (#1514)

runPostNativeCha's BFS inner loop was missing the same self-loop guard
already added to runChaPostPass and runPostNativeThisDispatch, allowing
B.m→B.m edges to persist on the native CHA path when super-dispatch
triggers CHA expansion. Also adds a TODO comment on the hardcoded 0.8
confidence in runChaPostPass noting it needs alignment with the formula
used in runPostNativeCha (requires extending findMethodStmt to fetch file).
@carlos-alm

Copy link
Copy Markdown
Contributor Author

Fixed both issues raised in the Greptile review:

P1 — Missing self-loop guard in runPostNativeCha: Added if (methodNode.id === source_id) continue; // skip self-loops before the key computation in the BFS inner loop of runPostNativeCha (native-orchestrator.ts), matching the guard already present in runChaPostPass and runPostNativeThisDispatch.

P2 — Asymmetric confidence in runChaPostPass: Added a TODO comment on the hardcoded 0.8 in helpers.ts noting it needs alignment with runPostNativeCha's computeConfidence(callerFile, methodFile) - CHA_DISPATCH_PENALTY formula (deferred because it requires extending findMethodStmt to fetch file).

Commit: fc6bc92

@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai

…Cha (#1514)

The comments said the confidence formula "mirrors the WASM path" but the
WASM path (runChaPostPass in helpers.ts) still uses hardcoded 0.8, not
computeConfidence - CHA_DISPATCH_PENALTY. Correct both comments to
accurately describe the native formula and cross-reference the TODO in
helpers.ts for the pending alignment.
@carlos-alm

Copy link
Copy Markdown
Contributor Author

Addressed the comment accuracy issue flagged by Greptile: the two comments in runPostNativeCha that said the confidence formula "mirrors the WASM path" were inaccurate — the WASM path (runChaPostPass in helpers.ts) still uses hardcoded 0.8, not computeConfidence - CHA_DISPATCH_PENALTY. Both comments now correctly describe the native formula and cross-reference the TODO in helpers.ts for the pending alignment. Commit: 5905bcc

@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant