Skip to content

test(parity): add native-engine guards for returnTypeMap and callAssignments#1287

Merged
carlos-alm merged 2 commits into
mainfrom
test/native-return-type-parity
Jun 2, 2026
Merged

test(parity): add native-engine guards for returnTypeMap and callAssignments#1287
carlos-alm merged 2 commits into
mainfrom
test/native-return-type-parity

Conversation

@carlos-alm
Copy link
Copy Markdown
Contributor

Summary

returnTypeMap test — verifies WASM produces a Map with return new Constructor() inference (confidence 0.85); verifies native produces an equivalent raw array (the form before patchReturnTypeMap converts it to a Map in production)

callAssignments test — verifies WASM records method calls in callAssignments when the callee's return type is not locally resolvable (cross-file propagation case); verifies native produces the same

Both native assertion blocks soft-skip on binaries older than #1283 (field absent = undefined).

Test plan

  • npx vitest run tests/engines/parity.test.ts -t "returnTypeMap|callAssignments" — 2 passed
  • npx vitest run tests/parsers/javascript.test.ts — 63 passed
  • Biome lint clean on modified file

Closes #1286

…gnments

normalize() in parity.test.ts strips returnTypeMap and callAssignments from
the structural comparison loop, so divergence between engines for those fields
would have gone undetected — exactly the regression pattern from #1279/#1283.

Two new explicit tests in the Cross-engine parity suite:
- JS — returnTypeMap: verifies WASM produces a Map with `return new X()`
  inference (confidence 0.85), and that native produces an equivalent raw array
  (patchReturnTypeMap converts it to a Map in production).
- JS — callAssignments: verifies WASM records cross-file receiver-typed method
  calls in callAssignments when the return type is not locally resolvable.
  Native is checked in its raw Vec form.

Both native assertions soft-skip on binaries older than #1283 that predate
the returnTypeMap/callAssignments fields (field absent = undefined).

Closes #1286
@claude
Copy link
Copy Markdown

claude Bot commented Jun 2, 2026

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


PR Review: test(parity): add native-engine guards for returnTypeMap and callAssignments

Tasks:

  • Initialize review
  • Read changed test file (tests/engines/parity.test.ts)
  • Read related source files for context
  • Analyze against review criteria (5 phases)
  • Post final assessment

Phase 1: Root Cause Analysis

Problem Being Solved: normalize() in the parity loop (lines 67–113) is intentionally narrow — it only projects definitions, calls, imports, classes, and exports. Fields like returnTypeMap and callAssignments are stripped before comparison. This means a native engine that correctly extracts structural symbols but silently drops returnTypeMap or callAssignments would still pass all structural parity tests. PR #1283 added native extraction for both fields; these tests are the regression guard.

Why This Approach: Correct — adding explicit, isolated tests for fields excluded from normalize() is the right way to close this gap. The existing normalize() design is intentional (it avoids false failures from format differences like Map vs raw array), and the PR doesn't change that design — it augments it with targeted assertions outside the loop.

Risk Assessment: Low. Pure test addition, no production code changes.


Phase 2: Configuration Change Detection

None. No lint rules disabled, no timeouts changed, no TypeScript flags relaxed. Clean.


Phase 3: Test Integrity

This is where I have substantive concerns:

1. Native assertions soft-skip on older binaries — but the skip mechanism is too permissive (parity.test.ts:766, parity.test.ts:808)

if (raw?.returnTypeMap === undefined) return;
if (raw?.callAssignments === undefined) return;

These guards silently pass if the binary predates #1283. That means in CI environments where the native binary hasn't been updated, both native assertion blocks become no-ops. The WASM side is validated; the native side is not. There's no warning or skip marker — the test name still says "populated by both WASM and native engines" even when only one engine was actually tested.

The correct pattern for "known binary version gap" used elsewhere in this file is skip: true on the test case (lines 420, 437, 521, etc.), or at minimum it.todo — not a silent return. A silent return in a test body is harder to grep for and doesn't appear in test runner output as skipped.

Recommended fix: Replace the silent return with an explicit it.skip guard or add a console.warn/skip annotation so CI visibility is preserved:

if (raw?.returnTypeMap === undefined) {
  // Binary predates #1283 — skip native assertion
  return;
}

...is arguably fine, but the comment at line 741 says "a simple existence check suffices" which frames this as intentional, so at minimum the test description should be 'JS — returnTypeMap: WASM always; native when binary >= #1283' to be honest about what's being tested.

2. WASM-only callAssignments test relies on WASM traversal order at parity.test.ts:799

expect(wasm?.callAssignments?.find((ca) => ca.varName === 'user')).toMatchObject({ ... });

Using .find() is fine for WASM (order may vary), but then for native (line 818) the same .find() is used on a raw array. This is correct — no issue here.

3. The callAssignments test fixture is correct, but the comment is subtly misleading (parity.test.ts:790–792)

resolveCallExprReturnType returns null and the assignment falls through to callAssignments with the receiver type from typeMap

This is accurate: repo gets type UserRepository from the new_expression at confidence 1.0, so when repo.findById('alice') is processed, typeMap.get('repo') returns { type: 'UserRepository', ... } and receiverTypeName is populated correctly. The comment is fine as-is, but noting it for reviewers.

4. No assertion on callAssignments.length for the WASM case

The test only asserts that callAssignments is an array and that a specific entry exists. It does not assert that no spurious extra entries are emitted. This is intentional (using .find() rather than deep equality), but means the test won't catch over-emission. Given the existing callAssignments unit tests in tests/unit/ts-resolver.test.ts already cover the negative cases, this is acceptable.


Phase 4: Code Quality

The tests are clear, well-commented, and correctly structured. The comment block at lines 729–741 is unusually thorough — it explains why the test exists (normalize() strips these fields), what the format difference is (Map vs array), and the binary-version soft-skip rationale. This is good practice.

One minor nit: the native section at lines 769–784 uses an inline type cast:

const entries = raw.returnTypeMap as Array<{ name: string; typeName: string; confidence: number; }>;

This is fine since this is a test file and the native shape isn't typed through a shared interface.


Phase 5: Backlog Compliance

  • Zero-dep: ✓ — No new runtime dependencies. Pure test additions.
  • Foundation-aligned: ✓ — Test-only. No pipeline changes, no cloud calls, no agent behavior.
  • Problem-fit: 3/5 — Closes a real regression gap (fields excluded from structural parity loop go undetected), but this is engineering hygiene rather than directly preventing hallucinated deps or reducing token waste for AI agents. Solid supporting work for fix(native): implement return-type + call-assignment extraction in Rust engine #1283.
  • Breaking: No — test file only, purely additive.
  • Tier: 1 (zero-dep ✓, foundation-aligned ✓)

Critical Concerns

Medium: Silent return masquerades as a passing test for native engine (lines 766, 808)

When the installed native binary predates #1283, both tests run and "pass" — but only the WASM half is actually validated. The test names claim "both WASM and native engines" are tested, which is misleading in those environments. In a parity CI job (CODEGRAPH_PARITY=1), the outer describeOrSkip already fails hard if native is absent, but it doesn't fail if native is present but old. This is the primary concern.

This isn't a blocker — the tests are still useful — but it violates the repo principle of "never silently skip verification" from CLAUDE.md.


Final Recommendation

## Root Cause Analysis
Problem Being Solved: normalize() strips returnTypeMap and callAssignments before
  structural comparison, making native regressions on those fields undetectable.
Why This Approach: Correct — targeted explicit tests outside the normalize() loop
  are the right solution. Doesn't disturb the existing structural loop.
Risk Assessment: Low — test-only, additive.

## Backlog Compliance
- Zero-dep: ✓
- Foundation-aligned: ✓
- Problem-fit: 3/5 — engineering hygiene / regression prevention
- Breaking: No
- Tier: 1

## Critical Concerns
- Medium: Silent `return` at lines 766 and 808 causes the test to silently pass
  for native without actually asserting anything when binary < #1283. Tests named
  "populated by both WASM and native engines" should either use `it.skip` + a
  condition, or add a `// eslint-disable` comment and a TODO noting the exact
  binary version needed, so it's visible in test output rather than silently green.

## Final Recommendation
- Rating: ⭐⭐⭐☆☆ (3/5)
- Action: APPROVE WITH CONDITIONS
- Reasoning: The tests are correct, well-motivated, and close a real gap. The
  only concern is the silent-pass behavior for outdated native binaries —
  acceptable if #1283's binary is already deployed in CI, but worth fixing
  before this pattern propagates to future parity tests. WASM assertions are
  unconditional and solid. Approve if native >= #1283 is already guaranteed
  in the parity CI job; otherwise address the silent-skip concern.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Jun 2, 2026

Greptile Summary

This PR closes a testing gap identified in #1286: the structural parity loop's normalize() function strips returnTypeMap and callAssignments, meaning regressions in native-engine output for those fields would go undetected. Two explicit it() tests are added inside the existing describeOrSkip('Cross-engine parity', ...) block to lock in the expected behaviour.

  • returnTypeMap test — asserts WASM returns a Map with type/confidence entries keyed by ClassName.methodName, and that native returns the equivalent raw { name, typeName, confidence } array (pre-patchReturnTypeMap format); native block soft-skips on binaries older than fix(native): implement return-type + call-assignment extraction in Rust engine #1283 via an === undefined guard.
  • callAssignments test — asserts WASM populates callAssignments when resolveCallExprReturnType returns null (cross-file propagation case), with the correct varName, calleeName, and receiverTypeName; native block mirrors the same shape and carries the same soft-skip guard.

Confidence Score: 5/5

Test-only change adding two well-scoped parity guards; no production code is touched and no existing tests are modified.

The change is confined to the parity test file. Both new tests follow the established pattern of the file (WASM assertions first, guarded native assertions second, soft-skip for old binaries). Named intermediate variables with toBeDefined() messages make failures immediately actionable. No logic under test is altered.

No files require special attention.

Important Files Changed

Filename Overview
tests/engines/parity.test.ts Adds two explicit parity tests for returnTypeMap and callAssignments fields that were previously stripped by normalize(), closing a gap where regressions in native-engine output for these fields would go undetected.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Test: returnTypeMap / callAssignments] --> B{hasNative?}
    B -- No --> C[Run WASM assertions only]
    B -- Yes --> D[Run WASM assertions]
    D --> E[nativeExtract code]
    E --> F{field === undefined?}
    F -- Yes --> G[Soft-skip: binary predates #1283]
    F -- No --> H[Assert native raw array format]
    C --> I[PASS / FAIL on WASM]
    H --> J[PASS / FAIL on native]
Loading

Reviews (3): Last reviewed commit: "test(parity): add toBeDefined guards and..." | Re-trigger Greptile

Comment thread tests/engines/parity.test.ts Outdated
Comment on lines +775 to +784
expect(entries.find((e) => e.name === 'UserService.getUser')).toMatchObject({
name: 'UserService.getUser',
typeName: 'User',
confidence: 0.85,
});
expect(entries.find((e) => e.name === 'UserService.buildQuery')).toMatchObject({
name: 'UserService.buildQuery',
typeName: 'QueryBuilder',
confidence: 0.85,
});
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 find() result passed directly to toMatchObject without a prior existence check

When find() returns undefined (entry absent), Vitest's toMatchObject surfaces a generic "expected undefined to match object" failure rather than pointing at the missing entry name. The same pattern recurs in the native callAssignments block (~line 818). Adding an intermediate toBeDefined() with a custom label makes the failure immediately actionable without changing test semantics.

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 in 9b508e4. Split each find() chain into a named intermediate variable and added toBeDefined(message) before toMatchObject() — all 6 affected assertions (2 WASM + 4 native across both tests). Failure messages now name the missing entry (e.g. "WASM returnTypeMap missing UserService.getUser") instead of surfacing a generic "expected undefined to match object".

…) chains

- Split find().toMatchObject() into named intermediate variables with
  toBeDefined(message) before each toMatchObject(), so failures name the
  missing entry rather than surfacing a generic "expected undefined to
  match object" error.
- Rename both test titles from "populated by both WASM and native engines"
  to "populated by WASM; native when binary >= #1283" to accurately reflect
  that native assertions are soft-skipped on pre-#1283 binaries.

Addresses Greptile P2 (find result passed directly to toMatchObject without
existence check) and Claude review note on misleading test descriptions.
@carlos-alm
Copy link
Copy Markdown
Contributor Author

Addressed in 9b508e4:

  1. Test titles (Claude's concern): Renamed both tests from "populated by both WASM and native engines" to "populated by WASM; native when binary >= fix(native): implement return-type + call-assignment extraction in Rust engine #1283", so the title honestly reflects that native assertions are soft-skipped on pre-fix(native): implement return-type + call-assignment extraction in Rust engine #1283 binaries.

  2. find().toMatchObject() without existence check (Greptile P2, Claude Phase 3 concern): Split all 6 find() chains into named intermediate variables with labeled toBeDefined(message) assertions before each toMatchObject(). Failure messages now name the missing entry directly (e.g. "WASM returnTypeMap missing UserService.getUser") rather than surfacing "expected undefined to match object".

The if (raw?.field === undefined) return version guards are intentionally kept as-is — they're the correct escape hatch for pre-#1283 binaries in environments where the outer describeOrSkip doesn't already hard-fail on binary presence. The guard style (rather than it.skip) is consistent with the rest of this file (Python test at line 732 uses the same inline return pattern).

@carlos-alm
Copy link
Copy Markdown
Contributor Author

@greptileai

@carlos-alm carlos-alm merged commit 8ef6180 into main Jun 2, 2026
26 checks passed
@carlos-alm carlos-alm deleted the test/native-return-type-parity branch June 2, 2026 07:39
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 2, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

follow-up: add native-engine integration tests for returnTypeMap and callAssignments

1 participant