Skip to content

fix(wasm): emit receiver edges for declaration-typed locals (cpp, cuda)#1497

Open
carlos-alm wants to merge 4 commits into
fix/v3120-regressions-1446-1447from
fix/wasm-receiver-decl-typed-1466
Open

fix(wasm): emit receiver edges for declaration-typed locals (cpp, cuda)#1497
carlos-alm wants to merge 4 commits into
fix/v3120-regressions-1446-1447from
fix/wasm-receiver-decl-typed-1466

Conversation

@carlos-alm

Copy link
Copy Markdown
Contributor

WASM engine was missing `receiver` edges for declaration-typed locals in C++ and CUDA (e.g. `UserService svc; svc.method()` → receiver edge `caller → UserService`). Native correctly emitted these at conf=0.9.

Root cause

The JS C++ and CUDA extractors had no handler for `declaration` AST nodes, so `typeMap` was never seeded for statically-typed locals. Without a typeMap entry for `svc`, `resolveReceiverEdge` found nothing to look up and silently skipped the receiver edge.

The native Rust path runs a second `walk_tree` pass with `match_c_family_type_map`, which handles the `declaration` node kind: it reads the `type` field and each `identifier` / `init_declarator` child to call `push_type_map_entry(symbols, varName, typeName)` at confidence 0.9.

Fix

Added `handleCppDeclaration` to `src/extractors/cpp.ts` and `handleCudaDeclaration` to `src/extractors/cuda.ts`. Both mirror the `match_c_family_type_map` "declaration" branch exactly: extract the type node text and seed `typeMap[varName] = { type, confidence: 0.9 }` for each identifier or `init_declarator` child. Primitive types (`int`, `char`, `bool`, …) are skipped to avoid spurious edges.

Verification

```
node scripts/parity-compare.mjs --langs cpp,cuda --hybrid

PARITY OK — 2 fixture(s), all engines identical

```

3044 tests pass, 0 failures.

Closes #1466

The JS C++ and CUDA extractors had no handler for 'declaration' AST nodes,
so typeMap was never seeded for statically-typed locals (e.g. 'UserService svc;').
Without a typeMap entry for 'svc', resolveReceiverEdge had nothing to look up and
silently skipped the receiver edge.

Add handleCppDeclaration / handleCudaDeclaration to both extractors. They mirror
match_c_family_type_map ('declaration' branch) from the native Rust path: extract
the type node text and seed typeMap[varName] = { type, confidence: 0.9 } for each
identifier or init_declarator child. Primitive types (int, char, bool, …) are
skipped to avoid spurious edges.

parity-compare.mjs --langs cpp,cuda --hybrid: PARITY OK (wasm = native = hybrid)
All 3044 tests pass.
@claude

claude Bot commented Jun 13, 2026

Copy link
Copy Markdown

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


PR Review: fix(wasm): emit receiver edges for declaration-typed locals (cpp, cuda)

  • Gather context and read changed files
  • Phase 1: Root cause analysis
  • Phase 2: Configuration change detection
  • Phase 3: Test integrity check
  • Phase 4: Code quality review
  • Phase 5: Backlog compliance
  • Final assessment

Root Cause Analysis

Problem Being Solved: WASM C++/CUDA extractors never handled declaration AST nodes, so typeMap was never seeded for statically-typed locals. When resolveReceiverEdge looked up the receiver in typeMap it found nothing and silently dropped the edge. The native Rust engine ran a separate walk_tree pass with match_c_family_type_map which did handle declaration, so native correctly emitted these edges at conf=0.9.

Why This Approach: The fix mirrors the exact native Rust logic. The declaration case is wired into the existing walkCppNode/walkCudaNode switch, so no architectural change is needed — just a missing case. The unwrapCppDeclaratorName/extractCudaFieldName helpers are already correct for this scenario, reused here.

Risk Assessment: Low. The change is purely additive (adds new typeMap entries that previously didn't exist), can only produce more edges, and is bounded by the primitive filter and the downstream RECEIVER_KINDS filter in resolveReceiverEdge. Parity verified via the parity-compare.mjs script.


Phase 2: Configuration Changes

None. No linter rules, tsconfig flags, or test thresholds were touched.


Phase 3: Test Integrity

No tests were modified or removed. The resolution-benchmark fixtures already contain the relevant UserService svc; declarations and expected receiver-typed edges — the fix causes those pre-existing expected edges to pass. No assertions were weakened.


Phase 4: Code Quality

Minor — TypeMapEntry import added but unused by name

Both files now import TypeMapEntry from ../types.js (cpp.ts:7, cuda.ts:7), but neither handler references it explicitly — the object literal { type: typeName, confidence: 0.9 } is structurally typed by ctx.typeMap's existing Map<string, TypeMapEntry> signature. Before this PR, TypeMapEntry was not imported in either file.

Whether this counts as unused depends on the TypeScript/Biome configuration. If the linter runs with noUnusedVariables/noUnusedImports on type-only imports, this will warn. The import itself is harmless (it's a type import — zero runtime cost), but it should either be removed (since it's not explicitly annotating anything in the new code) or kept only if it's intentionally documenting the shape of what's being set. Given the existing patterns in the other extractors that do use TypeMapEntry (e.g. helpers.ts uses it as an explicit parameter type), this is fine to keep for clarity — but worth checking that npm run lint passes cleanly.

handleCudaDeclaration uses extractCudaFieldName while handleCppDeclaration uses unwrapCppDeclaratorName — this is correct and intentional. CUDA has a richer declarator model (function-pointer fields, parenthesized_declarator fallback) that extractCudaFieldName handles, while C++ uses the CPP_DECLARATOR_WRAPPERS-based unwrapCppDeclaratorName. Mirrors the native divergence between unwrap_cpp_declarator and unwrap_cuda_declarator in Rust.

Primitive filter divergence from native Rust: The WASM engines filter primitive types at extraction time (isPrimitiveCppType / isCudaPrimitiveType); native Rust has no such filter but achieves the same result because resolveReceiverEdge filters by RECEIVER_KINDS and a primitive like int will never appear as a class/struct node. The WASM approach is a valid early-out optimization and is consistent with the // Skip primitive types comment already in the new code. No actual behavioral difference — not a bug.

CUDA_PRIMITIVE_TYPES and CPP_PRIMITIVE_TYPES are identical sets (lines 365–370 in cpp.ts, 415–420 in cuda.ts). This is acceptable duplication given that the two extractors are deliberately kept separate (different WASM grammars, different declarator unwrap helpers), but a shared C_FAMILY_PRIMITIVE_TYPES constant in helpers.ts would be DRY-er. Not a blocking concern.


Phase 5: Backlog Compliance

Axis Result
Zero-dep ✓ — no new runtime dependencies
Foundation-aligned ✓ — purely additive WASM extraction fix, no cloud, no code gen
Problem-fit 5 — directly fixes an engine parity bug that causes hallucinated (missing) dependency edges for C++/CUDA codebases; an AI agent using codegraph on a C++ repo would miss receiver relationships entirely without this
Breaking No — purely additive, typeMap gains entries it previously lacked
Tier 1

Critical Concerns

None blocking.

Non-blocking observation: The TypeMapEntry import is new in both files but not explicitly referenced by name in the added code. It will compile fine (structurally compatible), but should be verified against npm run lint to confirm Biome doesn't flag it as an unused import.


Final Recommendation

  • Rating: ⭐⭐⭐⭐ (4/5)
  • Action: APPROVE
  • Reasoning: Correct fix for a real parity bug. The declaration handler mirrors the native Rust match_c_family_type_map "declaration" branch faithfully, reuses existing declarator-unwrap helpers correctly, includes a sound primitive filter, and is covered by the existing resolution-benchmark fixtures. Parity-compare verification is clean. The only minor issue is the TypeMapEntry import that is structurally needed (the map's value type) but not explicitly annotated in the new code — verify npm run lint passes before merging.

@greptile-apps

greptile-apps Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Fixes missing receiver edges for declaration-typed locals in the WASM C++ and CUDA extractors by adding handleCppDeclaration and handleCudaDeclaration. Both handlers mirror the native Rust match_c_family_type_map "declaration" branch: they read the type node text, skip primitives, and seed typeMap[varName] = { type, confidence: 0.9 } for each identifier or init_declarator child.

  • src/extractors/cpp.ts and src/extractors/cuda.ts each gain a handleXDeclaration function and a corresponding isXPrimitiveType guard backed by an identical 27-entry primitive-types set.
  • tests/parsers/javascript.test.ts contains formatting-only changes (multi-line toEqual objects); no new cpp/cuda unit tests are included — verification relies on the parity integration script.

Confidence Score: 5/5

The change is a well-scoped additive fix — it only seeds new typeMap entries where the map was previously empty, making it impossible to remove existing receiver edges. The primitive-type guard and the intentional skip of pointer/reference declarators are both consistent with the Rust implementation.

Both new handlers are clean ports of the existing Rust logic with no behavioral divergence. The only concern is that the identical primitive-type sets are duplicated across the two files rather than shared, and there are no new parser-level unit tests — but neither issue affects correctness.

The duplicated primitive-type set in src/extractors/cuda.ts (identical to src/extractors/cpp.ts) is the only thing worth a second look.

Important Files Changed

Filename Overview
src/extractors/cpp.ts Adds handleCppDeclaration to seed typeMap for declaration-typed locals; mirrors the Rust match_c_family_type_map logic exactly; includes CPP_PRIMITIVE_TYPES guard and intentional scoping comment for pointer/reference declarators.
src/extractors/cuda.ts Adds handleCudaDeclaration in the same pattern as the C++ handler; CUDA_PRIMITIVE_TYPES set is identical to CPP_PRIMITIVE_TYPES — duplication worth consolidating into shared helpers.
tests/parsers/javascript.test.ts Formatting-only changes: multi-line toEqual objects in two existing assertions — no functional test changes.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[walkCppNode / walkCudaNode] -->|declaration node| B[handleCppDeclaration\nor handleCudaDeclaration]
    B --> C{type field present?}
    C -->|no| Z[return — no typeMap entry]
    C -->|yes, typeName| D{isPrimitive?}
    D -->|yes| Z
    D -->|no| E[iterate child nodes]
    E -->|init_declarator| F[nameNode = declarator child field]
    E -->|identifier| G[nameNode = child directly]
    E -->|pointer/ref declarator| H[skip — intentional parity with Rust]
    F --> I[unwrapDeclaratorName]
    G --> I
    I --> J[typeMap.set varName to type at conf 0.9]
    A -->|always after switch| K[recurse into all children]
    K --> A
Loading

Reviews (4): Last reviewed commit: "fix(lint): expand inline toEqual objects..." | Re-trigger Greptile

Comment thread src/extractors/cpp.ts
Comment on lines +228 to +232
if (kind === 'init_declarator') {
nameNode = child.childForFieldName('declarator') ?? null;
} else if (kind === 'identifier') {
nameNode = child;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Pointer/reference declarators silently skipped

The handler only seeds typeMap for direct identifier children and init_declarator children. For UserService *svc; or UserService &svc = ...;, the declarator field of the declaration node is a pointer_declarator / reference_declarator, not a bare identifier — so those variables never get a typeMap entry and svc->method() calls won't generate receiver edges. This may intentionally mirror the Rust match_c_family_type_map limitation; if so, a comment noting the gap would be helpful. The same applies in src/extractors/cuda.ts.

Fix in Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — added a clarifying comment in both handleCppDeclaration and handleCudaDeclaration (commit d3d83ef) documenting that this is intentional: the native Rust match_c_family_type_map helper in crates/codegraph-core/src/extractors/helpers.rs also only iterates init_declarator and identifier children of a declaration node — pointer_declarator/reference_declarator children are skipped on both sides. Both engines have identical scope here, so this is not a parity gap.

@github-actions

github-actions Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Codegraph Impact Analysis

6 functions changed6 callers affected across 2 files

  • walkCppNode in src/extractors/cpp.ts:27 (1 transitive callers)
  • handleCppDeclaration in src/extractors/cpp.ts:216 (2 transitive callers)
  • isPrimitiveCppType in src/extractors/cpp.ts:395 (3 transitive callers)
  • walkCudaNode in src/extractors/cuda.ts:40 (1 transitive callers)
  • handleCudaDeclaration in src/extractors/cuda.ts:216 (2 transitive callers)
  • isCudaPrimitiveType in src/extractors/cuda.ts:445 (3 transitive callers)

…sets to one-per-line

Biome flagged TypeMapEntry as an unused import in both cpp.ts and cuda.ts
(ctx.typeMap.set is typed via ExtractorOutput, not via a direct TypeMapEntry
annotation). Also reformatted CPP_PRIMITIVE_TYPES / CUDA_PRIMITIVE_TYPES to
one element per line, which is what the Biome formatter requires.
…Declaration

Add an inline comment in handleCppDeclaration and handleCudaDeclaration
explaining that pointer_declarator / reference_declarator children are
intentionally skipped. Native Rust match_c_family_type_map has the same
scope: it only iterates 'init_declarator' and 'identifier' children, so
both engines behave identically for pointer/reference-typed locals.
@carlos-alm

Copy link
Copy Markdown
Contributor Author

Addressed reviewer feedback:

  1. Unused TypeMapEntry imports (Greptile P2 + Claude) — removed the import from both src/extractors/cpp.ts and src/extractors/cuda.ts (commit 2986c81). Biome and TypeScript type-check both pass cleanly.

  2. Formatting (Biome) — expanded CPP_PRIMITIVE_TYPES and CUDA_PRIMITIVE_TYPES to one-element-per-line format that Biome requires (same commit 2986c81).

  3. Pointer/reference declarator scope (Greptile P2 inline) — added explanatory comments to both handlers clarifying this is intentional scope that matches the native Rust match_c_family_type_map helper, which also only iterates init_declarator and identifier children (commit d3d83ef). Both engines have identical behaviour; no parity gap.

@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