From e95267ab56e1b5381d51754c768f3b5a8219beb6 Mon Sep 17 00:00:00 2001 From: Ashley Claymore Date: Thu, 9 Jun 2022 15:41:11 +0100 Subject: [PATCH] update spec after #85 and support bigint --- polyfill.d.ts | 14 ++++ polyfill.js | 34 ++++++++- polyfill.test.js | 178 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 224 insertions(+), 2 deletions(-) diff --git a/polyfill.d.ts b/polyfill.d.ts index 45a5f97..1c024b8 100644 --- a/polyfill.d.ts +++ b/polyfill.d.ts @@ -78,5 +78,19 @@ declare global { toSorted(compareFn?: (a: number, b: number) => number): this; toSpliced(start: number, deleteCount?: number, ...values: number[]): this; } + + interface BigInt64Array { + with(index: number, value: bigint): this; + toReversed(): this; + toSorted(compareFn?: (a: bigint, b: bigint) => number | bigint): this; + toSpliced(start: number, deleteCount?: number, ...values: bigint[]): this; + } + + interface BigUint64Array { + with(index: number, value: bigint): this; + toReversed(): this; + toSorted(compareFn?: (a: bigint, b: bigint) => number | bigint): this; + toSpliced(start: number, deleteCount?: number, ...values: bigint[]): this; + } } export {}; diff --git a/polyfill.js b/polyfill.js index 75cc749..0829ffd 100644 --- a/polyfill.js +++ b/polyfill.js @@ -1,5 +1,6 @@ // @ts-check /// +/// ((arrayPrototype, typedArrayPrototype) => { "use strict"; @@ -44,7 +45,7 @@ return Math.max(0, Math.min(len, Number.MAX_SAFE_INTEGER)); } - /** @typedef {Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array} TypedArray */ + /** @typedef {Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array|BigInt64Array|BigUint64Array} TypedArray */ /** * @param {unknown} v @@ -92,6 +93,10 @@ return new Float32Array(length); case 'Float64Array': return new Float64Array(length); + case 'BigInt64Array': + return new BigInt64Array(length); + case 'BigUint64Array': + return new BigUint64Array(length); default: /** @type {never} */ const n = arrayName; @@ -99,6 +104,21 @@ } } + /** + * @param {TypedArray} example + * @returns {boolean} + */ + function isBigIntArray(example) { + assertTypedArray(example); + const arrayName = typedArrayNameInternalSlot(example); + switch (arrayName) { + case 'BigInt64Array': + case 'BigUint64Array': + return true; + } + return false; + } + function transfer({ count, src, srcStart, srcStep = 1, target, targetStart, targetStep = srcStep }) { let from = srcStart; let to = targetStart; @@ -249,12 +269,22 @@ const len = typedArrayLength(o); const relativeIndex = toIntegerOrInfinity(index); const actualIndex = relativeIndex < 0 ? len + relativeIndex : relativeIndex; + let asNumber; + { + if (isBigIntArray(o)) { + asNumber = 0n; + } else { + asNumber = -0; // important to use `-0` and not `0` + } + // @ts-ignore : using `+=` to emulate ToBigInt or ToNumber + asNumber += value; + } if (actualIndex < 0 || actualIndex >= len) { throw new RangeError(); } const a = typedArrayCreate(o, len); for (let k = 0; k < len; k++) { - const v = k === actualIndex ? value : o[k]; + const v = k === actualIndex ? asNumber : o[k]; a[k] = v; } return a; diff --git a/polyfill.test.js b/polyfill.test.js index d6b8b06..55e79f1 100644 --- a/polyfill.test.js +++ b/polyfill.test.js @@ -259,6 +259,29 @@ tape("Array.prototype[Symbol.unscopables]", (t) => { t.end(); }); + tape(`${TypedArray.name}.prototype.with executes 'user code' before starting copy`, (t) => { + const orig = new TypedArray([1, 2, 3]); + const idx = 1; + const valueUserCodeWillInsert = 4; + const userCodeReturnValue = 5; + const expected = new TypedArray([valueUserCodeWillInsert, userCodeReturnValue, 3]); + let userCodeExecuted = false; + /** @type any */ + const val = { + valueOf() { + userCodeExecuted = true; + orig[0] = valueUserCodeWillInsert; + return userCodeReturnValue; + } + }; + + const copy = orig.with(idx, val); + t.assert(userCodeExecuted); + t.deepEqual(copy, expected); + + t.end(); + }); + tape(`${TypedArray.name} does not use Symbol.species for the new methods`, (t) => { class SubClass extends TypedArray { } @@ -279,3 +302,158 @@ tape("Array.prototype[Symbol.unscopables]", (t) => { t.end(); }); }); + +[ + BigInt64Array, + BigUint64Array +].forEach((TypedArray) => { + tape(`${TypedArray.name}.prototype.toReversed`, (t) => { + const orig = new TypedArray([3n, 2n, 1n]); + const expected = new TypedArray([1n, 2n, 3n]); + + const copy = orig.toReversed(); + + t.deepEqual(copy, expected); + t.notEqual(orig, copy); + t.notDeepEqual(orig, copy); + t.end(); + }); + + tape(`${TypedArray.name}.prototype.toSorted`, (t) => { + const orig = new TypedArray([3n, 1n, 2n]); + const expected = new TypedArray([1n, 2n, 3n]); + + const copy = orig.toSorted(); + + t.deepEqual(copy, expected); + t.notEqual(orig, copy); + t.notDeepEqual(orig, copy); + t.end(); + }); + + tape(`${TypedArray.name}.prototype.toSorted(compareFn)`, (t) => { + const orig = new TypedArray([3n, 1n, 2n]); + const expected = new TypedArray([3n, 2n, 1n]); + function compareFn(a, b) { + return a > b ? -1 : 1; + } + + const copy = orig.toSorted(compareFn); + + t.deepEqual(copy, expected); + t.notEqual(orig, copy); + t.notDeepEqual(orig, copy); + t.end(); + }); + + tape(`${TypedArray.name}.prototype.toSpliced`, (t) => { + const orig = new TypedArray([1n, -1n, 0n, -1n, 4n]); + const expected = new TypedArray([1n, 2n, 3n, 4n]); + const idx = 1; + const delNum = 3; + const ins = [2n, 3n]; + + const copy = orig.toSpliced(idx, delNum, ...ins); + + t.deepEqual(copy, expected); + t.notEqual(orig, copy); + t.notDeepEqual(orig, copy); + t.end(); + }); + + tape(`${TypedArray.name}.prototype.with`, (t) => { + const orig = new TypedArray([1n, 1n, 3n]); + const expected = new TypedArray([1n, 2n, 3n]); + const idx = 1; + const val = 2n; + + const copy = orig.with(idx, val); + + t.deepEqual(copy, expected); + t.notEqual(orig, copy); + t.notDeepEqual(orig, copy); + t.end(); + }); + + tape(`${TypedArray.name}.prototype.with non bigint throws`, (t) => { + const orig = new TypedArray([1n, 2n, 2n]); + const idx = 3; + const val = 4; + + t.throws(() => { + // @ts-expect-error inserting number instead of bigint + orig.with(idx, val); + }, TypeError); + + t.end(); + }); + + tape(`${TypedArray.name}.prototype.with negativeIndex`, (t) => { + const orig = new TypedArray([1n, 2n, 2n]); + const expected = new TypedArray([1n, 2n, 3n]); + const idx = -1; + const val = 3n; + + const copy = orig.with(idx, val); + + t.deepEqual(copy, expected); + t.notEqual(orig, copy); + t.notDeepEqual(orig, copy); + t.end(); + }); + + tape(`${TypedArray.name}.prototype.with out of bounds`, (t) => { + const orig = new TypedArray([1n, 2n, 2n]); + const idx = 3; + const val = 4n; + + t.throws(() => { + orig.with(idx, val); + }, RangeError); + + t.end(); + }); + + tape(`${TypedArray.name}.prototype.with executes 'user code' before starting copy`, (t) => { + const orig = new TypedArray([1n, 2n, 3n]); + const idx = 1; + const valueUserCodeWillInsert = 4n; + const userCodeReturnValue = 5n; + const expected = new TypedArray([valueUserCodeWillInsert, userCodeReturnValue, 3n]); + let userCodeExecuted = false; + /** @type any */ + const val = { + valueOf() { + userCodeExecuted = true; + orig[0] = valueUserCodeWillInsert; + return userCodeReturnValue; + } + }; + + const copy = orig.with(idx, val); + t.assert(userCodeExecuted); + t.deepEqual(copy, expected); + + t.end(); + }); + + tape(`${TypedArray.name} does not use Symbol.species for the new methods`, (t) => { + class SubClass extends TypedArray { } + + function assertType(arr) { + t.equal(arr instanceof SubClass, false); + t.equal(arr instanceof TypedArray, true); + } + + /** @type {BigInt64Array} */ + // @ts-ignore + const orig = new SubClass([1n, 2n, 3n]); + + assertType(orig.with(0, 0n)); + assertType(orig.toReversed()); + assertType(orig.toSorted()); + assertType(orig.toSpliced(0, 0)); + + t.end(); + }); +});