Skip to content

fix(toml): handle Infinity/NaN and Date inside inline arrays#7171

Open
LeSingh1 wants to merge 2 commits into
denoland:mainfrom
LeSingh1:toml-inline-array-edges
Open

fix(toml): handle Infinity/NaN and Date inside inline arrays#7171
LeSingh1 wants to merge 2 commits into
denoland:mainfrom
LeSingh1:toml-inline-array-edges

Conversation

@LeSingh1

Copy link
Copy Markdown
Contributor

Partial fix for #7162.

#arrayDeclaration rendered primitive arrays by calling JSON.stringify(value), which silently turned Infinity / -Infinity / NaN into the literal null and wrapped Date values in extra quotes. Both forms broke round-trip through parse():

toml.stringify({ x: [Infinity, -Infinity, NaN] });
// x = [null,null,null]   (was supposed to be x = [inf,-inf,nan])

toml.stringify({ x: [new Date(0)] });
// x = ["1970-01-01T00:00:00.000"]   (reparsed as a string, not a Date)

The same flaw affected #printAsInlineValue, which is the path for mixed-type arrays — Date values were quoted there too, and non-finite numbers fell through as String(Infinity) / String(NaN) (not valid TOML).

The fix is two small edits:

  1. #arrayDeclaration no longer calls JSON.stringify. It maps the array through #printAsInlineValue so primitive arrays use the same TOML literal forms as mixed-type arrays.
  2. #printAsInlineValue emits inf / -inf / nan for non-finite numbers and leaves Date values unquoted, matching TOML's datetime literal syntax.

After:

x = [inf,-inf,nan]
x = [1970-01-01T00:00:00.000]
x = [inf,-inf,nan,{}]
x = [1970-01-01T00:00:00.000,{}]

The existing handles mixed array test pinned the buggy date = \"2022-05-13T00:00:00.000\" form inside a nested inline map. Updated to the correct unquoted form (and round-trip verified — parse(stringify(...)) returns a Date instance again instead of a string).

Four new tests cover the issue's specific repros.

Not included in this PR:

  • {x: [null]} / {x: [1, null]} — TOML disallows null, and the right behavior (skip vs. throw with a clearer message vs. coerce) is a design call worth its own discussion. Leaving for a follow-up.
  • Datetime offset (the trailing Z) — #printDate drops the offset suffix in all TOML output, not just arrays. Restoring it is a broader change touching every Date round-trip and would balloon this PR. Happy to file separately.

Inline array stringification used JSON.stringify, which silently turned
Infinity/-Infinity/NaN into 'null' and wrapped Date values in extra
quotes. Both forms broke round-trip through parse():

  stringify({x: [Infinity, -Infinity, NaN]}) // x = [null,null,null]
  stringify({x: [new Date(0)]})               // x = ["1970-01-01..."]

The same flaw affected #printAsInlineValue, which was the path for
mixed-type arrays — Date values were quoted and non-finite numbers
fell through as String(Infinity) / String(NaN).

Replace the JSON.stringify call in #arrayDeclaration with a per-element
walk through #printAsInlineValue, and teach #printAsInlineValue to emit
TOML's inf / -inf / nan keywords for non-finite numbers and to leave
Date values unquoted to match TOML's datetime literal syntax.

The 'handles mixed array' test pinned the buggy
date = "2022-05-13T00:00:00.000" form inside a nested inline map.
Updated to the correct unquoted form. Four new tests pin the issue's
specific repros (primitive Infinity/-Infinity/NaN, primitive Date,
mixed inf/nan/object, mixed date/object).

Fixes denoland#7162 (partially — the null-in-array cases are left for a
follow-up since the spec disallows null in TOML and the right behavior
is a separate design call).
@github-actions github-actions Bot added the toml label May 30, 2026
@codecov

codecov Bot commented May 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.62%. Comparing base (e28fa81) to head (0cbfe2a).
⚠️ Report is 16 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff            @@
##             main    #7171    +/-   ##
========================================
  Coverage   94.62%   94.62%            
========================================
  Files         628      630     +2     
  Lines       51636    51898   +262     
  Branches     9323     9380    +57     
========================================
+ Hits        48863    49111   +248     
- Misses       2208     2217     +9     
- Partials      565      570     +5     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@bartlomieju bartlomieju left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reviewed and verified locally (checked out the branch, ran round-trips against the real parse(), full deno test -A toml/ -> 687 passed, 0 failed). The fix is correct and well-scoped.

Confirmed working:

  • [Infinity, -Infinity, NaN] -> x = [inf,-inf,nan] reparses back to the exact same values.
  • Date arrays now reparse as Date instances instead of strings.
  • String/empty/number arrays produce byte-identical output to the old JSON.stringify path, so no behavioral drift there.

The change is safe by construction: #getTypeOfArray classifies any array containing a nested array as MIXED, so #arrayDeclaration only ever receives flat primitive arrays.

Updating the nestedArray1 expectation is legitimate -- it pinned the buggy quoted-date form, and the new value genuinely round-trips. A few inline notes below; none are blocking.

Comment thread toml/stringify.ts
return `"${this.#printDate(value)}"`;
// TOML datetime literals are unquoted (e.g. 1979-05-27T07:32:00Z).
// The previous wrapping in quotes caused the value to round-trip as a
// string instead of a Date (#7162).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Confirmed this fixes the type (reparses as Date, not string). Worth being explicit, though, that it does not fix the value off-UTC: #printDate drops the offset suffix, so new Date(0) -> 1970-01-01T00:00:00.000 reparses an hour off in UTC+1 (orig 0ms -> roundtrip -3600000ms).

This is not a regression -- top-level dates via #dateDeclaration already drop the offset identically, so this just makes array dates consistent. It matches the offset issue you deferred in the PR description. No change needed here; just flagging that the "round-trips as a Date" comment is type-accurate but not value-accurate.

Comment thread toml/stringify.ts
return `${this.#declaration(keys)}${JSON.stringify(value)}`;
// JSON.stringify(value) turned Infinity / -Infinity / NaN into `null` and
// wrapped Date values in extra quotes, producing arrays that round-trip
// incorrectly (#7162). Use the per-element inline formatter instead so

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Minor (optional): value.map((x) => this.#printAsInlineValue(x)).join(",") now appears in three spots -- here, #printObject (the MIXED branch), and the array case inside #printAsInlineValue itself. Could collapse into a small #inlineArray(value) helper. The duplication predates this PR, so fine to leave, but it's a natural cleanup point now that this path also uses it.

Comment thread toml/stringify_test.ts

// https://github.com/denoland/std/issues/7162 — inline array stringification
// used JSON.stringify, which turns Infinity/-Infinity/NaN into null and
// wraps Date values in quotes. Both forms broke round-trip through parse().

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggestion (non-blocking): consider adding a parse(stringify(x)) deep-equality assertion for the non-finite case. The exact-value round-trip is the real win here (verified: [Infinity, -Infinity, NaN] survives intact), and asserting it directly locks in the stringify<->parse contract against future parser changes.

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