feat(bigint): decode BigInt from JSON numbers - #4213
Open
bobzhang wants to merge 1 commit into
Open
Conversation
`Json` keeps the source text of a number literal in `Number(_, repr~)`, but nothing ever read it back: `stringify` wrote it and every `FromJson` impl ignored it. `BigInt::from_json` accepted only the JSON string that `BigInt::to_json` emits, so a document from another producer carrying `12345678901234567890123` could not be decoded without the caller destructuring `Number` by hand — and going through the `Double` would have changed the last six digits. `BigInt::from_json` now also accepts a JSON number that denotes an integer: - with `repr` present it is authoritative and must spell a plain integer literal, so an exponent-form literal such as `1e400` is rejected rather than decoded from the infinity it rounded to; - with `repr` absent the `Double` must be finite and integral, and is converted exactly through its significand and exponent — an `Int64` detour would cap the range at 2^63, well short of the integers a double holds, so `1e300` decodes to the 301-digit integer that double is. `Json::number(123)` previously failed to decode; the error test in json/from_json_test.mbt now covers `true` and `1.5` instead. Mutation-verified: ignoring `repr` fails 4 tests (the doc test decodes 12345678901234567741440), and routing through `Int64` fails 2 (2^63 saturates, 1e300 comes out 19 digits). All 7533 tests pass, and bigint plus json pass on all four backends — the js one matters here because it uses the host `BigInt`. Reviewed with Codex CLI: "The revised comments match the lexer, and the new boundary assertions match the arithmetic. No blocking issues." It also cross-checked the bit surgery against 47,499 integral doubles. Signed-off-by: Codex CLI <codex@openai.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VubGDsJHzgC6ykiq7t4hrY
Collaborator
Coverage Report for CI Build 6585Coverage increased (+0.009%) to 89.279%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
The decoder widening is internally consistent with existing Json::Number(repr) semantics and is backed by targeted tests covering key correctness boundaries and failure cases.
Pull request overview
This PR widens @json.FromJson decoding for @bigint.BigInt to accept JSON numbers that denote integers, preserving exact digits when the JSON parser provided the original numeric source text (repr) and otherwise converting finite integral Double values exactly via IEEE-754 significand/exponent decomposition.
Changes:
- Extend
BigInt::from_jsonto acceptJson::Numberin addition to the existing string encoding, with clear failure modes for non-finite and non-integral values. - Add a focused BigInt JSON-number decoding test suite covering large-integer
repr, boundary doubles, exponent-form overflow, and non-integer rejection. - Update existing JSON decode error tests to reflect the broadened decoder behavior and new error messaging.
File summaries
| File | Description |
|---|---|
bigint/bigint.mbt |
Implements Number decoding path for BigInt::from_json, including exact Double→BigInt conversion helper and updated error message for type mismatch. |
bigint/from_json_number_test.mbt |
Adds comprehensive tests for decoding BigInt from JSON numbers (including repr preservation and IEEE-754 boundary cases). |
bigint/moon.pkg |
Adds moonbitlang/core/double as a test-only dependency to support new boundary tests using @double.* constants. |
json/from_json_test.mbt |
Updates the BigInt decode error handling test to reflect that integer numbers now decode successfully and adjusts the expected error messages. |
Review details
- Files reviewed: 4/4 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
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.
Motivation
Jsoncarries the exact source text of a number literal inNumber(_, repr~)— the parser attaches it to every integer literal past the exact-integer range of aDoubleand to every literal that overflows to an infinity. Nothing ever read it back:stringifywrote it, and everyFromJsonimpl ignored it.BigInt::from_jsonaccepted only the JSON string thatBigInt::to_jsonemits, so a document from another producer could not be decoded:The digits were right there in
repr; going through theDoubleinstead would have turned the last six of them into741440.Change
BigInt::from_jsonnow also accepts a JSON number that denotes an integer:reprpresent — it is authoritative and must spell a plain integer literal. An exponent-form literal such as1e400is rejected (`1e400` is not a plain integer literal) rather than decoded from the infinity it rounded to; BigInt decoding should not quietly return a rounded value.reprabsent — theDoublemust be finite and integral, and is converted exactly through its significand and exponent. AnInt64detour would cap the range at 2^63, well short of the integers a double holds, so1e300decodes to the 301-digit integer that double actually is.BigInt::to_jsonis unchanged — it still emits a string, and its round trip still works. This only widens the decoder.One behavior change:
Json::number(123)previously failed to decode, andjson/from_json_test.mbtasserted that. It now decodes to123; the error test coverstrueand1.5instead.Out of scope
Int64/UInt64from_jsonaccepting numbers — they need a range check and a separate decision.Double→BigIntconversion as public API; it is a private helper here.Verification
reprfails 4 tests (the doc test decodes12345678901234567741440); routing throughInt64fails 2 (2^63 saturates to…807, and 1e300 comes out 19 digits long).-0.0, 2^52+1, 2^53, 2^53+1, ±2^63, 2^64,@double.max_value(asserted equal to(2N.pow(53N) - 1N) << 971, 309 digits), a subnormal,1.5, NaN, ±infinity.moon test: 7533 passed, 0 failed.moon test --target all -p moonbitlang/core/bigint -p moonbitlang/core/json: wasm 391, wasm-gc 418, js 392, native 391 — all passed. The js backend matters because it uses the hostBigInt.moon fmt,moon check,moon infoclean; no.mbtichange.Reviewed and signed off by Codex CLI, which independently cross-checked the significand/exponent conversion against 47,499 finite integral doubles including the requested boundaries.
Signed-off-by: Codex CLI codex@openai.com
🤖 Generated with Claude Code
https://claude.ai/code/session_01VubGDsJHzgC6ykiq7t4hrY