Skip to content

fix(literal): use Object.is so literal(NaN) accepts NaN (#1529)#1535

Open
sanjibani wants to merge 1 commit into
open-circle:mainfrom
sanjibani:fix/issue-1529-literal-Object-is
Open

fix(literal): use Object.is so literal(NaN) accepts NaN (#1529)#1535
sanjibani wants to merge 1 commit into
open-circle:mainfrom
sanjibani:fix/issue-1529-literal-Object-is

Conversation

@sanjibani

@sanjibani sanjibani commented Jul 9, 2026

Copy link
Copy Markdown

Closes #1529.

Bug

`literal(NaN)` was an unsatisfiable schema: the runtime value check used `===`, and `NaN === NaN` is `false`, so every input - including the `NaN` the schema was constructed from - failed validation.

`Literal` is typed as `bigint | boolean | number | string | symbol`, and `literal(NaN)` is a fully type-checked construction. The sibling `picklist` schema compares with `Array.includes` (SameValueZero) and already returns `true` for `picklist([NaN])` against `NaN`, so the two schemas gave opposite answers for the same value.

v.parse(v.literal(NaN), NaN); // throws: Invalid type: Expected NaN but received NaN
v.is(v.literal(NaN), NaN);    // false
v.is(v.picklist([NaN]), NaN);  // true

Fix

Switch the single comparison in `library/src/schemas/literal/literal.ts` from `===` to `Object.is`. `Object.is(NaN, NaN)` is `true`, so `literal(NaN)` now accepts `NaN`.

This matches the equality migration already in flight: #1517 moves `_merge` (used by `intersect`) to `Object.is`, and #1477 moves `value` / `notValue` / `values` / `notValues` to `Object.is`. Neither of those touched `literal`, so this closes the same NaN gap one layer up.

Trade-off

Like #1517 and #1477, `Object.is` also distinguishes `+0` from `-0` (they are equal under `===` and SameValueZero). The `Literal` union is `bigint | boolean | number | string | symbol`; `-0` is a valid `number`, and `literal(-0)` and `literal(0)` would still be distinguishable as different construction values but would accept each other's runtime values. The previous `===` already collapsed `+0` and `-0` at construction time, so the new behavior is a strict relaxation of `-0` distinguishability, not a tightening of any contract.

Tests

Added two regression tests:

  • `for NaN literal` in the no-issues suite: `literal(NaN)` accepts `NaN`. Fails against the unpatched code (`NaN === NaN` is `false`).
  • `for invalid NaN literal` in the issues suite: `literal(NaN)` still rejects other numbers, booleans, strings, symbols, etc. (`NaN` is the only value that should match.)

2 files changed, +35/-1.

Summary by CodeRabbit

  • Bug Fixes
    • Improved literal matching for edge-case values like NaN and signed zero.
    • literal(NaN) now accepts NaN correctly and returns no validation issues.
    • Invalid values still produce the expected validation error, including the correct displayed expected value.

`literal(NaN)` was an unsatisfiable schema: the runtime value check
used `===`, and `NaN === NaN` is `false`, so every input - including
the `NaN` the schema was constructed from - failed validation.

`Literal` is typed as `bigint | boolean | number | string | symbol`,
and `literal(NaN)` is a fully type-checked construction. The sibling
`picklist` schema compares with `Array.includes` (SameValueZero) and
already returns `true` for `picklist([NaN])` against `NaN`, so the
two schemas gave opposite answers for the same value.

Switch the single comparison in `library/src/schemas/literal/literal.ts`
from `===` to `Object.is`. `Object.is(NaN, NaN)` is `true`, so
`literal(NaN)` now accepts `NaN`.

This matches the equality migration already in flight: open-circle#1517 moves
`_merge` (used by `intersect`) to `Object.is`, and open-circle#1477 moves
`value` / `notValue` / `values` / `notValues` to `Object.is`.
Neither of those touched `literal`, so this closes the same NaN gap
one layer up.

Added two regression tests:
- `for NaN literal` in the no-issues suite: `literal(NaN)` accepts
  `NaN` (was failing before the fix).
- `for invalid NaN literal` in the issues suite: `literal(NaN)`
  still rejects other numbers, booleans, strings, symbols, etc.
  (`NaN` is the only value that should match.)

Closes open-circle#1529
@dosubot dosubot Bot added size:XS This PR changes 0-9 lines, ignoring generated files. bug Something isn't working labels Jul 9, 2026
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a56194c3-9ccb-4a40-9fdc-8ecb050b4bfb

📥 Commits

Reviewing files that changed from the base of the PR and between 32247b3 and bc98085.

📒 Files selected for processing (2)
  • library/src/schemas/literal/literal.test.ts
  • library/src/schemas/literal/literal.ts

Walkthrough

The literal schema's runtime comparison was changed from strict equality (===) to Object.is when checking whether a dataset value matches the configured literal. This resolves an issue where literal(NaN) rejected NaN due to NaN === NaN evaluating to false. Two tests were added: one confirming literal(NaN) accepts NaN without schema issues, and another confirming literal(NaN, 'message') rejects non-NaN inputs with an issue expecting 'NaN'.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: switching literal matching to Object.is for NaN support.
Linked Issues check ✅ Passed The changes satisfy issue #1529 by using Object.is in literal matching and adding regression tests for NaN behavior.
Out of Scope Changes check ✅ Passed The PR stays within scope, ограничing changes to the literal NaN fix and related regression tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No issues found across 2 files

Re-trigger cubic

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

Labels

bug Something isn't working size:XS This PR changes 0-9 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant