Skip to content

fix(cpp): resolve cross-namespace same-tail inheritance bases bridge-held (#1993)#2005

Merged
magyargergo merged 3 commits into
mainfrom
fix/1993-cpp-ns-qualified-inheritance
Jun 4, 2026
Merged

fix(cpp): resolve cross-namespace same-tail inheritance bases bridge-held (#1993)#2005
magyargergo merged 3 commits into
mainfrom
fix/1993-cpp-ns-qualified-inheritance

Conversation

@magyargergo

Copy link
Copy Markdown
Collaborator

Stacked PR 3/5 — base fix/1995-cpp-union-anon-ns-collision (#2004). Auto-retargets when #2004 merges.

Summary

Follow-up to #1981 / #1982. PR #1981 landed a targeted bridge (tagNamespacePrefixes + a resolveDefGraphId namespace-prefixed retry) that fixes the within-namespace same-tail heritage case (NS::A::Inner vs NS::B::Inner, distinguished by the class chain). The residual: a cross-namespace same-tail base (NS1::A::Inner vs NS2::A::Inner) — both key the namespace-omitted A.Inner in the qualifiedNames resolution index, so resolveQualifiedInheritanceBase could not pick a winner and the deriving classes cross-wired (DB's EXTENDS bound to NS1's A::Inner).

Fixed bridge-held — without flipping the class-chain-only qualifiedName invariant (which would re-bite two-phase lookup / UDC / brace-init) and without re-keying the resolution index.

Fix

The namespacePrefix sidecar (set by tagNamespacePrefixes, deliberately separate from qualifiedName) already records each namespaced def's enclosing namespace. Two sidecar-only changes:

  1. tagNamespacePrefixes now also tags defs declared directly in a namespace (e.g. the deriving NS1::DA), composed identically to the existing class-nested path. The original loop only reached class-nested defs (A::Inner).
  2. resolveQualifiedInheritanceBase, on a same-tail tie at a key, breaks the tie by preferring the candidate whose namespacePrefix matches the deriving class's — the base in the same enclosing namespace. It still refuses when the sidecar can't pick a unique.

def.qualifiedName and the qualifiedNames index keys are untouched, so two-phase lookup, UDC ranking, brace-init, and file-local linkage are unaffected. The deferred root-cause work (making qualifiedName namespace-inclusive and removing the bridge) is unchanged and out of scope.

Tests

New cpp-cross-namespace-same-tail fixture + test (registry-primary, in the cpp parity expected-failures like the #1982 namespaced tests): NS1.DA EXTENDS NS1.A.Inner and NS2.DB EXTENDS NS2.A.Inner — positive base-identity by the resolved node's qualifiedName, which fails pre-fix (DB cross-wired to NS1.A.Inner).

Verification

Closes #1993.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Jun 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
gitnexus Ready Ready Preview, Comment Jun 4, 2026 9:08am

Request Review

@magyargergo

Copy link
Copy Markdown
Collaborator Author

Tri-review — approve-with-nits (1 blocker being addressed in-PR)

Method: per-PR multi-lens review → independent adversarial re-verification → static-analysis alignment (tsc/eslint/prettier, all clean) → cross-stack synthesis + critic gate.

Review: PR #2005 — C++ cross-namespace same-tail heritage tie-break (#1993)

Verdict: approve-with-nits. The fix is correct on the real runtime path, language-neutral, well-contained, and additive. The one thing I'd want before merge is a worker-path parity test, to match the precedent set by both predecessor lanes in this stack and the DoD's sequential≡worker requirement.

What's good

  • Sound mechanism, minimal blast radius. Sidecar-only: def.qualifiedName and the qualifiedNames index keys are untouched, so two-phase lookup / UDC / brace-init / file-local linkage are unaffected. The deriving-side tag (namespace-direct → NS1) and base-side tag (class-nested → NS1) compose identically and compare equal; nsCount===1 selects, otherwise refuse-on-tie is preserved.
  • Object-identity invariant holds. buildDefIndex/buildQualifiedNameIndex are built from file.localDefs, which share instances with Scope.ownedDefs (documented at walkers.ts:763), and the mutation runs in populateOwners before finalize — so the sidecar is visible via scopes.defs.
  • Non-overlapping loops. Each def is owned by exactly one scope; the class-nested loop skips Namespace scopes and the new loop only processes them, plus the new loop guards namespacePrefix !== undefined before writing. No double-tag.
  • Language-neutrality preserved. The shared walkers.ts change names no languages — it gates on kind==='Namespace'/type==='Namespace' (namespace-free languages are a no-op). C++ activation stays behind the populateOwners provider hook (AGENTS.md:42). ✅
  • Positive identity assertion + dual parity legs. The test resolves the EXTENDS source by qualifiedName and asserts the target's qualifiedName (not dangle-only), and discriminates the cross-wire. createResolverParityIt('cpp') + the byte-matching expected-failures entry (helpers.ts:553cpp.test.ts:4213) give both registry-primary and legacy legs. tsc clean; no CHANGELOG/golden/snapshot touched.

Findings

Major

Minor

  • Narrative contradiction on the pre-fix failure mode (cpp-cross-namespace-same-tail/main.cpp:1-8). The fixture says "silent miss, not a miswire"; the PR summary + test comment say DB "cross-wired to NS1.A.Inner." Pre-fix, the count>1 branch returns undefined (DA untagged) → simple-name fall-through, whose outcome could be a miss or a mis-bind. Both fail the assertion pre-fix, so fail-pre/pass-post holds — but align the three descriptions.

Nits

Nice, surgical fix that stays within the established sidecar bridge rather than flipping the qualifiedName invariant.

magyargergo added a commit that referenced this pull request Jun 3, 2026
…orrect narrative

Add the missing parse-worker.ts parity describe for the #1993 cross-namespace
same-tail heritage tie-break, mirroring the #1982/#1995 worker siblings
(workerThresholdsForTest minFiles:1/minBytes:1, workerPoolSize:2, usedWorkerPool
guard, and the same NS1.DA→NS1.A.Inner / NS2.DB→NS2.A.Inner base assertions), and
register both worker test names in LEGACY_RESOLVER_PARITY_EXPECTED_FAILURES['cpp']
(registry-primary-only, like the sequential entry). Closes the DoD sequential≡worker
gap flagged in the tri-review of PR #2005.

Also correct the fixture/test narrative: the pre-fix failure is a CROSS-WIRE (DB's
EXTENDS binds NS1::A::Inner via the refuse-on-tie scope-walk fallback), not a silent
miss — the empirical pre-fix run shows the edge exists but points at the wrong target.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@magyargergo

Copy link
Copy Markdown
Collaborator Author

✅ Blocker resolved + narrative corrected (review follow-up)

Worker-path parity test added (355f08df). The #1993 describe now ships the missing parse-worker.ts sibling — mirrors the #1982/#1995 worker blocks (workerThresholdsForTest {minFiles:1,minBytes:1}, workerPoolSize:2, a usedWorkerPool===true guard, and the same NS1.DA→NS1.A.Inner / NS2.DB→NS2.A.Inner base assertions). Both worker test names are registered in LEGACY_RESOLVER_PARITY_EXPECTED_FAILURES['cpp'] (registry-primary-only, like the sequential entry).
Verified: primary 3/3 (worker pool genuinely used + correct resolution), legacy 217 passed / 80 skipped (the two new worker its skip cleanly). DoD sequential≡worker now holds.

Narrative correction — the review had this backwards. The synthesis recommended changing the PR body from "cross-wire" to "silent miss". The empirical pre-fix run is a cross-wire, not a silent miss: baseQnOf('NS2.DB') returned 'NS1.A.Inner' — an existing EXTENDS edge to the wrong target (a true silent miss would have tripped the toBeDefined() guard first). Pre-fix, resolveQualifiedInheritanceBase refuses-on-tie and the scope-walk fallback first-wins to NS1's Inner, so DB binds NS1::A::Inner (DA resolves correctly only by that first-wins luck). So the PR body was already correct; I corrected the fixture/test comments (which wrongly said "silent miss") to match — same commit.

Re-stacked: #2006/#2007 rebased onto the updated #1993; chain stays linear, all PRs MERGEABLE.

magyargergo and others added 3 commits June 4, 2026 08:59
…held (#1993)

PR #1981's bridge fixed within-namespace same-tail heritage (NS::A::Inner vs NS::B::Inner). The residual: a cross-namespace same-tail base (NS1::A::Inner vs NS2::A::Inner) both key the namespace-omitted `A.Inner` in the qualifiedNames index, so resolveQualifiedInheritanceBase couldn't pick a winner and the deriving classes cross-wired (DB's EXTENDS bound to NS1's A::Inner).

Fixed bridge-held via the existing `namespacePrefix` sidecar — no qualifiedName invariant flip, no resolution-index re-keying: (1) tagNamespacePrefixes also tags defs declared directly in a namespace (the deriving NS1::DA), composed identically to the class-nested path; (2) resolveQualifiedInheritanceBase breaks a same-tail tie by preferring the candidate whose namespacePrefix matches the deriving class's. Two-phase lookup, UDC, brace-init, file-local linkage untouched (def.qualifiedName + index keys unchanged).

New cpp-cross-namespace-same-tail fixture + registry-primary test (in the cpp parity expected-failures). Verified: cpp suite 287/287 primary, 209 + 78 skips legacy — no regression; tsc + prettier clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…orrect narrative

Add the missing parse-worker.ts parity describe for the #1993 cross-namespace
same-tail heritage tie-break, mirroring the #1982/#1995 worker siblings
(workerThresholdsForTest minFiles:1/minBytes:1, workerPoolSize:2, usedWorkerPool
guard, and the same NS1.DA→NS1.A.Inner / NS2.DB→NS2.A.Inner base assertions), and
register both worker test names in LEGACY_RESOLVER_PARITY_EXPECTED_FAILURES['cpp']
(registry-primary-only, like the sequential entry). Closes the DoD sequential≡worker
gap flagged in the tri-review of PR #2005.

Also correct the fixture/test narrative: the pre-fix failure is a CROSS-WIRE (DB's
EXTENDS binds NS1::A::Inner via the refuse-on-tie scope-walk fallback), not a silent
miss — the empirical pre-fix run shows the edge exists but points at the wrong target.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…pp bench baseline (#1993)

F4 follow-up to #1993: declare `namespacePrefix?: string` on SymbolDefinition
(gitnexus-shared) and drop the six `as { namespacePrefix?: string }` casts in
walkers.ts / graph-bridge/ids.ts that #1993 introduced. Pure type-level — the `as`
assertions erase at compile time, runtime is byte-identical, and the field stays a
sidecar (no graph-node identity; the qualifiedName-keyed index is untouched).

Also regenerate the cpp scope-capture bench baseline: rebased onto main (now
carrying #1995's cpp fixtures), #1993 adds cpp-cross-namespace-same-tail, growing
the cpp-* corpus 272->273 and drifting the fingerprint d63ded6->6d6207ae. Pure
fixture-corpus drift — no scope-extractor change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

CI Report

All checks passed

Pipeline Status

Stage Status Details
✅ Typecheck success tsc --noEmit
✅ Tests success unit tests, 3 platforms
✅ E2E success gitnexus-web changes only

Test Results

Tests Passed Failed Skipped Duration
11134 11118 0 16 697s

✅ All 11118 tests passed

16 test(s) skipped — expand for details
  • COBOL pipeline benchmark > scales with file count
  • C++ ADL emit benchmark > emit phase scales sub-quadratically with co-scaled files and sites
  • C++ pipeline benchmark > scales with file count
  • C# pipeline benchmark > scales with file count — namespaces spread across the solution
  • C# pipeline benchmark > scales with file count — all types in one (global) namespace bucket
  • C# pipeline benchmark > scales with file count — all types in one (named) namespace bucket
  • Go pipeline benchmark > scales with file count (workers enabled)
  • Go pipeline benchmark — worker pool (issue Worker idle timeout kills long Go scope extraction and surfaces as Napi::Error during analyze #1848) > does not quarantine the large generated Go file on sub-batch idle timeout
  • Go structural interface detection benchmark > scales linearly with interface × struct count
  • Go structural interface detection split-phase benchmark > separates index-build and detection time
  • PHP pipeline benchmark > scales with file count (workers enabled)
  • Ruby pipeline benchmark > scales with file count (workers enabled)
  • Rust pipeline benchmark > scales with file count (workers enabled)
  • Vue pipeline benchmark > scales with component count
  • run.cjs direct-exec entrypoint (fix(cli): steer docs, skills, and hooks through a CLI-neutral project-local runner (#1939) #1945) > resolves a .cmd shim via the Windows shell branch, passing args and exit code
  • buildTypeEnv > known limitations (documented skip tests) > Ruby block parameter: users.each { |user| } — closure param inference, different feature

Code Coverage

Tests

Metric Coverage Covered Base Delta Status
Statements 80.38% 38911/48407 N/A% 🟢 ████████████████░░░░
Branches 68.96% 24752/35892 N/A% 🟢 █████████████░░░░░░░
Functions 85.6% 4049/4730 N/A% 🟢 █████████████████░░░
Lines 83.98% 35010/41687 N/A% 🟢 ████████████████░░░░

📋 View full run · Generated by CI

@magyargergo magyargergo merged commit 9f3bcee into main Jun 4, 2026
31 checks passed
@magyargergo magyargergo deleted the fix/1993-cpp-ns-qualified-inheritance branch June 4, 2026 09:39
magyargergo added a commit to magyargergo/GitNexus that referenced this pull request Jun 4, 2026
…o refactor/942-delete-legacy-resolution

Two conflicts resolved:
- bench/scope-capture/baselines.json: same regenerate recipe (take main's
  entry set, re-measure merged tree, re-apply the 6 abhigyanpatwari#942 fixture-scrub
  fingerprints). Bench --check PASS (14 langs).
- test/integration/resolvers/helpers.ts: abhigyanpatwari#2005 added cpp entries to
  LEGACY_RESOLVER_PARITY_EXPECTED_FAILURES, a ~665-line legacy-parity map
  that abhigyanpatwari#942 deletes wholesale. Keep abhigyanpatwari#942's deletion (the map is dead once the
  legacy leg is gone); discard abhigyanpatwari#2005's additions to it. No resolver test
  references the map (tsc clean). abhigyanpatwari#2005's 67 new cpp.test.ts cases auto-merge
  and run as plain scope-resolution tests: cpp.test.ts 297 pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

fix(cpp): scope-model def.qualifiedName drops the enclosing namespace (root cause behind #1982's targeted heritage bridge)

1 participant