mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-03 02:21:44 +00:00
2 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e275826236
|
fix(csharp): eliminate global-namespace typeBindings O(files²) OOM (#1871) (#1954)
* fix(csharp): eliminate global-namespace typeBindings O(files²) OOM (#1871) Large C# solutions with tens of thousands of files in the global (unnamed) namespace OOM'd / hung for hours at "Resolving types (Csharp 2/3)". PR #1905 fixed the BindingRef twin of this via the `workspaceFqnBindings` fast-path, but left the typeBindings propagation loop in `populateCsharpNamespaceSiblings` untouched: it copies every global file's module-scope return-type bindings into every OTHER global file's `Scope.typeBindings`. With S files in the `''` bucket and K distinct method names, that is O(S²) time and O(S·K) memory — ~1.3B Map entries (~65-130 GB) at 36k files. Measured on a concentrated global-namespace fixture: the per-file copy went quadratic (1000→2000 files = 3.06× for 2× the files, 65s at 2000). Route global-namespace module typeBindings through a new scope-independent `workspaceTypeBindings` channel populated ONCE (O(K)) and consulted as a fallback by the typeBindings chain-walkers (`findReceiverTypeBinding`, `followChainPostFinalize`), instead of the per-file copy. After: 2000 files 6.3s, 4000 files 6.8s, heap linear. This also makes resolution MORE correct, not just faster. The C# spec makes the unnamed namespace a single declaration space whose members are "available for use in a named namespace", so global types are visible from every file. The old per-file copy only exposed them to OTHER no-namespace files; named-namespace files never saw them. Consulting the shared channel from every scope chain mirrors how Roslyn resolves against a single `Compilation.GlobalNamespace` symbol rather than copying symbols per file. Strengthen csharp-pipeline-benchmark.test.ts so it would catch this: give each file a unique method name (a shared name collapses the module-typeBinding key and skips all copies, hiding the blow-up) and raise the concentrated scales to 2000 so the sub-quadratic assertion trips on the regression. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(csharp): generalize shared-channel resolution to concentrated named namespaces (#1871) #1954 eliminated the namespace-siblings O(files²) OOM only for the global ('' / no-declared-namespace) bucket. A solution with all files under one named namespace (e.g. file-scoped `namespace Company.Product;`, common in modern .NET) still reproduced the #1871 blow-up — and in BOTH loops: the BindingRef per-scope augmentation (#1905's twin) AND the typeBindings per-file copy (#1954's twin) were each still O(N²) for a named bucket. Generalize the shared-channel approach to named namespaces: - Add namespace-keyed channels `namespaceFqnBindings` / `namespaceTypeBindings` (the per-namespace analogues of `workspaceFqnBindings` / `workspaceTypeBindings`) plus `accessibleNamespacesByScope`, populated ONCE per named bucket from the existing `expandedNamespaces` derivation — O(defs), not O(files × defs). - Make the shared walkers (`findReceiverTypeBinding`, `lookupBindingsAt`, `followChainPostFinalize`) namespace-aware: after the per-scope chain and the flat global channel miss, consult the per-namespace channels gated by the caller module's accessible namespaces. Language-neutral — only the C# hook populates the channels; the machinery names no language (AGENTS rule). - Precedence preserved: local chain → named namespace → global. Named is consulted before the flat global channel because pre-#1871 named siblings lived in the chain / bindingAugmentations (above the workspace channel), so a name in both a named and the global namespace must still resolve named-first. - `using static` member exposure and the global '' fast-paths are unchanged. Parity-neutral: `run-parity.ts --language csharp` passes (legacy DAG == registry-primary, 218 tests each); the C# resolver suite (386 tests) is green. Measured: a concentrated named namespace at 500/1000/2000 files now scales linearly (~0.57×) and ~5.6s at 2000 files, vs the quadratic blow-up before. Tests: - New always-run unit coverage for the walker fallbacks (namespace-channel-lookup.test.ts): global `workspaceTypeBindings` (the #1954 channel previously covered only by a gated benchmark), namespace gating / no-leak, named-before-global precedence, local shadowing, loop termination. - Extend the immutability validator + invariant I8 to the new channels and `workspaceTypeBindings`; update the `mkIndexes` factory. - Add a concentrated-NAMED-namespace shape to the C# pipeline benchmark with the sub-quadratic scaling assertion and an edge-count sanity check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
26894d835b
|
fix(csharp): eliminate namespace-siblings OOM and worker-path re-parse (#1905)
* fix(csharp): eliminate O(S·D) BindingRef OOM in namespace siblings Types declared in the C# global (default) namespace are visible from every file, so the previous per-scope augmentation materialized O(scopes × defs) BindingRefs — on large Unity solutions (tens of thousands of global types) this caused severe slowness and OOM. Route global-namespace types through a single workspace-level binding channel (workspaceFqnBindings, consulted by lookupBindingsAt) for O(D) memory. Also fix quadratic costs in the non-global path: append defs in place instead of copying (was O(D²) per bucket), pre-index the first scope per file (was O(S²·D)), and seed de-dup sets instead of repeated .some scans. Add csharp-pipeline-benchmark.test.ts (mirrors the PHP benchmark) with spread and concentrated-global-namespace scenarios to track elapsedMs, peakHeapMB, nodeCount, and edgeCount. Post-fix runs show linear scaling and stable heap. Co-authored-by: Cursor <cursoragent@cursor.com> * perf(csharp): scanner fallback for namespace siblings on the worker path Worker threads can't return tree-sitter Trees across MessageChannels, so the cross-phase tree cache is empty for worker-parsed files. The C# same-namespace pass (populateCsharpNamespaceSiblings -> extractFileStructure) then re-parsed every file with tree-sitter to find namespace / using-static nodes — effectively parsing a large solution a second time during scope resolution. Add a line-scanner fallback (extractCsharpStructureViaScanner) used only when no cached Tree is available, mirroring PHP's fix for issue #1741. It extracts the same namespaces / usingStaticPaths the AST walk produces for the common line-anchored forms (file-scoped + block namespaces, plain / global / aliased `using static`). The AST walk stays authoritative on the sequential / warm-cache path. Micro-benchmark over 3000 synthetic files: scanner is ~188x faster than parse+walk (0.001 vs 0.251 ms/file) with identical output on the parity spot-check; real-world files are larger, so the worker-path saving is bigger. Adds csharp-namespace-extraction.test.ts (12 cases) covering all declaration forms plus negative cases (using var, plain using, comments). Co-authored-by: Cursor <cursoragent@cursor.com> * chore(autofix): apply prettier + eslint fixes via /autofix command * fix(csharp): cover global-namespace workspaceFqnBindings path + doc + using-static perf Addresses the production-readiness review of the namespace-siblings OOM fix. - Add a unit test proving global-(default-)namespace C# types route to indexes.workspaceFqnBindings (one entry per simple name) with ZERO bindingAugmentations — pinning the O(D) invariant behind the #1871 Unity-scale OOM fix and guarding against a revert to per-scope O(scopes x defs) augmentation. (The csharp-hooks mock now supplies workspaceFqnBindings, which the global fast path reads directly.) - Correct the workspaceFqnBindings doc comment: it is shared by PHP (backslash-FQN keys) and C# (global-namespace simple-name keys); the two key formats are disjoint. - Pre-index parsedFiles by path before the `using static` member-injection loop, replacing an O(files) find-per-import with an O(1) Map lookup. Verified: tsc --noEmit clean; csharp-hooks + csharp-namespace-extraction suites pass (38 tests); prettier clean; eslint 0 errors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(csharp): apply PR-review polish to namespace-siblings (tests, types, docs) Addresses the multi-agent code review of this PR — the concrete, defensible findings. Two items intentionally deferred (below). - namespace-siblings.ts: couple the augmentation bucket + its de-dup set into one nullable lifecycle, removing the seen!/bucketArr! non-null assertions (identical runtime, still lazy). - validate-bindings-immutability.ts: extend the dev-mode immutability validator to the third channel (workspaceFqnBindings) + a test; complete the validator test mock with workspaceFqnBindings. - walkers.ts: document that namesAtScope deliberately excludes the scope-independent workspaceFqnBindings channel (enumerating workspace names at every scope would flood per-scope callers; lookupBindingsAt still consults it when resolving a specific name). - scope-resolution-indexes.ts: reframe the workspaceFqnBindings doc to describe the key-format contract language-neutrally (examples, not language branching). - csharp-hooks.test.ts: assert workspace entries carry origin:'namespace'; add a partial-class test (same simple name, distinct nodeIds across global files → both kept); rename the stale "parses" cache-miss test to "scans". - csharp-pipeline-benchmark.test.ts: clearTimeout the Promise.race budget timer (dangling handle when the pipeline won the race). - csharp.test.ts: correct the #1066 comment — extractFileStructure no longer re-parses on cache miss (line scanner); only emitCsharpScopeCaptures re-parses. Deferred (surfaced, not applied): (1) worker-path scanner mis-reads namespace/using-static inside block comments and verbatim/raw strings — an explicitly documented trade-off mirroring the PHP scanner; hardening it to track comment/string state is a separate decision. (2) workspaceFqnBindings is read via an `as Map` cast; a type-safe mutable handle from finalize-orchestrator is a cross-module contract change. Verified: tsc --noEmit clean; 49 unit tests pass (incl. 3 new); prettier clean; eslint 0 errors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(csharp): harden worker-path scanner + localize workspace-map cast Addresses the two deferred PR-review findings plus the remaining test gap. #1 — Worker-path scanner false positives: the line scanner now tracks block- comment and string state across lines (advanceCsScanState), so a `namespace` / `using static` keyword at the start of a line inside a block comment, verbatim string (@"..."), or raw string literal ("""...""") is no longer mistaken for a declaration on the worker cache-miss path. It matches only at code-state line starts. 5 new scanner tests cover the block-comment / raw / verbatim cases. #4 — workspaceFqnBindings type safety: the ReadonlyMap->Map cast is localized to one documented line, and global-namespace writes go through a new getWorkspaceBucket helper (mirroring getAugmentationBucket) rather than an inline `.set()` at the mutation site. #2 — lookupBindingsAt workspace-channel coverage: walkers-augmentations.test.ts now exercises the third (workspace) channel: workspace-only, append-after- finalized/augmented, and dedup-loses-to-finalized/augmented precedence. #5 — OOM CI guard: the deterministic O(D) invariant (zero per-scope augmentation for global types) is already asserted by the always-on csharp-hooks unit tests added earlier; the scale/time benchmark stays appropriately opt-in (skipIf). Verified: tsc --noEmit clean; 69 unit tests (4 suites) + 210 C# integration resolver tests pass; prettier clean; eslint 0 errors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * perf(csharp): replace remaining O(A) .some dedup scans with seeded Sets The using-static member-injection loop and the cross-namespace import loop both de-duped via `bucketArr.some((b) => b.def.nodeId === ...)` — O(A) per item. Both now use a per-file `Map<simpleName, Set<nodeId>>`, seeded lazily from the augmentation bucket (capturing entries from earlier passes), matching the global and named-namespace paths. Same dedup semantics, O(1) amortized. Verified: tsc --noEmit clean; csharp-hooks unit (27) + C# integration resolver (210) tests pass; prettier + eslint clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |