Repository navigation
Fix put return value, exact int64 encode_json, NaN ordering; narrow difftest HFIR skip (#161, #156, #158) - #162
Merged
Merged
Conversation
howlcipher
commented
Oct 12, 2026
howlcipher
left a comment
Owner
Author
There was a problem hiding this comment.
Review of #162 at 2607b20c5a473e511dc60efc0146f9aa21297532 (base 54009fb2)
Verdict: merge-ready. No blockers. A few nits below.
I used a fresh clone in /workspace, with a base worktree, and built hf on both. Every check below runs the interpreter (-run), governed AST bytecode and HFIR bytecode.
1. NaN (#158)
<,<=,>and>=with NaN on either side, int or float, including NaN vs NaN: allfalseon all 3 paths. Base gavetruefor(<= n n)and(>= 1 n).==and!=are unchanged and follow IEEE:(== n n)is false,(!= n n)is true and(== n 1.0)is false. That matches base on all paths.- Non-NaN differential: 300 programs gave 3,000 random int and float lists (MaxInt64, MinInt64, 2^53±1, -0.0, ±Inf, 1e308, 5e-324, -1e-308) plus 3,000
rangecalls.sort,min,max,sort_by,max_by,min_byandrangegave 0 mismatches between base and head, with all 3 paths agreeing and every run exiting 0. - NaN in lists: 50 programs with 500 lists containing NaN gave output byte-identical to base and identical across the 3 paths. No panic, hang or non-zero exit. The result is deterministic but not a total order, because it depends on position. For example,
(sort (list n 3.0 1.0 n 2.0))gives[NaN 1 2 3 NaN],(min (list n 1.0 2.0))givesNaN, and(min (list 1.0 n))gives1. - #153 compare-boundary differential: 4,704 cases (the 6 operators over 28×28 int and float boundaries, including 2^63 and ±Inf), checked against an exact
Fractionreference on 3 paths. 0 mismatches.
2. encode_json (#156)
- MaxInt64, MinInt64, 2^53±1, -2^53-1 and -42 come out exact. Nested dicts and lists with int64 come out exact. The round trip
encode_json → parse_json → encode_jsonmatches byte for byte, and(== (parse_json Any "9007199254740993") 9007199254740993)is true. - Float formatting: 716 values, including 0.1, 1.5, -0.0, 1e20, 1e-7, 2.0, ±2^53, 1e21, 5e-324, MaxFloat64, 500 random floats across 1e-30 to 1e30 and 200 random ints within ±2^53. Output is byte-identical to base on all 3 paths.
2.0still encodes as2, as it did on base, so JSON output doesn't tell floats from ints. That's unchanged and not a regression. Inside the language, floats and ints stay distinct types, because homogeneous lists still reject a mix.- Inf and NaN give the same clean
ENCODE_JSON_ERROR/unsupported valueas base on all paths.
3. put (#161)
putreturns the dict on all 3 paths for flat, nested and statement-position forms, and(encode_json (put d k v))matches. Base failed every expression-position case.- Aliasing:
putmutates its input and returns that same dict, so(let (e (put d "y" 2)) ...)gives anethat aliasesd. This matches base, wheremap_seton aletalias also mutates the original. Under #137 only lists are values, so this is consistent. - Fuzz in the style of #160: 600 random nested
put/let/map_get/encode_jsonprograms. 0 cross-path mismatches and 0 STACK_UNDERFLOW. 245 of them deliberately hit the TYPE_ERROR for putting into a list value, and they fail at the same point with the same stdout on all paths. On base, all of the first 100 diverged.
4. difftest skip
- Only
optimize_signatureis onhfirFencedConstructs. It printsSKIP optimization_signature.howl: HFIR leg fenced (optimize_signature); interpreter, go and bytecode legs agree. - Counts went from Passed 17 / Skipped 31 / Failed 1 on base to Passed 16 / Skipped 32 / Failed 1 on head, so the skip is no longer counted as Passed.
- The one failure is
test_env.howl(interpreter:capability denied: environment), and it fails the same way on base.TestHFIRFencedConstructIsExplicitpasses.
5. Pins, freeze and locks
The diff touches only desugar_records.go, vm.go, difftest and two test files. It changes no pin or freeze file and doesn't touch the TestProdFlipCriteriaLock source. These all pass: TestHFIRBenchmarkSolutionParity, the arity, drift and fuzz tests, TestProdFlipCriteriaLock, the freeze tests and the new Issue161/156/158 tests.
6. CI and tooling
- CI
buildis green on the head SHA. gofmt -lreports 0 files, andgo vet ./...exits 0.go test ./... -count=1exits 0. A first run failedexamples/TestHTTPServerServeHFBCwithunexpected listener on port 8080. The cause was a stale/tmp/hf160process left on the box from my #160 review. I killed it, and that test alone and./examples/... ./apps/...both passed. The full suite then exited 0. It was not a PR issue.
7. Hard nos
- No prod
-compile-bc/HFIR default flip, and #90 stays Partial. - No tip-lock 4d74dbc.
- No live-LLM run, and no H1 PASS/KILL claim.
Nits (non-blocking)
- NaN in
sort/min/maxdepends on position, as shown above. Consider documenting it, or defining a total order with NaN last, in a follow-up. - Document that
putreturns and mutates the same dict, since it can now be used as an expression and aliasing is easy to miss. test_env.howlfails in difftest on both base and head. Pass-allow-caps environmentto that interpreter leg, or skip it explicitly.- JSON encodes
2.0as2. That's pre-existing; worth noting next to #156.
howlcipher
marked this pull request as ready for review
October 12, 2026 23:36
This was referenced Oct 12, 2026
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 #161, Fixes #156, Fixes #158
Changes
ast.Desugarnow lowers(put m k ... v)to(let (P m) (do <writes> P)), soputevaluates to the updated root dict on the interpreter, AST bytecode and HFIR.(encode_json (put d "a" 1))gives{"a":1}on all three paths. It used to givenullon the interpreter and STACK_UNDERFLOW on bytecode and HFIR. Statement-positionputbehaves as before.hfirFencedConstructslist (optimize_signature). Any other Phase-1 subset refusal now fails. A fenced fixture still has its interpreter, Go and bytecode legs compared, then printsSKIP ...: HFIR leg fenced (optimize_signature)and counts as Skipped, not Passed. Unit test:TestHFIRFencedConstructIsExplicit.normalizeForJSONno longer converts int64 to float64, soencode_jsonwrites int64 exactly (2^53+1, MaxInt64, MinInt64). Floats and ints up to 2^53 encode exactly as before.<,<=,>and>=return false when either numeric operand is NaN (relationalHasNaN). The check sits in both the interpreter and the VM relational paths, so all three paths are covered.sort,min,max,sort_by,max_byandmin_bywith NaN give the same output as on base.No new opcode or capability. No capability or spawn code was touched.
Tests
issue161_156_158_cli_test.gochecks exact stdout on the interpreter, governed AST bytecode and HFIR bytecode for put-as-expression (flat, nested and statement), int64 JSON, the NaN relational matrix and the NaN sort/min/max/_by outputs.gofmtis clean, andgo build ./...andgo vet ./...pass.go test ./...passes locally, including the arity, drift, fuzz, lock and freeze tests andTestHFIRBenchmarkSolutionParity. The one exception isexamples/TestHTTPServerServeHFBC, which failed locally only because a stray process was holding port 8080 on the box. CI will run it clean.54009fb, so pins are unchanged.go run ./tools/difftest:optimization_signature.howlnow prints SKIP. Thetest_env.howlinterpreter failure also happens on base in this local environment.No production flip. #90 stays Partial.