From f6cceb4669d6b4482f5dff52e3fbb62e550c3703 Mon Sep 17 00:00:00 2001 From: nimbly1 <258366730+nimbly1@users.noreply.github.com> Date: Mon, 26 Oct 2026 19:51:26 -0500 Subject: [PATCH] [Fix] compare the values wrapped by two boxed primitives Two boxed primitives of the same kind were equal whatever they wrapped: `deepEqual(Object(1), Object(2))` was true, in loose and strict mode, and so were boxed `true` and `false`, boxed BigInts and boxed Symbols, alone or nested in an array or an object. Only the kind was compared, and `String` objects were told apart just because of their index keys. Compare the wrapped primitives with `Object.is`, read through the intrinsic `valueOf` so that an overridden one is never called. This is what node's `assert.deepEqual` and `assert.deepStrictEqual` do: a boxed `NaN` still equals another boxed `NaN`, and a boxed `0` no longer equals a boxed `-0`. Co-Authored-By: Claude Sonnet 5.5 --- index.js | 7 +++++++ test/cmp.js | 16 ++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/index.js b/index.js index 188dab6..0d9cf53 100644 --- a/index.js +++ b/index.js @@ -34,6 +34,11 @@ var $setDelete = callBound('Set.prototype.delete', true); var $setHas = callBound('Set.prototype.has', true); var $setSize = callBound('Set.prototype.size', true); +// the primitive that a boxed primitive wraps, read without calling a `valueOf` that the object may override +function unbox(boxed, kind) { + return callBound(kind + '.prototype.valueOf')(boxed); +} + // taken from https://github.com/browserify/commonjs-assert/blob/bba838e9ba9e28edf3127ce6974624208502f6bc/internal/util/comparisons.js#L401-L414 function setHasEqualElement(set, val1, opts, channel) { var i = getIterator(set); @@ -130,6 +135,8 @@ function internalDeepEqual(actual, expected, options, channel) { if (actualBoxed !== expectedBoxed) { return false; } + // two boxed primitives of the same kind are equal only if the primitives they wrap are the same + if (actualBoxed && !is(unbox(actual, actualBoxed), unbox(expected, actualBoxed))) { return false; } // 7.3. Other pairs that do not both pass typeof value == 'object', equivalence is determined by ==. if (!actual || !expected || (typeof actual !== 'object' && typeof expected !== 'object')) { diff --git a/test/cmp.js b/test/cmp.js index fe24d1c..5d20755 100644 --- a/test/cmp.js +++ b/test/cmp.js @@ -1042,15 +1042,31 @@ test('boxed primitives', function (t) { t.deepEqualTest(Object(''), '', 'boxed and primitive `""`', false, false); t.deepEqualTest(Object('str'), 'str', 'boxed and primitive `"str"`', false, false); + t.deepEqualTest(Object(3), Object(3), 'two boxed `3`s', true, true); + t.deepEqualTest(Object(3), Object(4), 'boxed `3` and boxed `4`', false, false); + t.deepEqualTest(Object(NaN), Object(NaN), 'two boxed `NaN`s', true, true); + t.deepEqualTest(Object(0), Object(-0), 'boxed `0` and boxed `-0`', false, false); + t.deepEqualTest(Object(true), Object(true), 'two boxed `true`s', true, true); + t.deepEqualTest(Object(true), Object(false), 'boxed `true` and boxed `false`', false, false); + t.deepEqualTest(Object('str'), Object('str'), 'two boxed `"str"`s', true, true); + t.deepEqualTest(Object('str'), Object('other'), 'boxed `"str"` and boxed `"other"`', false, false); + t.deepEqualTest([Object(3)], [Object(4)], 'boxed numbers that differ, in arrays', false, false); + t.deepEqualTest({ a: Object(3) }, { a: Object(4) }, 'boxed numbers that differ, in objects', false, false); + t.deepEqualTest({ a: Object(3) }, { a: Object(3) }, 'boxed numbers that match, in objects', true, true); + t.test('symbol', { skip: !hasSymbols }, function (st) { var s = Symbol(''); st.deepEqualTest(Object(s), s, 'boxed and primitive `Symbol()`', false, false); + st.deepEqualTest(Object(s), Object(s), 'two boxes of the same `Symbol()`', true, true); + st.deepEqualTest(Object(s), Object(Symbol('')), 'boxes of two different symbols', false, false); st.end(); }); t.test('bigint', { skip: typeof BigInt !== 'function' }, function (st) { var hhgtg = BigInt(42); st.deepEqualTest(Object(hhgtg), hhgtg, 'boxed and primitive `BigInt(42)`', false, false); + st.deepEqualTest(Object(hhgtg), Object(BigInt(42)), 'two boxed `BigInt(42)`s', true, true); + st.deepEqualTest(Object(hhgtg), Object(BigInt(43)), 'boxed `BigInt(42)` and boxed `BigInt(43)`', false, false); st.end(); });