Repository navigation
fix(hy-emit): N5 zero-value maps allocate at declaration so the first write works (fixes #645) - #662
Merged
limitlessprojects merged 1 commit intoOct 18, 2026
Conversation
… write works (fixes limitlessprojects#645) SPEC-LANGUAGE N5 / T8: `var m map[k]v; m[k] = v` works (first write allocates), where Go panics on a nil map; N3 extends the empty-map zero value field-wise into structs. The emitter lowered every zero map Go-exact, so an accepted program built and then died with `assignment to entry in nil map`. The zero map is now allocated where it is declared (zeroinit.rs): `var` locals with no initializer, the unkeyed map fields of struct literals (recursively through nested struct fields), and `new(T)`, with user type aliases resolved. Generic structs keep Go's zero value (their field types need the literal's type arguments substituted). Corpus: n5-zero-map-first-write.hy registered additively as ACCEPT (80 programs: ACCEPT 22 / ACCEPT-MANAGED 2 / REJECT 56), its stdout pinned byte-exact by hyb/tests/spec_n5_zero_maps.rs; emission shapes pinned by hy-emit/tests/n5_zero_map_lowering.rs. D11 stays open; this follows the spec as written.
Owner
|
Landed as maintainer integration 74af832 (merged onto current main). Thank you, @Morphexe — this is the landed take for #645; the write-site-guard alternative (#663) is being credit-closed with the analysis. What was adapted in the merge (content preserved)
Verification on the merged tree (main @ 74af832, go1.26.9 / rust 1.99.0)
Side finding (pre-existing, NOT a #662 regression — verified identical on pre-merge main)Generic struct emission itself is broken on main today: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #645.
Problem
SPEC-LANGUAGE N5 (and T8):
var m map[k]v; m[k] = vworks — first write allocates — where Go panics on a nil map. N3 gives maps an empty zero value and structs field-wise zero values, so a map field of a zero-valued struct is covered too. The emitter lowered every zero map Go-exact, so the issue's program passedhyb check, built, and panicked at its first write:Fix
hy-emitnow allocates a zero map where it is declared (newzeroinit.rs):var m map[string]intvar m map[string]int = map[string]int{}var b bag(bag has a map field)var b bag = bag{counts: map[string]int{}}bag{label: "x"}/bag{}bag{label: "x", counts: map[string]int{}}type tally = map[...]aliasesshelf{top: bag{counts: …}, tags: tally{}}new(bag)&bag{counts: map[string]int{}}Why declaration-site allocation and not a write-site guard: a guard at
x[k] = vneeds the place's static type (the emitter has none for arbitrary places) and evaluates the place twice. Per the spec the two are the same value: a Hybrid map has nonil(N1) and its zero value is the empty map (N3). Fields the literal keys itself are never touched, and types whose Go zero value already matches Hybrid's (int, plain structs, …) emit exactly as before.Kept deliberately minimal while D11 (keep N5 vs Go-exact) is open — this follows the spec as written, and the corpus verdict note says a D11 reversal amends it. Not covered by this change: generic structs (
box[int]{}— field types need the folded type arguments substituted), arrays of map-carrying structs,make([]bag, n)elements, and named fn results. Those keep Go's zero value, as today.No checker change.
Tests
tests/checker-corpus/n5-zero-map-first-write.hy, registered additively per the corpus conventions —EXPECTED-VERDICT: ACCEPT, a VERDICTS.md registration section, README census,parse_allcount 79 → 80. Corpus is now 80 programs: ACCEPT 22 · ACCEPT-MANAGED 2 · REJECT 56. It covers avarzero map, a zero-valued struct field, a partially keyed literal, a nested struct, an aliased map type,new(T), and a compound first write (t["z"] += 7).// EXPECTED-OUTPUT:lines (the corpus-35 marker). The differential loop only checks build + exit 0, socompiler/crates/hyb/tests/spec_n5_zero_maps.rsbuilds it withhyb buildand diffs stdout byte-exact.compiler/crates/hy-emit/tests/n5_zero_map_lowering.rs(var / alias / nested / keyed-field-untouched /new/ plain structs unchanged / generic structs left alone).On
main@ 0ca08c2 the corpus program builds and panics at the first write (assignment to entry in nil map, exit 2). With this branch it prints the expected output.Local run of everything
.github/workflows/ci.ymlruns (Go 1.26.9, Rust 1.99.0):cargo test --workspace: 1148 passed, 0 failed (main: 1143)tools/corpus_harness.py: 80/80 verdict agreement, tripwire not fired (2.5%)tools/differential_loop.py: FULL AGREEMENT, accept 24/24, reject 56/56tools/run_examples.sh: all 10 examples passcheck-versions.shCONSISTENT, row-13 gomod sample (build/vet/test/run + drift detection) OK, Go runtime hygiene OK, every Python suite in the quick job OK, no conflict markersgo vet-clean