docs/sonic-go127-compatibility.md
Last updated: July 30, 2026
Test module: compatibility/
Sonic treats Go 1.27 as a supported target. The current implementation and validation evidence use Go 1.27rc2; the Go project still marks the 1.27 release notes as draft and expects the final release in August 2026.
The support claim currently means:
encoding/json backends are covered.It does not mean that unreleased runtime internals are frozen. The final toolchain must be validated again before the release-candidate qualifier can be removed from this evidence.
Go 1.27 affects Sonic in four separate areas:
| Surface | Sonic adaptation | Current evidence | Remaining risk |
|---|---|---|---|
| JIT and native build tags | Go 1.27 uses the supported implementation instead of unsupported stubs | Default and optimized main-module tests pass | Final compiler or linker changes |
| Loader runtime ABI | loader/funcdata_go127.go follows the Go 1.27 moduledata layout | Loader race tests pass | moduledata is private runtime state |
| Map iteration | GoMapIterator retains legacy trailing storage | Go 1.25 noswissmap and Go 1.27 tests pass | Linkname shims are not public APIs |
| JSON semantics | Default jsonv2 backend and legacy backend are tested separately | Compatibility and issue suites pass in both modes | RC behavior may change before final |
moduledataThe Go 1.27 runtime layout adds typedesclen, itaboffset, and itabsize,
removes the old typelinks and itablinks layout, and changes typemap to
map[*_type]*_type. Sonic mirrors that private layout in
loader/funcdata_go127.go.
This is an ABI adaptation, not a public Go API integration. A passing loader test is necessary but does not replace a source-level comparison against the final Go runtime.
internal/rt.GoMapIterator keeps a trailing ClearSeq uint64 field:
GOEXPERIMENT=noswissmap require clearSeq in the
legacy hiter layout.ClearSeq therefore remains for older supported toolchains. It is not a new
Go 1.27 field.
The tests distinguish four entry points:
| Name | Entry point | Role |
|---|---|---|
| SonicStd | sonic.ConfigStd | Classic encoding/json v1 compatibility target |
| GoStd127 | encoding/json on Go 1.27 | v1 API backed by jsonv2 by default |
| LegacyStd | encoding/json with GOEXPERIMENT=nojsonv2 | Temporary legacy v1 backend |
| JSONV2 | encoding/json/v2 | New API with stricter defaults |
GoStd127 and JSONV2 are not interchangeable. GoStd127 keeps the v1 API and is
intended to preserve v1 marshal and unmarshal behavior. The direct JSONV2 API
intentionally uses new defaults and is not a ConfigStd compatibility target.
Sonic continues to target classic v1 behavior. It does not add a production switch to reproduce release-candidate backend quirks. A future v2-style Sonic API, if needed, should be a separate opt-in contract.
The standalone compatibility module has four test files:
compatibility/
catalog_test.go # Sonic versus the active encoding/json backend
jsonv2_test.go # v1 facade versus encoding/json/v2 defaults
backend_jsonv1_test.go # !goexperiment.jsonv2 expectations
backend_jsonv2_test.go # goexperiment.jsonv2 expectations
The two backend files only select the stdUsesJSONV2 constant through build
tags. No runtime version parser, profile registry, or forwarding layer is
required.
jsonv2_test.go is compiled only with go1.27 && goexperiment.jsonv2, so
older toolchains and GOEXPERIMENT=nojsonv2 never resolve
encoding/json/v2.
Every Sonic-versus-standard-library assertion in catalog_test.go calls
sonic.ConfigStd directly. ConfigDefault appears only in the two preset
tests that explicitly compare Sonic configurations.
These results were observed with Go 1.27rc2. They are regression evidence, not a promise that the final Go 1.27 implementation will keep the same behavior.
| Case and test | Input or type | SonicStd | GoStd127 | LegacyStd | Compatibility impact |
|---|---|---|---|---|---|
F32-OVF | Value above float32 range | Error; destination remains 0 | Error; destination becomes +Inf | Error; destination remains 0 | Error-time mutation |
F64-OVF | 1e1000 | Error; destination remains 0 | Error; destination becomes +Inf | Error; destination remains 0 | Error-time mutation |
F64-LONG-OVF | 1,712-digit mantissa followed by e-913 | No error; incorrectly returns a finite value near 5.33e-114 | Range error; element becomes +Inf | Range error; element remains nil | Historical range-detection bug exposed by Go 1.27 |
MAP-F64-ENC | map[float64]string | Unsupported | Encodes successfully | Unsupported | Accepted type set |
MAP-PTR-ENC | map[*int]*int | Unsupported | Encodes successfully | Unsupported | Accepted type set |
MAP-F64-DEC | Float map keys | Decodes successfully | Decodes successfully | Error | Accepted type set |
STR-NULL | ,string pointer fields receive "null" | Nil pointers; no error | *string path returns a type error | Matches SonicStd | Error and pointer state |
UTF8-ENC | Go string contains invalid UTF-8 | Escaped \ufffd | Literal U+FFFD in output | Matches SonicStd | Byte-level output |
TMPTR-UNJ | Named pointer whose element implements UnmarshalJSON | Uses default field decoding | Calls UnmarshalJSON | Matches SonicStd | Method dispatch |
UNJ-PARTIAL | Custom unmarshal fails before field E | JIT keeps E=0; OptDec writes E=1 | Writes E=1 | Keeps E=0 | Error-time partial writes |
TM-KEY-QUOTE | MarshalText returns quoted text | Quotes delimit the key | Quotes become key content and are re-escaped | Matches SonicStd | Encoded map-key bytes |
IFACE-SKIP-NONPTR | Interface holds a non-pointer value | Error; preserves the dynamic type | Type error; interface becomes nil | Matches SonicStd | Interface state |
IFACE-SKIP-NILPTR | Interface holds a typed nil | Error; preserves the typed nil | Type error; interface becomes nil | Matches SonicStd | Interface state |
SELF-REF | Self-referential interface{} | Replaces the cycle with a map | Not called because rc2 recurses to stack overflow | Not compared | Process availability |
UNJ-PARTIAL, SYN-PREMUT, STRING-GONUM, STRING-GONUM-PLUS, and
BASE64-ERROR have explicit decoder-dependent expectations. They read
internal/envs.UseOptDec and pin both JIT and OptDec behavior.
The most consequential differences are error-time mutation, interface state, and self-reference handling. They can change application state even when both implementations return an error. Accepted map-key types and method dispatch change whether an operation succeeds. UTF-8 and quoted map-key cases primarily affect byte-for-byte comparisons, signatures, snapshots, and cache keys.
F64-LONG-OVF is a correctness difference, not merely an error-time mutation.
The test number is approximately 5.33e798, far above the maximum finite
float64. Go 1.18 through Go 1.26 and Sonic releases from v1.0.0 through
v1.15.2 instead returned a finite value near 5.33e-114 without an error.
Both implementations historically used an 800-byte decimal fallback buffer.
When the 1,712-digit integer mantissa was truncated, the fallback computed the
decimal point from the retained 800 digits, turning the exponent into roughly
800 - 913 = -113. Go 1.27's
strconv scaling change
detects the full decimal magnitude before that fallback and returns
ErrRange. The Go 1.27 jsonv2 backend stores +Inf before returning the JSON
type error; GOEXPERIMENT=nojsonv2 returns the same error without storing a
value.
This MR records the gap but intentionally does not change Sonic's decoding semantics. A follow-up fix should preserve the full decimal-point position or reject the value before the bounded fallback, then evaluate the compatibility impact for users that currently receive the historical finite result.
Auditing every option in DefaultOptionsV1 found five compatibility cases that
were not represented in the original catalog:
| Case and test | SonicStd | GoStd127 | LegacyStd | Classification |
|---|---|---|---|---|
TEXT-APPEND | Calls MarshalText, producing "MARSHAL" | Prefers AppendText, producing "APPEND" | Calls MarshalText | Go 1.27 accepted behavior change |
STRING-GONUM | Independent subtests show JIT rejects "00012" and "0x1_4p-2"; OptDec decodes them as 12 and 5 | Decodes them as 12 and 5 | Same as GoStd127 | Sonic JIT v1 gap |
SYN-PREMUT | JIT writes A=1 before the later syntax error; OptDec leaves the destination unchanged | Leaves the destination unchanged | Same as GoStd127 | Sonic JIT v1 gap |
MAP-VALUE-REPLACE | Merges an object into the existing map value | Replaces the map value before decoding | Same as GoStd127 | Existing SonicStd v1 gap |
MARSHAL-ERR-WRAP | Returns the error from MarshalJSON directly | Wraps it in *json.MarshalerError | Wraps it in *json.MarshalerError | Existing SonicStd error-contract gap |
TEXT-APPEND is the only new GoStd127-versus-LegacyStd difference in this
group. Go issue #79852 considered
whether the v1 facade should ignore encoding.TextAppender; it was closed
without changing Go 1.27. Sonic must therefore choose whether v2-oriented APIs
should recognize AppendText, but ConfigStd can retain the legacy method
order as a documented compatibility choice.
STRING-GONUM is confirmed by Go
issue #75619. The v1 API uses
strconv grammar inside ,string, including zero padding, underscores, and
hexadecimal floating-point syntax. The jsonv2-backed facade was changed in
CL 709615 to preserve that behavior. Sonic still
parses the quoted content as a JSON number.
The remaining three cases follow directly from documented v1 options:
ReportErrorsWithLegacySemantics
requires complete syntactic validation before semantic mutation and preserves
v1 error wrapping.MergeWithLegacySemantics
specifies replacement of map values rather than recursive merge.These are not reasons to add Go 1.27 build-tag behavior. They are concrete
ConfigStd correctness gaps that exist under both Go backends. Two of them are
specific to Sonic's JIT decoder and already align under OptDec. Syntax
prevalidation and map-value replacement have the highest application impact
because successful earlier writes can survive an error or stale state can
survive a successful decode.
A second source-guided tri-diff found behavior that changes inside the v1 API when only the backend is switched:
| Case and test | SonicStd | GoStd127 | LegacyStd | Impact |
|---|---|---|---|---|
STRING-GONUM-PLUS | Independent subtests show JIT rejects leading + and .5; OptDec accepts them | Accepts them | Rejects them | Accepted numeric grammar |
MAP-STRING-TEXT | Uses the underlying named-string key | Calls MarshalText | Uses the underlying key | Method dispatch and map-key bytes |
BASE64-ERROR | Clears the byte slice; JIT returns base64.CorruptInputError, OptDec returns a mismatch error | Preserves it and wraps the cause in *json.UnmarshalTypeError | Preserves it and returns base64.CorruptInputError directly | Destination state and error type |
MARSHAL-ERR-WRAP | Returns the original error | MarshalerError.Type is *T | MarshalerError.Type is T | Observable error metadata |
UNSUPPORTED-VALUE-PAYLOAD | Value remains populated | Value is an invalid reflect.Value | Value contains the unsupported value | Observable error metadata |
UNMARSHAL-TYPE-ROOT | Returns Sonic's own mismatch error | Struct names the root destination type | Struct names the immediate containing type | Error classification and logging |
The ,string behavior follows the same strconv compatibility path discussed
in #75619, but leading + and
leading decimal points demonstrate an actual GoStd127-versus-LegacyStd split.
The nested error-type change is related to the long-standing ambiguity in
#43126. No exact upstream issue was
found for the named-string map-key dispatch, Base64 wrapper, pointer-valued
MarshalerError.Type, or empty UnsupportedValueError.Value.
GoStd127 also adds an Err field and Unwrap method to
encoding/json.UnmarshalTypeError; those members are absent when the same
toolchain is built with GOEXPERIMENT=nojsonv2. This is a conditional public
API difference, not merely a runtime error-text change. Sonic should not try to
mirror backend-dependent error fields. A stable Sonic error contract, with
documented errors.Is and errors.As behavior, is safer than exposing a
Go127Semantics switch.
Sonic's public Decoder supports Decode, Buffered,
DisallowUnknownFields, More, and UseNumber, but does not expose the v1
Token or InputOffset methods. Its internal stream decoder has
InputOffset, but callers receiving the public interface cannot use it. This
is an existing API-coverage gap rather than a Go 1.27 semantic regression.
Supporting direct jsonv2 would additionally require deciding whether Sonic
offers a token/value streaming layer comparable to encoding/json/jsontext;
adding configuration flags alone would not close that surface difference.
The cases should not all be handled by making ConfigStd match GoStd127:
| Action | Cases | Reason |
|---|---|---|
Fix in ConfigStd | SYN-PREMUT (JIT), MAP-VALUE-REPLACE, STRING-GONUM (JIT), BASE64-ERROR destination state, MARSHAL-ERR-WRAP | GoStd127 and LegacyStd agree on the core v1 behavior, or Sonic's JIT and OptDec disagree. These are Sonic compatibility or internal-consistency gaps. |
| Keep Sonic's stable v1 behavior | F32-OVF, F64-OVF, STR-NULL, UTF8-ENC, TMPTR-UNJ, TM-KEY-QUOTE, interface clearing, SELF-REF | Sonic matches LegacyStd or has safer behavior. Matching GoStd127 would silently change ConfigStd or copy an unsafe implementation artifact. |
| Record GoStd127 drift; consider only for an opt-in v2 preset | broader map keys, TEXT-APPEND, STRING-GONUM-PLUS, MAP-STRING-TEXT | These differ between GoStd127 and LegacyStd. They should not change the classic v1 preset by toolchain version. |
| Do not chase backend-specific error metadata | pointer-valued MarshalerError.Type, empty UnsupportedValueError.Value, root-valued UnmarshalTypeError.Struct, conditional UnmarshalTypeError.Err | The Go 1.27 default and legacy backends already disagree. Sonic should document one stable error contract instead. |
| Evaluate as separate API work | Decoder.Token, public Decoder.InputOffset, jsontext-style streaming | These are API-surface gaps, not fixes for the Go 1.27 runtime backend. |
The first implementation batch should focus on destination correctness:
MAP-VALUE-REPLACE, SYN-PREMUT, and BASE64-ERROR. They can leave stale or
partially modified application state. Numeric grammar and error wrapping are
important compatibility work but have lower state-corruption risk.
The accepted encoding/json/v2 proposal
does not retain two permanent implementations. Instead, encoding/json calls
the jsonv2 engine with
DefaultOptionsV1,
while encoding/json/v2 uses the new defaults. Individual options can override
parts of either preset. The
Go 1.27 release notes state that v1
marshal and unmarshal behavior is preserved, while exact error text may differ.
They also describe GOEXPERIMENT=nojsonv2 as a temporary implementation
fallback that is expected to be removed, not a long-term semantic selection
mechanism.
This gives upstream three ways to handle a reported difference:
DefaultOptionsV1 can reproduce the old
behavior;The table distinguishes an exact issue from the nearest official design or source evidence. “No exact issue found” means that the cited issue does not fully cover Sonic's reproducer and the case should not be described as fixed upstream.
| Sonic cases | Official evidence and status | Upstream disposition |
|---|---|---|
F32-OVF, F64-OVF | #77666 is closed and implemented by CL 772360. The working group chose strconv.ParseFloat-like results: an out-of-range float returns +Inf or -Inf together with an error. The rc2 decoder assigns the parsed value before returning its range error. | The numeric conversion rule is intentional v2 behavior. #77666 concerns token accessors, not v1 destination mutation; no exact issue was found for the 0 to +Inf mutation. Treat that mutation as an unresolved v1-facade difference. |
MAP-F64-ENC, MAP-PTR-ENC, MAP-F64-DEC | #79938 reported broader map-key acceptance and was closed as not planned. A maintainer explained that v2 defers an invalid-key error until an actual element is encoded. The v2 contract recursively encodes keys and only requires their JSON representation to be a string. | Broader map-key handling is part of the v2 model. #79938 only partially covers these exact float and pointer cases, and DefaultOptionsV1 has no option that restores the legacy key-type allowlist. Current facade behavior is therefore accepted in practice, but not explicitly promised for these three inputs. |
STR-NULL | StringifyWithLegacySemantics and MergeWithLegacySemantics are both in DefaultOptionsV1. | The official design intends to preserve v1 ,string and null-merge behavior. No exact issue was found for the *string result, so the rc2 type error and pointer mutation remain an unreported v1-facade regression candidate. |
UTF8-ENC | #75163 is closed as completed. The working group deliberately kept literal U+FFFD rather than escaped \ufffd; CL 687116 removed the proposed legacy escape option. Maintainers cited existing v1 inconsistencies, RFC 8785 formatting, and precedent for changing semantically equivalent JSON bytes. | Intended v1-facade byte-output change, not considered a Go 1 compatibility violation. Upstream said it may reconsider a switch only if later releases show substantial breakage. |
TMPTR-UNJ | #75361 is still open. It covers the same named-pointer method-discovery mechanism and a worse recursive form. The working group chose to keep v2's consistent pointer-receiver dispatch; it recommends declaring a method-free value type and converting its pointer. CL 738361 explored rejecting recursive named-pointer calls, but the runtime guard was abandoned after a measured 3–9% cost; the planned vet check was not ready for Go 1.27. | Method dispatch is an intended v2 semantic that upstream decided not to emulate exactly in the v1 facade. Recursion detection remains unresolved; the exact non-recursive Sonic case is mechanism-aligned but not the issue's reproducer. |
UNJ-PARTIAL | #74614 fixed a related facade regression where a custom unmarshaler ran before invalid trailing input was discovered; CL 689919 restored whole-value validation before mutation. | Upstream treats consequential error-time mutation as fixable when it can reproduce v1 behavior. That issue does not cover continuing to later fields after UnmarshalJSON itself returns an error. No exact issue was found for Sonic's case, so it remains unresolved. |
TM-KEY-QUOTE | CallMethodsWithLegacySemantics explicitly preserves special v1 method rules for map keys. Historical #29732 documents map-key method asymmetry but not already-quoted MarshalText output. | No exact official issue or CL was found. Since the active v1 compatibility option still fails to reproduce the legacy bytes, classify this as an unresolved v1-facade difference rather than an intended direct-v2 default. |
IFACE-SKIP-NONPTR, IFACE-SKIP-NILPTR | DefaultOptionsV1 includes MergeWithLegacySemantics, whose documentation explicitly covers interface values. Historical #20994 and #21494 show that interface replacement has long been compatibility-sensitive, but neither is this rc2 failure case. | Clearing the interface despite the v1 merge option is a v1-facade regression candidate. No exact upstream report or resolution was found for the non-pointer or typed-nil cases. |
SELF-REF | The same interface merge option is the nearest official contract. No exact golang/go issue was found for v = &v causing a stack overflow under the Go 1.27 facade. #75361 is related only in showing that recursive method discovery must be bounded. | Unresolved and high priority to report upstream. A process-ending stack overflow is not an intentional v2 semantic and must not be copied into Sonic. |
Two related upstream decisions show the boundary more clearly:
SyntaxError/UnmarshalTypeError offsets.These six cases are not facade regressions. They are documented v2 defaults
with explicit migration controls in the accepted proposal and
v2_options.go:
| Direct JSONV2 default | v1 behavior restored by |
|---|---|
| Reject duplicate object names | jsontext.AllowDuplicateNames(true) |
| Match struct fields case-sensitively | jsonv2.MatchCaseInsensitiveNames(true) plus the v1 delimiter rule |
Encode nil slices/maps as []/{} | jsonv2.FormatNilSliceAsNull(true) and FormatNilMapAsNull(true) |
| Require an exact array length | jsonv1.UnmarshalArrayFromAnyLength(true) |
Encode [N]byte as Base64 | jsonv1.FormatByteArrayAsArray(true) |
| Reject invalid UTF-8 | jsontext.AllowInvalidUTF8(true) |
Historical issues such as #14750 for case-insensitive field matching and #37711 / #27589 for nil collection output explain why configuration was requested. The final accepted resolution is #71497: strict v2 defaults, a complete v1 preset, and composable options for hybrid migration.
The catalog also keeps cases that currently agree with the active v1 facade. They catch accidental Sonic drift:
TextMarshaler map keys remain aligned.[3]byte value encodes as [1,2,3].*bool(false) is not omitted by omitempty.Separate preset cases document intentional Sonic configuration differences:
ConfigDefault does not HTML-escape; ConfigStd does.null by default.CaseSensitive is enabled.TestJSONV2Semantics records intentional differences between the v1 facade
and direct encoding/json/v2 use:
| Case and test | SonicStd / GoStd127 | JSONV2 |
|---|---|---|
| Duplicate names | Accepted; last value wins | Error |
| Case folding | foo matches Foo | Case-sensitive by default |
| Nil slice | null | [] |
| Oversized array | Accepted and truncated | Error |
| Byte array | [1,2,3] | "AQID" |
| Invalid UTF-8 | Replaced with U+FFFD | Error |
These tests describe migration impact. They do not make JSONV2 defaults a
ConfigStd compatibility requirement.
The runtime ABI and JSON behavior require different strategies. Build tags are appropriate for private runtime layouts, which are selected by the toolchain. Public JSON semantics need a stable contract that does not change silently when users rebuild the same source with a newer Go release.
Keep ConfigStd aligned with classic encoding/json v1 semantics across Go
versions. Record GoStd127 drift in compatibility tests, but do not reproduce
release-candidate implementation quirks.
Advantages:
Costs:
ConfigStd is not byte-for-byte or state-for-state identical to the Go 1.27
implementation in every edge case.This is the current behavior and the recommended default.
Add Go-version or experiment-specific files, for example
go1.27 && goexperiment.jsonv2, and make the existing API use GoStd127
behavior when built with that toolchain.
Advantages:
encoding/json
implementation without a user-visible configuration change.Costs and risks:
Build tags should continue to select runtime ABI implementations such as
loader/funcdata_go127.go. They are not recommended for silently changing the
semantics of ConfigStd.
Keep ConfigStd stable and expose selected v2-style behavior through an
opt-in preset and granular options. Existing options already cover part of the
space:
NoNullSliceOrMap selects empty arrays and objects instead of null;CaseSensitive selects strict struct-field name matching;ValidateString provides stricter string validation, although it is not a
complete JSONV2 compatibility switch.Additional behavior would need explicit support, potentially including:
encoding.TextAppender over encoding.TextMarshaler;The API should prefer a named preset such as ConfigJSONV2 plus granular
encoder and decoder flags. A single Go127Semantics boolean would couple the
contract to a toolchain version and make later evolution ambiguous.
Not every observed difference should become an option. Error-time destination mutation, interface clearing, and cycle handling should have one documented, safe behavior. In particular, self-reference must be bounded by cycle or depth detection rather than emulating a stack overflow.
| Criterion | Preserve v1 semantics | Build-tag default | Explicit options |
|---|---|---|---|
| Existing user compatibility | Best | Poor | Best |
| Same-toolchain parity with GoStd127 | Partial | Best | Opt-in |
| Runtime selection | Not needed | Compile time only | Per API instance |
| Implementation and CI cost | Lowest | Highest | Medium to high |
| Risk of silent behavior change | Lowest | Highest | Low |
| Suitable for runtime ABI adaptation | No | Yes | No |
| Suitable for JSON behavior | Default contract | Not recommended | Recommended opt-in |
ConfigStd on classic v1 semantics.ConfigJSONV2 contract as opt-in and test it against both
direct encoding/json/v2 and the Go 1.27 v1 facade.ConfigStd gaps such as syntax prevalidation,
map-value replacement, quoted numeric parsing, and marshaler error wrapping
independently of any JSONV2 API design.compatibility/ is a separate workspace module. A root-level go test ./...
does not run it.
# Go 1.27 default encoding/json backend
env -u GOROOT GOTOOLCHAIN=go1.27rc2 \
go test ./compatibility/ -count=1
# Go 1.27 legacy encoding/json backend
env -u GOROOT GOTOOLCHAIN=go1.27rc2 GOEXPERIMENT=nojsonv2 \
go test ./compatibility/ -count=1
# Sonic optimized paths
env -u GOROOT GOTOOLCHAIN=go1.27rc2 \
SONIC_USE_OPTDEC=1 SONIC_USE_FASTMAP=1 SONIC_ENCODER_USE_VM=1 \
go test ./compatibility/ -count=1
# Sonic optimized paths with the legacy encoding/json backend
env -u GOROOT GOTOOLCHAIN=go1.27rc2 GOEXPERIMENT=nojsonv2 \
SONIC_USE_OPTDEC=1 SONIC_USE_FASTMAP=1 SONIC_ENCODER_USE_VM=1 \
go test ./compatibility/ -count=1
The broader Go 1.27 validation set is:
env -u GOROOT GOTOOLCHAIN=go1.27rc2 GOMAXPROCS=4 \
go test ./... -count=1
env -u GOROOT GOTOOLCHAIN=go1.27rc2 GOMAXPROCS=4 \
SONIC_USE_OPTDEC=1 SONIC_USE_FASTMAP=1 SONIC_ENCODER_USE_VM=1 \
go test ./... -count=1
env -u GOROOT GOTOOLCHAIN=go1.27rc2 GOMAXPROCS=4 \
go test -race ./loader/... -count=1
env -u GOROOT GOTOOLCHAIN=go1.27rc2 GOMAXPROCS=4 \
go test -race ./issue_test -count=1
env -u GOROOT GOTOOLCHAIN=go1.27rc2 GOEXPERIMENT=nojsonv2 GOMAXPROCS=4 \
go test -race ./issue_test -count=1
The Go 1.27 x86 job explicitly runs:
GOEXPERIMENT=nojsonv2;The repository matrix also covers Linux amd64, Linux arm64, macOS arm64, and Windows amd64. These jobs prove the tested source state against rc2. They do not prove that final runtime internals will remain unchanged, and they do not replace validation with application workloads.
A new case should state:
For interfaces and pointers, assert dynamic types, pointer layers, typed nils, and partial writes directly. Re-marshaling the value is not sufficient because different internal states can produce identical JSON.
After the final toolchain is published:
1.27.0-rc.2 in CI with the final version.moduledata and the map iterator linkname shim with final sources.If final changes only error wording, tests should continue to assert error types or presence rather than complete strings. Changes to output, dynamic types, or destination state require a new compatibility decision.