Skip to content

TOML: map date/time values to Temporal, round-trip them through stringify - #37018

Merged
dylan-conway merged 23 commits into
mainfrom
farm/76e35552/toml-temporal-dates
Aug 14, 2026
Merged

dylan-conway merged 23 commits into
mainfrom
farm/76e35552/toml-temporal-dates

Conversation

@robobun

@robobun robobun commented Aug 6, 2026 •

Copy link
Copy Markdown
Collaborator

TOML.parse returned all four TOML date/time types as strings of their source text. They now map 1:1 and losslessly onto Temporal, which is enabled by default on main:

TOML JS
offset date-time (1979-05-27T00:32:00-07:00) Temporal.Instant
local date-time (1979-05-27T07:32:00, 1.1's 1979-05-27 07:32) Temporal.PlainDateTime
local date (1979-05-27) Temporal.PlainDate
local time (07:32:00, 07:32) Temporal.PlainTime

Per the spec an offset date-time "specifies an instant", so it is Temporal.Instant and the written offset normalizes to UTC. Sub-second digits are preserved (Temporal carries nanoseconds; fractional seconds beyond 9 digits are truncated as the TOML spec directs, since Temporal rejects them). Leap-second :60 clamps to :59 through Temporal's own ISO parsing.

const doc = Bun.TOML.parse("d = 1979-05-27T00:32:00-07:00");
doc.d instanceof Temporal.Instant; // true
doc.d.toString(); // "1979-05-27T07:32:00Z"
Bun.TOML.stringify(doc); // "d = 1979-05-27T07:32:00Z\n" (unquoted date-time literal)

How

  • The TOML parser tags the E::String it produces with the date/time kind (toml_datetime on EString); the lexer already distinguished the four kinds. The TOML AST never enters the JS visit/transform passes, so only the TOML sinks check the tag.
  • All three sinks of the TOML AST stay consistent:
    • Bun.TOML.parse and import/require of .toml construct the object through the same JSC code paths Temporal.*.from(string) uses (new bindings in bindings.cpp).
    • The bundler lowers the node to a Temporal.*.from("...") call over a real unbound Temporal symbol, so the chunk renamer renames a user binding named Temporal instead of letting it capture the reference, and the calls are pure-annotated so unused exports tree-shake.
    • bun build --no-bundle of a .toml file (which prints the data AST directly, without a symbol table) prints the tagged string as a bare Temporal.*.from("...") call. That path turns each top-level key into a module-scope var, so a document with both a date/time and a top-level key literally named Temporal fails at evaluation with a TypeError; renaming without a symbol table was tried and dropped as open-ended.
  • TOML.stringify emits Temporal.Instant/PlainDateTime/PlainDate/PlainTime as unquoted TOML literals, so stringify(parse(doc)) round-trips date/times. Temporal.ZonedDateTime is also accepted and emits the offset at that instant, dropping the [Time/Zone] annotation (TOML has no zone syntax); non-ISO [u-ca=...] calendar annotations are dropped the same way (the stored ISO fields are emitted). PlainYearMonth/PlainMonthDay/Duration have no TOML form and throw. Values TOML's 4-digit years cannot spell throw like Date already did, with one refinement for instants: an offset date-time at a year edge (0000-01-01T00:00:00+01:00 is valid TOML but its UTC year is -1) is emitted with the nearest offset whose local year fits, so everything parse accepts also stringifies; only instants a day or more outside 0000..9999 throw. Date output now trims trailing fraction zeros (Z rather than .000Z) so Date and Instant spell the same instant identically; otherwise Date handling is unchanged.
  • With BUN_JSC_useTemporal=0, a date/time value throws a TypeError ("Date/time values require Temporal, which is disabled in this process"); the module-import path previously would have panicked on any conversion error and now fails the load with the pending exception.
  • bunfig.toml uses no date values, so config parsing is unaffected.

Everything else about parse/stringify (error messages, layout, numbers) is byte-for-byte unchanged.

Tests

  • Regenerated toml-test-suite.test.ts (708 cases) from the same pinned toml-test commit: datetime expectations construct the Temporal value and compare with plain toEqual (deepEquals learned Temporal objects in Compare Temporal objects by value in Bun.deepEquals and toEqual #37024). All pass, including the parse(stringify(parse(input))) lap each valid case asserts.
  • Hand-written coverage in toml.test.ts: the four mappings, instant semantics, TOML spellings Temporal does not print (space separator, lowercase t/z, omitted seconds, leap second), fraction truncation, nesting, both useTemporal=0 behaviors, stringify of all eight Temporal types, year bounds, calendar/zone annotation dropping, array layout.
  • Import fixtures (test/js/bun/resolve/toml) extended with a [dates] table; bundler tests assert the emitted Temporal.*.from modules run and that a user var Temporal in the same bundle is renamed instead of captured.

Types and docs updated (bun.d.ts JSDoc, docs/runtime/toml.mdx).


[review] gate passed · iteration 3 · 25 files touched

fails on main (without fix)
ASAN without fix: 53 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/bundler/bundler_loader.test.ts test/js/bun/resolve/toml/toml.test.js test/js/bun/toml/toml-test-suite.test.ts test/js/bun/toml/toml.test.ts
bun test v1.4.0 (48b6af953)

test/bundler/bundler_loader.test.ts:
(pass) bundler > bun loader > bun/loader-yaml-file [1013.43ms]
(pass) bundler > bun loader > bun/loader-text-file [412.02ms]
(pass) bundler > bun loader > bun/loader-json-file [489.14ms]
(pass) bundler > bun loader > bun/loader-toml-file [429.12ms]
(pass) bundler > bun loader > bun/loader-toml-datetime-shadowed-temporal-global [413.44ms]
runtime failed file: /tmp/bun-build-tests/bun-avOXit/bun/loader-toml-datetime-imported-temporal-binding/out.js
stdout output:
polyfill false 1979-05-27
---
expected stdout:
polyfill true 1979-05-27
---
1843 |               console.log(`---`);
1844 |               console.log(`expected ${name}:`);
1845 |               console.log(expected);
1846 |               console.log(`---`);
1847 |             }
1848 |             expect(result).toBe(expected);
                                  ^
error: exp
... (truncated)

release without fix: 53 FAILED
bun test v1.4.0-canary.1 (b7a043103)

test/bundler/bundler_loader.test.ts:
(pass) bundler > bun loader > bun/loader-yaml-file [31.84ms]
(pass) bundler > bun loader > bun/loader-text-file [14.09ms]
(pass) bundler > bun loader > bun/loader-json-file [18.21ms]
(pass) bundler > bun loader > bun/loader-toml-file [13.89ms]
(pass) bundler > bun loader > bun/loader-toml-datetime-shadowed-temporal-global [22.56ms]
runtime failed file: /tmp/bun-build-tests/bun-Rboeq6/bun/loader-toml-datetime-imported-temporal-binding/out.js
stdout output:
polyfill false 1979-05-27
---
expected stdout:
polyfill true 1979-05-27
---
1843 |               console.log(`---`);
1844 |               console.log(`expected ${name}:`);
1845 |               console.log(expected);
1846 |               console.log(`---`);
1847 |             }
1848 |             expect(result).toBe(expected);
                                  ^
error: expect(received).toBe(expected)

Expected: "polyfill true 1979-05-27"
Received: "polyfill false 1979-05-27"

      at <anonymous> (/workspace/bun/test/bundler/expectBundled.ts:1848:28)
(fail) bundler > bun loader > bun/loader-toml-datetime-imported-temporal-binding [18.93ms]
93
... (truncated)
passes on PR (with fix)
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/bundler/bundler_loader.test.ts test/js/bun/resolve/toml/toml.test.js test/js/bun/toml/toml-test-suite.test.ts test/js/bun/toml/toml.test.ts
bun test v1.4.0 (48b6af953)

test/bundler/bundler_loader.test.ts:
(pass) bundler > bun loader > bun/loader-yaml-file [1011.62ms]
(pass) bundler > bun loader > bun/loader-text-file [508.58ms]
(pass) bundler > bun loader > bun/loader-json-file [360.53ms]
(pass) bundler > bun loader > bun/loader-toml-file [381.91ms]
(pass) bundler > bun loader > bun/loader-toml-datetime-shadowed-temporal-global [387.83ms]
(pass) bundler > bun loader > bun/loader-toml-datetime-imported-temporal-binding [377.21ms]
(pass) bundler > bun loader > bun/loader-toml-datetime-no-bundle [464.49ms]
(pass) bundler > bun loader > bun/loader-toml-datetime [433.16ms]
(pass) bundler > bun loader > bun/loader-text-file [398.02ms]
(pass) bundler > bun loader > bun/loader-xml-file [432.77ms]
(pass) bundler > node loader > bun/loader-yaml-file [427.98ms]
(pass) bundler > node loader > bun/loader-text-file [385.65ms]
(pass) bundler > 
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 652ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/23] gen generated_host_exports.rs
generated_host_exports.rs: 92 exports (host=3, lazy=10, generic=79, rust=0); 240 extern-C blocks audited
[2/23] gen cpp.rs (cppbind)
[3/23] gen JS modules (bundle-modules)
Preprocess modules (8539ms)
Bundle modules (64ms)
Postprocesss modules (324ms)
Bundle Functions (858ms)
Generate Code (29ms)

[9.83s] Bundled "src/js" for production
  2632 kb
  197 internal modules
  13 native modules
  91 internal functions across 17 files
[3/8] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)

  nightly-2026-07-20-x86_64-unknown-linux-gnu unchanged - rustc 1.99.0-nightly (9f36de775 2026-07-19)

�[1m�[92m   Compiling�[0m bun_ast v0.0.0 (/workspace/bun/src/ast)
�[1m�[92m   Compiling�[0m bun_install_types v0.0.0 (/workspace/bun/src/install_types)
�[1m�[92m   Compiling�[0m bun_parsers v0.0.0 (/workspace/bun/src/parsers)
�[1m�[92m   Compiling�[0m bun_react_compiler v0.0.0 (/workspace/bun/src/react_compiler)
�[1m�[92m   Compiling�[0m bun_css v0.0.0 (/workspace/bun/src/
... (truncated)
diff hotspot
docs/runtime/toml.mdx                          |  38 +-
 packages/bun-types/bun.d.ts                    |  20 +-
 src/ast/e.rs                                   |  45 ++
 src/ast/expr.rs                                |   1 +
 src/bundler/ParseTask.rs                       |  30 +-
 src/bundler/bundle_v2.rs                       |   2 +-
 src/bundler/transpiler.rs                      |   1 +
 src/codegen/cppbind.ts                         |   1 +
 src/js_parser/parse/parse_entry.rs             | 117 +++
 src/js_parser_jsc/expr_jsc.rs                  |  26 +-
 src/js_parser_jsc/lib.rs                       |   3 +-
 src/js_printer/lib.rs                          |  21 +
 src/jsc/JSValue.rs                             |  19 +
 src/jsc/bindings/bindings.cpp                  | 155 ++++
 src/jsc/lib.rs                                 |   1 +
 src/parsers/toml.rs                            | 129 ++--
 src/runtime/api.rs                             |  63 +-
 src/runtime/api/TOMLObject.rs                  | 150 +++-
 test/bundler/bundler_loader.test.ts            |  67 ++
 test/js/bun/resolve/toml/toml-fixture.toml     |   6 +
 test/js/bun/resolve/toml/toml-fixture.toml.txt |   6 +
 test/js/bun/resolve/toml/toml.test.js          |   9 +
 test/js/bun/toml/generate_toml_test_suite.ts   |  93 +--
 test/js/bun/toml/toml-test-suite.test.ts       | 951 ++++++++++++-------------
 test/js/bun/toml/toml.test.ts                  | 297 ++++++--
 25 files changed, 1494 insertions(+), 757 deletions(-)

gate history · 7 passed · 0 rejected · iteration 3

evidence per changed file
file                                reads  edits  tests
docs/runtime/toml.mdx                   1      1      0
packages/bun-types/bun.d.ts             1      1      0
src/ast/e.rs                            6      8      0
src/ast/expr.rs                         5      6      0
src/bundler/ParseTask.rs                2      1      0
src/bundler/bundle_v2.rs                1      1      0
src/bundler/transpiler.rs               5      7      0
src/codegen/cppbind.ts                  0      0      0
src/js_parser/parse/parse_entry.rs      4     10      0
src/js_parser_jsc/expr_jsc.rs           4      7      0
src/js_parser_jsc/lib.rs                1      2      0
src/js_printer/lib.rs                   3      7      0
src/jsc/JSValue.rs                      2      4      0
src/jsc/bindings/bindings.cpp           7     11      0
src/jsc/lib.rs                          0      0      0
src/parsers/toml.rs                     5      7      0
(+ 9 more files)

… as date/time literals

TOML.parse previously returned all four TOML date/time types as strings
of their source text. They now map 1:1 onto Temporal:

- offset date-time -> Temporal.Instant (an offset date-time specifies an
  instant; the written offset normalizes to UTC)
- local date-time -> Temporal.PlainDateTime (including TOML 1.1's space
  separator and omitted seconds)
- local date -> Temporal.PlainDate
- local time -> Temporal.PlainTime

The parser now produces a dedicated E::DateTime AST node carrying the
kind and source text (fractional seconds truncated to Temporal's 9-digit
limit, as the TOML spec directs). All three sinks of the TOML AST stay
consistent: Bun.TOML.parse and import/require construct the Temporal
object through the same JSC code paths Temporal.*.from(string) uses, and
the bundler lowers the node to a Temporal.*.from("...") call over a real
unbound Temporal symbol so chunk renaming protects the global reference
and unused date exports stay tree-shakable.

TOML.stringify now emits Temporal.Instant, PlainDateTime, PlainDate, and
PlainTime as unquoted TOML date/time literals, so stringify(parse(doc))
round-trips date/times instead of re-quoting them as strings.
Temporal.ZonedDateTime emits its offset form (the time-zone annotation
has no TOML representation), non-ISO calendar annotations are dropped
the same way, and values outside TOML's 4-digit years throw like Date
already did. PlainYearMonth, PlainMonthDay, and Duration have no TOML
form and throw. Date is unchanged.

With BUN_JSC_useTemporal=0 a date/time value now throws a TypeError
(and a TOML module import fails with that exception instead of
panicking).

The toml-test conformance suite is regenerated: expectations compare
Temporal class + canonical toString, since Temporal instances have no
own properties for toEqual to see.
@coderabbitai

coderabbitai Bot commented Aug 6, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

TOML date/time literals now retain their specific kind, parse into corresponding Temporal objects, and serialize back to TOML-compatible values. Bundler output, runtime conversion, error propagation, tests, and documentation were updated for the new behavior.

Changes

TOML Temporal date/time handling

Layer / File(s) Summary
Date/time classification and AST metadata
src/parsers/toml.rs, src/ast/e.rs, src/ast/expr.rs
The parser classifies the four TOML date/time forms, truncates excess fractional precision, and preserves the kind through AST cloning.
Runtime Temporal conversion
src/jsc/bindings/bindings.cpp, src/js_parser_jsc/..., src/runtime/api.rs
Tagged literals convert to Temporal.Instant, Temporal.PlainDateTime, Temporal.PlainDate, or Temporal.PlainTime. Conversion errors propagate through the JavaScript runtime.
Bundler and printed module output
src/js_parser/parse/parse_entry.rs, src/js_printer/lib.rs, src/bundler/transpiler.rs
Nested TOML date/time values emit globalThis.Temporal reconstruction calls. Bundler name collisions are handled without capturing Temporal references.
Lazy AST error propagation
src/bundler/ParseTask.rs, src/bundler/bundle_v2.rs
Loader and generated HTML paths return parser errors when lazy AST generation produces no AST.
Temporal TOML serialization
src/runtime/api/TOMLObject.rs, src/jsc/bindings/bindings.cpp
TOML.stringify serializes supported Temporal types and Date, normalizes offsets and precision, and rejects unsupported types, placements, and years.
Behavioral test coverage
test/js/bun/toml/*, test/bundler/bundler_loader.test.ts, test/js/bun/resolve/toml/*
Tests cover Temporal type mapping, canonicalization, nested values, bundling, disabled Temporal support, serialization errors, and round trips.
Documentation and declarations
docs/runtime/toml.mdx, packages/bun-types/bun.d.ts
TOML parsing and stringification documentation describes Temporal mappings, supported values, normalization, and rejection rules.

Suggested reviewers: alii, dylan-conway, jarred-sumner

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The description clearly explains the change, implementation details, scope, edge cases, and verification results, despite using different section headings than the template.
Title check ✅ Passed The title clearly and concisely summarizes the main changes: mapping TOML date/time values to Temporal and supporting stringify round trips.

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the claude label Aug 6, 2026
The TOML AST never enters the JS visit/transform passes (SLazyExport is
not visited; the runtime import and Bun.TOML.parse convert the raw expr
directly), so a dedicated expression variant is not needed: a
toml_datetime tag on EString carries the same information, and only the
TOML sinks (expr_to_js, data_to_js, to_lazy_export_ast, the printer's
EString arm) check it. The bundler still rewrites tagged strings into
Temporal.*.from calls over an unbound Temporal symbol for rename safety
and tree shaking; behavior is unchanged and the test suite is identical.
Comment thread src/ast/e.rs Outdated
Comment thread src/ast/e.rs Outdated
Comment thread src/ast/e.rs Outdated
Comment thread src/js_parser/parse/parse_entry.rs Outdated
Comment thread src/js_parser/parse/parse_entry.rs Outdated
Comment thread src/js_printer/lib.rs Outdated
Comment thread src/jsc/bindings/bindings.cpp Outdated
Comment thread src/jsc/bindings/bindings.cpp Outdated
Comment thread src/jsc/bindings/bindings.cpp Outdated
Comment thread src/jsc/bindings/bindings.cpp Outdated
Comment thread src/jsc/bindings/bindings.cpp Outdated
Comment thread src/jsc/bindings/bindings.cpp Outdated
Comment thread src/parsers/toml.rs
Comment thread src/parsers/toml.rs Outdated
Comment thread src/runtime/api/TOMLObject.rs Outdated
Comment thread src/runtime/api/TOMLObject.rs Outdated
Comment thread src/runtime/api/TOMLObject.rs Outdated
Comment thread src/runtime/api/TOMLObject.rs Outdated
Comment thread src/runtime/jsc_hooks.rs Outdated
Comment thread src/ast/e.rs
Comment thread src/ast/e.rs
Comment thread src/ast/e.rs
Comment thread src/js_parser/parse/parse_entry.rs
Comment thread src/jsc/bindings/bindings.cpp Outdated
Comment thread src/jsc/bindings/bindings.cpp Outdated
Comment thread src/runtime/api/TOMLObject.rs Outdated
Comment thread src/bundler/ParseTask.rs
…hter comments, docs/types wording for truncation, annotations, year bounds
Comment thread src/jsc/bindings/bindings.cpp
Comment thread src/jsc/bindings/bindings.cpp Outdated
Comment thread src/jsc/bindings/bindings.cpp
…xport unwrap

parse_data_loader hand-builds the module without a symbol table, so a
top-level TOML key that shadows a global the printed code needs cannot
be handled the way the bundler does. Renaming vars by string comparison
was open-ended; a key named globalThis next to a date/time on this path
now fails at evaluation with a TypeError instead.
Comment thread src/js_printer/lib.rs Outdated
Comment thread src/js_printer/lib.rs
…sc::TemporalType

Main's WebKit already provides JSC::TemporalType / JSC::temporalType(), so
drop the hand-rolled inherits<> chain and the TOML-private enum in favor
of Bun__JSValue__temporalType -> bun_jsc::TemporalType, the same surface
the Temporal console formatting work uses.
Comment thread src/jsc/bindings/bindings.cpp

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed the latest revision (through 0a4b019) and the automated bug-hunting pass found no issues; my earlier inline nits on ParseTask.rs and the deep-header test have all been addressed. Given the scope — new user-facing API surface (breaking change to TOML.parse return types, Date output format tweak), new C++ Temporal bindings with uncheckedDowncast, and the deliberate --no-bundle limitation for a top-level Temporal key — a maintainer sign-off is still warranted.

What was reviewed:

  • Exception handling in Bun__Temporal__fromDateTimeLiteral / toTOMLDateTime — throw scopes and RETURN_IF_EXCEPTION are placed correctly; uncheckedDowncast is guarded by the caller's temporalType classification.
  • The 64-byte stack buffer vs. worst-case Temporal string length (35 bytes for a 9-frac-digit offset date-time) — RELEASE_ASSERT(length <= bufLen) holds.
  • tomlOffsetForInstant Int128 arithmetic and the year-edge offset selection round-trip cases.
  • EString layout change (new Option<TomlDateTimeKind> field) propagated through Default, init, and the expr.rs clone path.
Extended reasoning...

Overview

This PR changes Bun.TOML.parse to return Temporal objects for TOML's four date/time types instead of source-text strings, and teaches TOML.stringify to emit Temporal (and Date) values as unquoted TOML date/time literals so parse↔stringify round-trips. It touches 25 files across the TOML parser (src/parsers/toml.rs), AST (src/ast/e.rs, expr.rs), three consumption sinks (runtime expr_jsc.rs, bundler parse_entry.rs, printer lib.rs), new C++ JSC bindings (bindings.cpp — Bun__Temporal__fromDateTimeLiteral, Bun__Temporal__toTOMLDateTime, Bun__JSValue__temporalType, tomlOffsetForInstant), the TOML.stringify implementation, docs, types, and ~1000 lines of test changes including a regenerated 708-case conformance suite.

Security risks

None identified. Input is TOML source text validated by the existing scanner before reaching Temporal construction; the new truncate_fractional_seconds bounds its allocation by input length. The C++ toTOMLDateTime writes into a caller-supplied 64-byte stack buffer with a RELEASE_ASSERT(length <= bufLen) guard; the longest possible output (9999-12-31T23:59:59.999999999-23:59, 35 bytes) fits comfortably. uncheckedDowncast on Temporal cells is safe because temporalType was already computed on the same value and has_toml_form gates the switch arms.

Level of scrutiny

High. This is a breaking API change (date/times were strings, now Temporal objects), adds new C++↔Rust FFI surface with manual buffer handling, embeds an API design decision (offset date-time → Instant rather than ZonedDateTime; year-edge instants pick a fitting offset rather than throwing; .000Z → Z for Date), and accepts a known --no-bundle limitation (top-level Temporal key shadows the printed global). These are exactly the kind of decisions REVIEW.md says need maintainer agreement, not automated approval.

Other factors

The PR has been through extensive iteration: earlier rounds fixed iterative-walk stack safety, converted all new_lazy_export_ast .unwrap() sites, dropped an open-ended globalThis mangling scheme, and addressed docs/types wording. All 40+ review threads are marked resolved, and dylan-conway has been actively engaged resolving the latest round. Test coverage is thorough (conformance suite round-trip lap, bundler symbol-collision tests, both useTemporal=0 paths, GC stress, year-edge boundary cases). No bugs were found on this revision. The remaining question is maintainer sign-off on the API shape and the --no-bundle trade-off, which is not something an automated review should decide.

@robobun

robobun commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

CI on 0a4b019 (build 93779): no failing job. The red status is two darwin 26 aarch64 - test-bun shards that were canceled without ever being assigned an agent (runnable 01:03, canceled 01:45; the release-tier=latest aarch64 queue is one box with two slots and was backlogged; the same lane passes on main's build 94234 when it gets an agent). Every test listed in the annotations passed on retry (complex-workspace, napi, sourcetextmodule-leak in the parallel batch, next-pages ssr and bun-install-registry on Windows aarch64, grpc-js test-tonic on darwin 14); none of them touch TOML, Temporal, the lazy-export path, or the printer. All other lanes, including darwin 14 aarch64 and darwin 14 x64, are green.

@dylan-conway
dylan-conway merged commit 056491f into main Aug 14, 2026
11 of 12 checks passed
@dylan-conway
dylan-conway deleted the farm/76e35552/toml-temporal-dates branch August 14, 2026 23:34

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed this PR and the bug-hunting pass found no issues. Given it's a user-facing API change (TOML date/times now return Temporal objects instead of strings) that spans the parser, printer, bundler lazy-export path, and new JSC C++ bindings, a maintainer sign-off on the API mapping and the --no-bundle shadowing trade-off would still be worthwhile.

What was reviewed:

  • The three sinks (JSC materialization, bundler lowering, printer) stay consistent; the unbound Temporal symbol lets the chunk renamer handle collisions.
  • Bun__Temporal__toTOMLDateTime buffer bound: longest output (9999-12-31T23:59:59.999999999-23:59, 35 bytes) fits the 64-byte stack buffer, guarded by RELEASE_ASSERT.
  • tomlOffsetForInstant Int128 arithmetic and the year-edge offset selection round-trip against the parse-side test cases.
  • Exception handling in the new C++ bindings uses RETURN_IF_EXCEPTION after every fallible Temporal call; the useTemporal=0 path throws instead of reaching the missing structures.
Extended reasoning...

Overview

This PR changes Bun.TOML.parse to return Temporal objects (Instant/PlainDateTime/PlainDate/PlainTime) for TOML's four date/time types instead of source-text strings, and teaches TOML.stringify to emit all serializable Temporal types as unquoted TOML literals. It touches 25 files across the AST (toml_datetime tag on EString), the TOML parser, the JS printer, the bundler's lazy-export lowering (lower_date_time_literals in parse_entry.rs), the runtime expr_to_js converter, and ~130 lines of new C++ in bindings.cpp (Bun__Temporal__fromDateTimeLiteral, Bun__Temporal__toTOMLDateTime, tomlOffsetForInstant). The net diff is ~+1540/-770, plus a regenerated 708-case conformance suite and extensive hand-written tests.

Security risks

None identified. The only untrusted-input surface is the TOML scanner, which already validated date/time syntax; the new truncate_fractional_seconds operates on that validated ASCII slice. The C++ toTOMLDateTime writes into a caller-provided stack buffer with a RELEASE_ASSERT(length <= bufLen) bound; the longest possible output is well under 64 bytes. No auth, crypto, or filesystem paths are involved.

Level of scrutiny

High. This is a user-facing behavioral change to a documented API — code that previously received strings from TOML.parse will now receive Temporal objects. The PR encodes several API design decisions a maintainer should ratify: offset date-time → Instant (offset normalizes away) rather than preserving the written offset; ZonedDateTime and non-ISO calendars silently drop annotations on stringify; the --no-bundle path deliberately fails at runtime with a TypeError if a top-level TOML key is literally Temporal (the earlier string-mangling approach was removed in c45d52f after three review rounds). It also adds new C++ JSC bindings that use uncheckedDowncast after a Rust-side type discrimination — correct as written, but the kind of code REVIEW.md flags for careful review.

Other factors

The PR has been through many review iterations: eight prior inline findings from this system (all resolved with code changes — iterative worklist instead of recursion, .ok_or(ParserError)? on all 14 new_lazy_export_ast sites, concurrent stderr draining in tests), plus CodeRabbit and comment-cop passes. dylan-conway has been actively pushing fixes and resolving threads, so a human is already engaged, but no independent approval is on the thread. Test coverage is thorough (conformance suite round-trips, boundary years, sub-minute LMT offsets, GC stress, useTemporal=0 on both parse and import paths, bundler symbol-renaming tests). CI is green on all lanes that ran. Given the API-design surface and the C++ bindings, deferring rather than auto-approving.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants