Skip to content

fix(go): generic composite literal constructor inference (F33)#1976

Merged
magyargergo merged 11 commits into
abhigyanpatwari:mainfrom
evander-wang:codex-go-f33-generic-composite-literals
Jun 3, 2026
Merged

fix(go): generic composite literal constructor inference (F33)#1976
magyargergo merged 11 commits into
abhigyanpatwari:mainfrom
evander-wang:codex-go-f33-generic-composite-literals

Conversation

@evander-wang

@evander-wang evander-wang commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

What changed

Go generic composite literal constructors (e.g. Box[User]{}) were not captured by the scope-resolution pipeline. The tree-sitter parser produces generic_type nodes for these patterns, but the Go ingestion code only handled plain type_identifier nodes in composite literal positions.

This is Go language coverage gap F33.

Why

Generic composite literals are a core Go 1.18+ feature. Without this fix, any codebase using pkg.T[Type]{} patterns gets no constructor-inferred type binding, producing missing CALLS edges and incomplete call graphs.

How

  • Extended tree-sitter queries (query.ts): Added (generic_type) to constructor detection patterns in GO_SCOPE_QUERY
  • New normalization (captures.ts): normalizeGenericConstructorCapture() strips type parameters from generic_type nodes to extract the base type name (Box[User]Box)
  • Recursive type extraction (type-binding.ts): Enhanced extractSimpleTypeNameText() to recursively unwrap generic_type and qualified_type nesting

How to verify

cd gitnexus

# Unit tests — Go type binding (16 tests)
npx vitest run test/unit/scope-resolution/go/go-type-binding.test.ts

# Golden snapshots (9 tests)
npx vitest run test/unit/scope-resolution/go/go-captures-golden.test.ts

# Integration tests — Go resolver (136 tests)
npx vitest run test/integration/resolvers/go.test.ts

# Typecheck
npx tsc --noEmit

All pass: 16/16 unit ✅, 9/9 golden ✅, 136/136 integration ✅.

Risk and rollback

  • Risk: Low — changes isolated to Go ingestion, no shared infrastructure, backward compatible
  • Rollback: Revert the commit; non-generic constructor captures are unaffected
  • Follow-ups: Edge-case tests for nested generics (Box[Container[User]]{}) and qualified generics (pkg.Box[User]{}) can be added in a separate PR

Refs #1927

@vercel

vercel Bot commented Jun 2, 2026

Copy link
Copy Markdown

@evander-wang is attempting to deploy a commit to the NexusCore Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions

github-actions Bot commented Jun 2, 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
10941 10928 0 13 677s

✅ All 10928 tests passed

13 test(s) skipped — expand for details
  • COBOL 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)
  • 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.3% 38239/47620 79.84% 📈 +0.5 🟢 ████████████████░░░░
Branches 68.85% 24315/35314 68.5% 📈 +0.3 🟢 █████████████░░░░░░░
Functions 85.45% 3978/4655 84.94% 📈 +0.5 🟢 █████████████████░░░
Lines 83.9% 34397/40993 83.36% 📈 +0.5 🟢 ████████████████░░░░

📋 View full run · Generated by CI

@magyargergo magyargergo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

PR Tri-Review — F33 generic composite-literal constructor inference

Verdict: production-ready with minor follow-ups. No correctness, security, or performance regression was found across three review methods; the implementation is correct and the primary cases are well-tested. The remaining items are maintainability/coverage follow-ups, none merge-blocking.

Methods & engine independence (stated honestly). GitNexus risk lane + 5 Compound-Engineering personas (correctness, adversarial, maintainability, performance, testing) + Codex (gpt-5.5, xhigh). 7 of the 8 perspectives (6 review lanes + this synthesis) are Claude — their agreement is "consistent across personas," not independent confirmation. Only Codex is a different engine, so Codex-corroborated points carry the real cross-engine weight. The coordinator re-verified every load-bearing claim with real tree-sitter parses in a worktree.

What's solid (validated, not merely unchallenged):

  • Correctness lane refuted all six probed failure modes with real parses; the adversarial lane could not break it (exotic / multi-arg / *T / slice / map type-args, ~2000-level nesting ≈28 ms with no overflow, capture leakage from type-param lists / field types / signatures / new/make/assertions → no spurious edges, container-of-generic correctly non-capturing, no double-emission). Codex independently concluded "no runtime defect."
  • Strictly additive for non-generic Go: the new normalizeGenericConstructorCapture guard cannot fire for type_identifier / qualified_type nodes, so existing capture output is unchanged — zero regression surface for non-generic repos.
  • Performance: the new normalization is O(1) per match; the #1848/#1915 hot-path linearity is intact. bench --check → scaling ratio 1.144 (budget 1.5); scaling_budget correctly left unchanged, only the fingerprint rebaselined.
  • Legacy/registry split is test-accounting, not a production divergence: Go is registry-primary by default (MIGRATED_LANGUAGES), so no production consumer hits the legacy DAG; the LEGACY_RESOLVER_PARITY_EXPECTED_FAILURES entry is correctly scoped to the one new test.
  • Test runs (correctness/adversarial lanes, worktree sequential parser): Go scope-resolution unit 85/85, constructor-inference integration 9/9 (incl. the new Box test), captures-golden 9/9; bench fingerprint matches the rebaseline.

Inline findings (maintainability, non-blocking):

  1. [P2] extractSimpleTypeNameText is now duplicated verbatim in captures.ts and type-binding.ts — drift hazard.
  2. [P3] extractTypeNode's fallback gained generic_type but its sibling extractCompositeLiteralTypeNode did not — harmless today (the var x = Box[T]{} path is rescued by childForFieldName('type'); coordinator reproduced → {name:b, type:Box}), but a latent inconsistency.

Codex's independent edge case — investigated, NOT a confirmed defect. Codex flagged that generic-qualified models.Box[T]{} drops the package qualifier at capture time (emits Box), while plain models.Box{} keeps models.Box (coordinator reproduced both). Verified consequence: for the type-binding / receiver path this is provably benign — interpretGoTypeBindingnormalizeGoTypeName strips both the qualifier and the [...], so plain and generic both reduce to Box (interpret.ts:37, 93–96); any shadowing would already affect plain models.Box{}, so this PR introduces no regression there. The reference/CALLS path is untested under shadowing (a package that both declares a local Box and instantiates an imported models.Box[T]), but Codex itself downgraded this to "coverage, not a runtime defect" after tracing the call path, and the non-shadowing case is proven correct by the new integration test. Recommend a shadowed-qualified-generic regression test to close it.

Coverage gaps (nice-to-have; behaviors verified to work but untested): var x = Box[T]{} (var-spec path), multi-assign generic (a, b := X{}, Box[int]{}), nested generic (Box[Container[int]]{}), multi-type-arg (Box[K,V]{}), qualified generic at the unit level, and a negative assertion that the type argument models.User does not create a spurious User edge (adversarial confirmed it doesn't). Separately, the scope-capture bench synthetic Go source contains no generic literals, so the F33 path isn't exercised at the 250/800-entity scale — perf for it rests on the O(1)-per-match analysis plus the 1.144 measurement on the small fixture; one generic literal in the bench generator would gate it at scale.

Merge state & branch hygiene. mergeable=MERGEABLE (no conflicts) but mergeStateStatus=BLOCKEDchecks failing. Branch hygiene: one merge-from-main commit (3412d4c0) present but harmless and merge-safe (no overlap with the F33 production files); the other three commits are the F33 work.

CI. All substantive gates green — typecheck, lint, format, benchmarks, scope-resolution parity, coverage, tree-sitter ABI (all platforms), CodeQL, both Build & Push, macOS platform-sensitive. Failing: Vercel (deploy-auth gate, not code) and tests / windows-latest (platform-sensitive)CI Gate. The Windows failure is a pre-existing, unrelated flake: 8 cli-e2e.test.ts failures about CLI stdout / fd-1 / EPIPE handling and the eval-server "No indexed repositories found" startup (cli-e2e.test.ts:1242/1259/1270/1283/1334/289) — this PR touches only Go ingestion code, with no CLI/eval-server/stdout surface, so it cannot be the cause.


Automated multi-tool digest — 6 Claude review lanes + Codex (gpt-5.5, xhigh), one independent engine; coordinator-verified against real parses. Treat as input to your judgment, not a substitute — verify before acting.

}
}

function extractSimpleTypeNameText(node: SyntaxNode): string {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2 · maintainability · code-read] This extractSimpleTypeNameText is byte-identical to the copy in type-binding.ts:247–257 (same qualified_type handling + the new generic_type branch). Two copies will drift independently the next time a node type needs unwrapping. captures.ts already imports from type-binding.ts (line 13), so exporting the existing definition there and importing it here removes the duplicate with zero behavior change. Non-blocking.

expr.childForFieldName('type') ??
expr.namedChildren.find((c) => ['type_identifier', 'qualified_type'].includes(c.type)) ??
expr.namedChildren.find((c) =>
['type_identifier', 'qualified_type', 'generic_type'].includes(c.type),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P3 · maintainability/consistency · reproduced] This fallback now includes generic_type, but the sibling extractCompositeLiteralTypeNode (line 264, the var-spec path) still lists only ['type_identifier', 'qualified_type']. It's harmless today — var x = Box[T]{} is rescued by childForFieldName('type') (reproduced: yields {name:'b', type:'Box'}), so this fallback is effectively dead code — but the asymmetry is a latent trap and the var-form generic case currently has no test. Suggest mirroring the list here (or adding a var-form generic test). Non-blocking.

expr.childForFieldName('type') ??
expr.namedChildren.find((c) => ['type_identifier', 'qualified_type'].includes(c.type)) ??
expr.namedChildren.find((c) =>
['type_identifier', 'qualified_type', 'generic_type'].includes(c.type),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can we move this array into a set so we won't need to recreate it in every iteration? Plus we can reuse it at line 282.

if (result.size === 0) return []; // Early exit: no struct has all required methods
}
return best === undefined ? [...indexes.structsById.keys()] : [...best];
return result === undefined ? [...indexes.structsById.keys()] : [...result];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If we are carrying a lot of elements in either case, it will result in stackoverflow because of the spread operator.

@magyargergo magyargergo merged commit 5dcffde into abhigyanpatwari:main Jun 3, 2026
30 of 31 checks passed
@magyargergo magyargergo linked an issue Jun 4, 2026 that may be closed by this pull request
5 tasks
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.

Go: parsing-layer coverage gaps (5 findings)

2 participants