Skip to content

fix: add missing same-class bare-call fallback to incremental rebuild#1947

Closed
carlos-alm wants to merge 1 commit into
fix/issue-1764-object-literal-computed-method-keys-extractfrom
fix/issue-1765-incremental-rebuild-missing-same-class-bare
Closed

fix: add missing same-class bare-call fallback to incremental rebuild#1947
carlos-alm wants to merge 1 commit into
fix/issue-1764-object-literal-computed-method-keys-extractfrom
fix/issue-1765-incremental-rebuild-missing-same-class-bare

Conversation

@carlos-alm

Copy link
Copy Markdown
Contributor

Fixes #1765

The full-build call-resolution pipeline (resolveFallbackTargets in src/domain/graph/builder/stages/build-edges.ts) retries a no-receiver call as <CallerClass>.<callName> when global resolution fails — this is how C#/Java-style static sibling calls (e.g. IsValidEmail() inside Validators.ValidateUser) resolve to Validators.IsValidEmail. The incremental single-file rebuild path (src/domain/graph/builder/incremental.ts, used by watch mode) only implemented the same-class this.method() and Object.defineProperty fallbacks — the class-scoped bare-call fallback, and the isModuleScopedLanguage import it needs, were entirely missing.

Fix

Rather than duplicating the fallback logic into incremental.ts (which is exactly how it drifted out of sync with build-edges.ts in the first place), this consolidates the two same-class fallback strategies into the shared src/domain/graph/builder/call-resolver.ts module — already the home of the underlying resolveSameClassQualifiedMethod helper both pipelines call:

  • resolveSameClassThisFallback and resolveSameClassBareCallFallback move from build-edges.ts (private) to call-resolver.ts (exported), unchanged in behavior.
  • build-edges.ts's resolveFallbackTargets now imports and calls the shared versions instead of its own local copies.
  • incremental.ts's fallback dispatcher (renamed applyThisReceiverFallbacksapplyCallResolutionFallbacks, since it now covers more than this-receiver calls) wires in the bare-call strategy between the existing this-fallback and the defineProperty accessor fallback, mirroring resolveFallbackTargets's strategy order exactly.

Both pipelines now call the identical exported functions, so they cannot drift apart again.

Scope note

While investigating, I found two more full-build fallback strategies (resolveKotlinReflectionPreQualified, resolveReflectionKeyExprFallback) that are also missing from the incremental path. Filed as #1946 — out of scope for this PR to keep it to a single concern.

Validation

  • npx tsc --noEmit — clean.
  • npm run lint — clean.
  • New unit tests (tests/unit/call-resolver.test.ts) directly exercise resolveSameClassThisFallback/resolveSameClassBareCallFallback in isolation with a lookup mock scoped to byNameAndFile only, proving each fallback resolves via the file-scoped lookup independent of the primary resolution path's own embedded same-class handling.
  • New integration test (tests/integration/issue-1765-incremental-bare-call-fallback.test.ts) with a C# fixture (two classes, colliding method names) verifies an incremental rebuild produces byte-identical calls edges to a full build, including a same-class bare-call resolution and a check that it does not false-positive across the sibling class.
  • Ran the full test suite after building all 34 WASM grammars locally (this worktree started with an incomplete grammar cache unrelated to this change): 3253 passed, 339 skipped, 0 regressions. The only remaining failures (10 files) require the prebuilt native addon, which isn't installed in this worktree — a pre-existing environment gap, not something this change touches (no Rust/native code changed).
  • tests/benchmarks/resolution/resolution-benchmark.test.ts (precision/recall across all 34 languages): 206/206 passed.
  • codegraph diff-impact --staged -T shows a contained blast radius: the two relocated/renamed functions and their transitive callers in incremental.ts, plus the new fixture.

The full-build call-resolution pipeline (stages/build-edges.ts) retries a
no-receiver call as <CallerClass>.<callName> when global resolution fails --
this is how C#/Java-style static sibling calls (e.g. IsValidEmail() inside
Validators.ValidateUser) resolve to Validators.IsValidEmail. The incremental
single-file rebuild path (incremental.ts, used by watch mode) only
implemented the same-class this.method() and Object.defineProperty
fallbacks, leaving class-scoped bare calls without a matching fallback.

Consolidates resolveSameClassThisFallback and resolveSameClassBareCallFallback
into the shared call-resolver.ts module (previously private to build-edges.ts)
so the full-build and incremental pipelines call the exact same functions and
cannot drift out of sync again. incremental.ts's applyCallResolutionFallbacks
(renamed from applyThisReceiverFallbacks, since it now covers more than
this-receiver calls) wires in the bare-call strategy between the existing
this-fallback and the defineProperty accessor fallback, mirroring
resolveFallbackTargets's strategy order in build-edges.ts.
@greptile-apps

greptile-apps Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a parity gap between the full-build and incremental (watch-mode) call-resolution pipelines: the incremental path was missing the same-class bare-call fallback (resolveSameClassBareCallFallback) that build-edges.ts uses to resolve C#/Java-style static sibling calls. Rather than duplicating the logic, both fallback helpers are promoted from private functions in build-edges.ts to exported functions in call-resolver.ts, which both pipelines now share.

  • resolveSameClassThisFallback and resolveSameClassBareCallFallback are moved from build-edges.ts (private) to call-resolver.ts (exported), with a re-export of isModuleScopedLanguage already in place so the language guard is included automatically.
  • incremental.ts's dispatcher is renamed applyCallResolutionFallbacks and wires in the new bare-call strategy between the existing this-fallback and the defineProperty fallback, matching the order in resolveFallbackTargets.
  • New unit tests cover each exported fallback function in isolation; a new integration test validates that an incremental rebuild of a two-class C# fixture produces byte-identical calls edges to a full build, including the cross-class non-resolution guard.

Confidence Score: 4/5

Safe to merge — the change is a well-scoped refactor that promotes two private helpers to shared exports without altering their logic, and the integration test directly validates that an incremental rebuild of the C# fixture produces byte-identical call edges to a full build.

The core fix is correct and the test coverage is strong. The only open point is the inline JSDoc in applyCallResolutionFallbacks claiming it mirrors the full-build pipeline when two fallbacks (Kotlin pre-qualified pass, reflection key expr) are still missing from the incremental path — a gap the PR description explicitly defers to #1946. This does not affect runtime behavior of the fix itself, but leaves the comment slightly misleading for future readers of that function.

incremental.ts — the JSDoc for applyCallResolutionFallbacks overstates parity with resolveFallbackTargets; the remaining divergences should be noted inline so future engineers do not assume the two pipelines are fully aligned.

Important Files Changed

Filename Overview
src/domain/graph/builder/call-resolver.ts Promotes resolveSameClassThisFallback and resolveSameClassBareCallFallback to exported functions; logic is unchanged from the private originals in build-edges.ts.
src/domain/graph/builder/incremental.ts Adds the missing bare-call fallback (Strategy 2) to applyCallResolutionFallbacks; JSDoc says it 'mirrors' resolveFallbackTargets but two fallbacks (Kotlin pre-qualified pass, reflection key expr) are still absent and filed separately as #1946.
src/domain/graph/builder/stages/build-edges.ts Removes the now-duplicated private fallback functions and imports the shared versions from call-resolver.ts; isModuleScopedLanguage is still used directly at two other sites so its import is not dead.
tests/fixtures/bare-call-scope/Validators.cs New C# fixture with two classes sharing an IsValidEmail method name; covers both the positive same-class resolution and the cross-class non-resolution guard.
tests/integration/issue-1765-incremental-bare-call-fallback.test.ts Integration test verifies incremental and full-build edges are byte-identical on the fixture; correctly touches both copies before their respective builds and cleans up temp directories.
tests/unit/call-resolver.test.ts Adds unit tests for resolveSameClassThisFallback and resolveSameClassBareCallFallback using a scoped lookup that only satisfies byNameAndFile, proving each helper resolves via the file-scoped lookup independently.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant BE as build-edges.ts (full build)
    participant INC as incremental.ts (watch mode)
    participant CR as call-resolver.ts (shared)

    note over BE,INC: Before this PR — bare-call fallback only in full build
    BE->>CR: resolveSameClassThisFallback() [private copy]
    BE->>CR: resolveSameClassBareCallFallback() [private copy]
    INC->>CR: resolveSameClassQualifiedMethod() [no language guard]
    note over INC: bare-call fallback missing

    note over BE,INC: After this PR — both pipelines share exported helpers
    BE->>CR: resolveSameClassThisFallback() [exported]
    BE->>CR: resolveSameClassBareCallFallback() [exported, incl. isModuleScopedLanguage guard]
    INC->>CR: resolveSameClassThisFallback() [exported]
    INC->>CR: resolveSameClassBareCallFallback() [exported, incl. isModuleScopedLanguage guard]
    note over INC: bare-call fallback now present
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant BE as build-edges.ts (full build)
    participant INC as incremental.ts (watch mode)
    participant CR as call-resolver.ts (shared)

    note over BE,INC: Before this PR — bare-call fallback only in full build
    BE->>CR: resolveSameClassThisFallback() [private copy]
    BE->>CR: resolveSameClassBareCallFallback() [private copy]
    INC->>CR: resolveSameClassQualifiedMethod() [no language guard]
    note over INC: bare-call fallback missing

    note over BE,INC: After this PR — both pipelines share exported helpers
    BE->>CR: resolveSameClassThisFallback() [exported]
    BE->>CR: resolveSameClassBareCallFallback() [exported, incl. isModuleScopedLanguage guard]
    INC->>CR: resolveSameClassThisFallback() [exported]
    INC->>CR: resolveSameClassBareCallFallback() [exported, incl. isModuleScopedLanguage guard]
    note over INC: bare-call fallback now present
Loading

Fix All in Claude Code

Reviews (1): Last reviewed commit: "fix: add missing same-class bare-call fa..." | Re-trigger Greptile

Comment on lines +650 to +654
* Runs in order (mirrors `resolveFallbackTargets` in the full-build
* `stages/build-edges.ts` pipeline — see #1765):
* 1. Same-class `this.method()` fallback.
* 2. Same-class bare-call fallback for non-JS/TS class-scoped languages.
* 3. Object.defineProperty accessor fallback (this-calls inside getter/setter).

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 The JSDoc says this function "mirrors resolveFallbackTargets" but it still omits two fallbacks that are present in the full-build pipeline: the Kotlin reflection pre-qualified pass (run before primary resolution) and resolveReflectionKeyExprFallback (run between the bare-call and defineProperty strategies). The PR description documents these gaps and files them as #1946, but a reader of only this function would incorrectly assume full parity. A short note in the comment would prevent future confusion.

Suggested change
* Runs in order (mirrors `resolveFallbackTargets` in the full-build
* `stages/build-edges.ts` pipeline see #1765):
* 1. Same-class `this.method()` fallback.
* 2. Same-class bare-call fallback for non-JS/TS class-scoped languages.
* 3. Object.defineProperty accessor fallback (this-calls inside getter/setter).
* Runs in order (partially mirrors `resolveFallbackTargets` in the full-build
* `stages/build-edges.ts` pipeline see #1765 for this fix, #1946 for the
* remaining divergences: Kotlin reflection pre-qualified pass and
* resolveReflectionKeyExprFallback are not yet wired into the incremental path):
* 1. Same-class `this.method()` fallback.
* 2. Same-class bare-call fallback for non-JS/TS class-scoped languages.
* 3. Object.defineProperty accessor fallback (this-calls inside getter/setter).

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Codegraph Impact Analysis

10 functions changed11 callers affected across 4 files

  • resolveSameClassThisFallback in src/domain/graph/builder/call-resolver.ts:78 (7 transitive callers)
  • resolveSameClassBareCallFallback in src/domain/graph/builder/call-resolver.ts:99 (7 transitive callers)
  • applyCallResolutionFallbacks in src/domain/graph/builder/incremental.ts:656 (5 transitive callers)
  • buildCallEdges in src/domain/graph/builder/incremental.ts:744 (5 transitive callers)
  • Validators in tests/fixtures/bare-call-scope/Validators.cs:3 (0 transitive callers)
  • Validators.ValidateUser in tests/fixtures/bare-call-scope/Validators.cs:5 (0 transitive callers)
  • Validators.IsValidEmail in tests/fixtures/bare-call-scope/Validators.cs:10 (1 transitive callers)
  • Formatters in tests/fixtures/bare-call-scope/Validators.cs:16 (0 transitive callers)
  • Formatters.IsValidEmail in tests/fixtures/bare-call-scope/Validators.cs:19 (0 transitive callers)
  • Formatters.FormatName in tests/fixtures/bare-call-scope/Validators.cs:24 (0 transitive callers)

@carlos-alm

Copy link
Copy Markdown
Contributor Author

Closing — this duplicates already in-progress work on #1886 for the same issue (#1765). Continuing the existing stacked chain (#1885#1939) instead of a competing branch.

@carlos-alm carlos-alm closed this Jul 7, 2026
@carlos-alm carlos-alm deleted the fix/issue-1765-incremental-rebuild-missing-same-class-bare branch July 7, 2026 08:20
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 7, 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.

1 participant