Baseline vs. Full Conversion — the separation contract
Companion to /CLAUDE.md. Defines what lives where, why, and the rules that keep the
converter-improvement loop and the full-stdlib goal from colliding again.
The three things
-
Baseline stdlib —
src/core/<pkg>Small, hand-finished, compiling subset of the Go standard library. This is what the behavioral tests and converter-improvement loop build against. It must always stay green. -
Full auto-conversion —
src/go-src-converted/(target location) The entire Go standard library (~305 packages) auto-converted bygo2cs -stdlib. The ultimate goal, but work in progress — does not all compile yet. -
Runtime —
src/core/golib/Hand-written C# runtime (slice,map,channel,@string,builtin,ж<T>, type aliases). Shared by both baseline and full conversion. Never auto-overwritten — some of it (builtin,unsafehelpers, assembly-backed routines) can never be produced by transpilation.
Why they must stay separate
Both baseline and full emit into namespace go with <pkg>_package static partial classes. Referencing
both from one C# project produces duplicate-type collisions. So they are kept in separate directories
and never referenced together by a single project.
How the collision happened (history)
| Commit | Date | Event |
|---|---|---|
9792eeea2 |
2020-07-09 | Hand-converted stub created at src/gocore/<pkg> (Tour-of-Go support). |
| (many) | 2020–2025 | Stub maintained/refined for years; it was the working library. |
ba6fef6c9 |
2025-03-08 | src/gocore renamed → src/core (path change only). |
3426298eb |
2025-05-05 01:51 | Last clean baseline. Stub compiles; tests green. |
6ca1c45b7 |
2025-05-05 01:59 | “Initial standard library conversion” — full stdlib written on top of src/core, overwriting the hand-finished packages (2,359 files, +508k lines). |
cc14584c7 |
2025-05-11 | Full-conversion work; tagged full-conversion-2025-05. |
| 2026-06-25 | 2026-06-25 | Separation restored: full conversion relocated to src/go-src-converted/; old stub restored into src/core; converter fixes; green baseline. |
The mistake was writing the full conversion into the same directory as the baseline instead of a
separate one. “All 305 packages converted successfully” meant the transpiler did not crash — not that
the emitted C# compiles. The overwrite replaced compiling fmt/time/etc. with large machine-generated
versions, which stalled the test loop.
The project was originally designed with this separation (gocore manual subset + go-src-converted
full auto-output), so restoring it realigns with the original design.
How it was resolved (2026-06-25)
- Relocated the full conversion out of
src/coreintosrc/go-src-converted/(a 2604-file git rename); rewrote inter-packagecsprojrefs andgo2cs.slnpaths; added.gitignorerules for the Godebug/logpackages that collide with the VS[Dd]ebug//[Ll]og/patterns. - Restored the old hand-finished stub from
3426298ebintosrc/core. Key finding: it compiles cleanly against today’sgolib— the feared API drift did not materialize, so it gave a green baseline immediately. Restored 14 packages; excluded the stubtesting(drifted, 400 errors, referenced by no test). - Scoped
src/go2cs.slnto the baseline + tests; addedsrc/go-src-converted.slnfor the 301 WIP projects. - Result:
go2cs.slnbuilds 79/79; behavioral suite green (216 tests).
The contract (rules going forward)
src/core/<pkg>is curated and must compile. Treat it as hand-owned source. Do not bulk-overwrite it with-stdliboutput.src/go-src-converted/is the full-conversion target. Allgo2cs -stdlibruns write here via-go2cspath. It may be regenerated wholesale; nothing hand-edited lives here long-term (fixes belong in the converter or, for out-of-band pieces, ingolib).golibis shared and never auto-generated. Both trees referencesrc/core/golib/golib.csproj.- Promotion
go-src-converted → coreis DEFERRED (strategy correction, 2026-07-01). Earlier work promoted packages intocoreas they went green (compiling). That was premature — compiling is not operating. Promotion should happen only once a package’s converted Go unit tests pass (Phase 4), and may not be needed at all (see The corrected end-state below). Until then,corestays the small bootstrap stub the behavioral tests build against (chicken-and-egg — the tests need a working library to run, andgo-src-convertedcompiling doesn’t yet mean it works).sync/atomicalready living incoreis fine — it remains a useful stub. Do not promote further on the basis of a clean compile. The converter is never pointed at the baseline directory. - The canonical MANUAL files live in
coreand are copied BACK intogo-src-converted. Files marked[module: GoManualConversion](the converter skips re-converting them) and hand-written*_impl.csfiles are hand-owned insrc/core/<pkg>. For a full-conversion milestone to be complete, these must be overlaid into their matchingsrc/go-src-converted/<pkg>locations — that overlaid tree (auto-output- manual/asm stubs) is the real final state.
overlay.shalready re-copies thesrc/coremanual files after the cs/csproj copy; during these final compiling stages, do this religiously.
- manual/asm stubs) is the real final state.
The corrected end-state (2026-07-01) — compile first, operate later
The milestone is a clean C# COMPILE of the whole overlaid go-src-converted (auto-output + the
manual/*_impl.cs/asm stubs) — not an operational one. Operational correctness is Phase 4 (converting +
passing the Go unit tests). Getting there, for runtime:
- Native-type pointer/unsafe ops are convertible. Go and C# are both GC languages with pinning and
unsafe pointers; native types share identical memory operations. Pointer parity for native types is the
goal and is achievable (the hand-converted
unsafe/sync/atomiccode proves the overlap). Fix these in the converter/golibproperly. - Managed-referent cases have a known model. Where Go stashes a managed pointer inside a
uintptr(guintptr/muintptr/puintptr…) to hide it from the GC, the C# equivalent holds theж<T>/objectdirectly (Volatile/Interlocked +nilCanon), never anuintround-trip — exactly ascore/sync/atomic/type.cs’satomic.Pointer<T>andreflectlite/value.cs’sobject? m_targetdo. A rawuintptrcannot hold a managed reference across a GC (the “compiles-but-crashes” trap). - Raw-metal on NON-native types is the dragon — stub it. Memory-layout math, type-descriptor
pointer-walking, and
*.asmcannot be faithfully transpiled. When the loop hits this wall, the file gets an immediate[module: GoManualConversion]task / review — a hand-written C# equivalent, or a throwing stub that won’t exist in the final build — not a converter fight. AGoManualConversionstub that makes the package COMPILE is an acceptable milestone solution; the faithful hand/asm implementation can follow.
So the loop no longer stops at the S1/CS0030 “architectural wall” — it sorts: convert the native-type
ops, apply the managed-referent model, and stub the genuine raw-metal dragons with GoManualConversion.
Once the whole stdlib compiles and the converted Go tests pass, a versioned build can ship to NuGet;
at that point the chicken-and-egg is gone and core can be dropped (behavioral tests reference NuGet) or
replaced with prior operational go-src-converted source — TBD.
Regenerating the full conversion
Current Go converter (authoritative flags in src/go2cs/main.go):
# Whole stdlib into the separate target:
go2cs -stdlib -comments -go2cspath <repo>/src/go-src-converted
# Specific packages only (used when greening a closure bottom-up):
go2cs -stdlib -comments -go2cspath <repo>/src/go-src-converted fmt strings io sort time
Always pass
-commentsfor stdlib conversion. It defaults off, but the converted C# is a derivative work — the per-file// Copyright … The Go Authors … BSD-style licenseheader must be preserved, and the Go doc-comments keep the output readable. Without the flag, headers and comments are stripped.
-parallel 1..4 controls concurrency; output .csproj references are generated from detected imports.
Note on the
<go2cspath>/coresubdir: the stdlib converter writes packages to<go2cspath>/core/<pkg>(a hardcodedcoresubdir). To regenerate cleanly intosrc/go-src-convertedyou must either point-go2cspathso that subdir lands there, or convert to a temp dir and move. Don’t let it overwrite the baselinesrc/corepackages.
The old stub as a fallback / reference
The last clean stub (3426298eb) is the source of today’s baseline. To inspect or recover individual files:
git worktree add ../go2cs-stub-ref 3426298eb # browse the last clean baseline
git show 3426298eb:src/core/fmt/print.cs # or per file
Stale tooling
- Fixed:
src/deploy-core.bat(gocore→core);docs/README.md(banner + corrected references). - Still stale:
src/convert-gosrc.cmd/convert-gosrc.batinvoke a retirednet6.0C#go2cs.exewith old flags (-s -r -e -g); update to the Go converter’s-stdlib -go2cspath …form.