From a0a7fc89c98f0938eaaf0f3a5b73043969a51760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Fri, 26 Jan 2024 21:57:22 +0100 Subject: [PATCH 1/3] Implement ES2023 Array.prototype.with (https://262.ecma-international.org/14.0/#sec-array.prototype.with) --- include/hermes/VM/NativeFunctions.def | 1 + include/hermes/VM/PredefinedStrings.def | 1 + lib/VM/JSLib/Array.cpp | 162 +++++++++++++++++++++--- test/hermes/array-functions.js | 31 +++++ 4 files changed, 179 insertions(+), 16 deletions(-) diff --git a/include/hermes/VM/NativeFunctions.def b/include/hermes/VM/NativeFunctions.def index e76b21d75c2..5cbb1a7a518 100644 --- a/include/hermes/VM/NativeFunctions.def +++ b/include/hermes/VM/NativeFunctions.def @@ -57,6 +57,7 @@ NATIVE_FUNCTION(arrayPrototypeSlice) NATIVE_FUNCTION(arrayPrototypeSome) NATIVE_FUNCTION(arrayPrototypeUnshift) NATIVE_FUNCTION(arrayPrototypeSplice) +NATIVE_FUNCTION(arrayPrototypeWith) NATIVE_FUNCTION(asyncFunctionConstructor) NATIVE_FUNCTION(atob) diff --git a/include/hermes/VM/PredefinedStrings.def b/include/hermes/VM/PredefinedStrings.def index 7c266666a5f..59a91a591c9 100644 --- a/include/hermes/VM/PredefinedStrings.def +++ b/include/hermes/VM/PredefinedStrings.def @@ -200,6 +200,7 @@ STR(includes, "includes") STR(subarray, "subarray") STR(flat, "flat") STR(flatMap, "flatMap") +STR(with, "with") STR(ArrayBuffer, "ArrayBuffer") STR(byteLength, "byteLength") diff --git a/lib/VM/JSLib/Array.cpp b/lib/VM/JSLib/Array.cpp index 8a737170a8c..71e57c93468 100644 --- a/lib/VM/JSLib/Array.cpp +++ b/lib/VM/JSLib/Array.cpp @@ -128,6 +128,13 @@ Handle createArrayConstructor(Runtime &runtime) { (void *)IterationKind::Entry, arrayPrototypeIterator, 0); + defineMethod( + runtime, + arrayPrototype, + Predefined::getSymbolID(Predefined::with), + nullptr, + arrayPrototypeWith, + 2); auto propValue = runtime.ignoreAllocationFailure(JSObject::getNamed_RJS( arrayPrototype, runtime, Predefined::getSymbolID(Predefined::values))); @@ -546,6 +553,28 @@ arrayPrototypeToLocaleString(void *, Runtime &runtime, NativeArgs args) { return HermesValue::encodeStringValue(*builder->getStringPrimitive()); } +static inline CallResult lengthOfArrayLike( + Runtime &runtime, + const Handle &O, + const Handle &jsArr) { + if (LLVM_LIKELY(jsArr)) { + // Fast path for getting the length. + return JSArray::getLength(jsArr.get(), runtime); + } + // Slow path + CallResult> propRes = JSObject::getNamed_RJS( + O, runtime, Predefined::getSymbolID(Predefined::length)); + if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + auto lenRes = toLength(runtime, runtime.makeHandle(std::move(*propRes))); + if (LLVM_UNLIKELY(lenRes == ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + + return lenRes->getNumber(); +} + // 23.1.3.1 CallResult arrayPrototypeAt(void *, Runtime &runtime, NativeArgs args) { @@ -559,23 +588,11 @@ arrayPrototypeAt(void *, Runtime &runtime, NativeArgs args) { // 2. Let len be ? LengthOfArrayLike(O). Handle jsArr = Handle::dyn_vmcast(O); - uint32_t len = 0; - if (LLVM_LIKELY(jsArr)) { - // Fast path for getting the length. - len = JSArray::getLength(jsArr.get(), runtime); - } else { - // Slow path - CallResult> propRes = JSObject::getNamed_RJS( - O, runtime, Predefined::getSymbolID(Predefined::length)); - if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { - return ExecutionStatus::EXCEPTION; - } - auto lenRes = toLength(runtime, runtime.makeHandle(std::move(*propRes))); - if (LLVM_UNLIKELY(lenRes == ExecutionStatus::EXCEPTION)) { - return ExecutionStatus::EXCEPTION; - } - len = lenRes->getNumber(); + auto lenRes = lengthOfArrayLike(runtime, O, jsArr); + if (LLVM_UNLIKELY(lenRes == ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; } + auto len = lenRes.getValue(); // 3. Let relativeIndex be ? ToIntegerOrInfinity(index). auto idx = args.getArgHandle(0); @@ -3512,6 +3529,119 @@ arrayPrototypeIncludes(void *, Runtime &runtime, NativeArgs args) { return HermesValue::encodeBoolValue(false); } +/// ES14.0 23.1.3.39 +CallResult +arrayPrototypeWith(void *, Runtime &runtime, NativeArgs args) { + GCScope gcScope{runtime}; + + // 1. Let O be ? ToObject(this value). + auto oRes = toObject(runtime, args.getThisHandle()); + if (LLVM_UNLIKELY(oRes == ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + auto O = runtime.makeHandle(*oRes); + + // 2. Let len be ? LengthOfArrayLike(O). + Handle jsArr = Handle::dyn_vmcast(O); + auto lenRes = lengthOfArrayLike(runtime, O, jsArr); + if (LLVM_UNLIKELY(lenRes == ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + auto len = lenRes.getValue(); + + // 3. Let relativeIndex be ? ToIntegerOrInfinity(index). + auto relativeIndexRes = toIntegerOrInfinity(runtime, args.getArgHandle(0)); + if (LLVM_UNLIKELY(relativeIndexRes == ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + // Use double here, because ToInteger may return Infinity. + double relativeIndex = relativeIndexRes->getNumber(); + + double actutalIndex = 0; + // 4. If relativeIndex ≥ 0, let actualIndex be relativeIndex. + if (relativeIndex >= 0) { + actutalIndex = relativeIndex; + } + // 5. Else, let actualIndex be len + relativeIndex. + else { + actutalIndex = len + relativeIndex; + } + + // 6. If actualIndex ≥ len or actualIndex < 0, throw a RangeError exception. + if (actutalIndex < 0 || actutalIndex >= len) { + return runtime.raiseRangeError("invalid or out-of-range index"); + } + + // 7. Let A be ArrayCreate(len). + auto ARes = JSArray::create(runtime, len, len); + if (LLVM_UNLIKELY(ARes == ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + auto A = ARes.getValue(); + + // 8. Let k be 0. + double k = 0; + + MutableHandle<> kHandle{runtime}; + MutableHandle<> fromValueHandle{runtime}; + + auto marker = gcScope.createMarker(); + // 9. Repeat, while k < len, + while (k < len) { + gcScope.flushToMarker(marker); + + // 9a. Let Pk be the result of ? Get(O, ! ToString(k)). + kHandle = HermesValue::encodeUntrustedNumberValue(k); + auto PkRes = JSObject::getComputed_RJS(O, runtime, kHandle); + if (LLVM_UNLIKELY(PkRes == ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + + // 9b. If k is actualIndex, let fromValue be value. + if (k == actutalIndex) { + fromValueHandle = args.getArgHandle(1); + } + // 9c. Else, let fromValue be ? Get(O, Pk). + else { + if (LLVM_LIKELY(jsArr)) { + const SmallHermesValue elm = jsArr->at(runtime, k); + // If the element is not empty, we can return it directly here. + // Otherwise, we must proceed to the slow path. + if (!elm.isEmpty()) { + fromValueHandle = elm.unboxToHV(runtime); + } + } + // Slow path + else { + CallResult> propRes = + JSObject::getComputed_RJS(O, runtime, kHandle); + if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + fromValueHandle = propRes->getHermesValue(); + } + } + + // 9d. Perform ! CreateDataPropertyOrThrow(A, Pk, fromValue). + if (LLVM_UNLIKELY( + JSObject::defineOwnComputedPrimitive( + A, + runtime, + kHandle, + DefinePropertyFlags::getDefaultNewPropertyFlags(), + fromValueHandle, + PropOpFlags().plusThrowOnError()) == + ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + + // 9e. Set k to k + 1. + ++k; + } + + return A.getHermesValue(); +} + CallResult arrayOf(void *, Runtime &runtime, NativeArgs args) { GCScope gcScope{runtime}; diff --git a/test/hermes/array-functions.js b/test/hermes/array-functions.js index a8dd31bcaf3..20c6180f722 100644 --- a/test/hermes/array-functions.js +++ b/test/hermes/array-functions.js @@ -1114,3 +1114,34 @@ print(Array.prototype.at.call({length: 3, 0: 'a', 1: 'b', 2: 'c'}, -1)); // CHECK-NEXT: c print(Array.prototype.at.call({length: 30}, 5)); // CHECK-NEXT: undefined + +print('with'); +// CHECK-LABEL: with +print(Array.prototype.with.length); +// CHECK-NEXT: 2 +var a = [1,2,3,4]; +print(a.with(0, 0).toString()) +// CHECK-NEXT: 0,2,3,4 +print(a.toString()) +// CHECK-NEXT: 1,2,3,4 +print(arrayEquals([ 1, 2, 3 ].with(0, 0), [ 0, 2, 3 ])); +// CHECK-NEXT: true +print(arrayEquals([ 1, 2, 3 ].with(1, 0), [ 1, 0, 3 ])); +// CHECK-NEXT: true +print(arrayEquals([ 1, 2, 3 ].with(-1, 30), [ 1, 2, 30 ])); +// CHECK-NEXT: true +print(Array.prototype.with.call({length : 3, 0 : 'a', 1 : 'b', 2 : 'c'}, 1, 'B') + .toString()) +// CHECK-NEXT: a,B,c +try { + [].with(1, 1); +} catch (e) { + print(e.name) +} +// CHECK-NEXT: RangeError +try { + [].with(-2, 1); +} catch (e) { + print(e.name) +} +// CHECK-NEXT: RangeError \ No newline at end of file From 3295fc4d608a91175b39b9d58e0e3b8420d22a01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Fri, 26 Jan 2024 22:19:28 +0100 Subject: [PATCH 2/3] Implement ES2023 Array.prototype.toReversed (https://262.ecma-international.org/14.0/#sec-array.prototype.toreversed) --- include/hermes/VM/NativeFunctions.def | 1 + include/hermes/VM/PredefinedStrings.def | 1 + lib/VM/JSLib/Array.cpp | 92 +++++++++++++++++++++++++ test/hermes/array-functions.js | 15 ++++ 4 files changed, 109 insertions(+) diff --git a/include/hermes/VM/NativeFunctions.def b/include/hermes/VM/NativeFunctions.def index 5cbb1a7a518..b5a297f184b 100644 --- a/include/hermes/VM/NativeFunctions.def +++ b/include/hermes/VM/NativeFunctions.def @@ -57,6 +57,7 @@ NATIVE_FUNCTION(arrayPrototypeSlice) NATIVE_FUNCTION(arrayPrototypeSome) NATIVE_FUNCTION(arrayPrototypeUnshift) NATIVE_FUNCTION(arrayPrototypeSplice) +NATIVE_FUNCTION(arrayPrototypeToReversed) NATIVE_FUNCTION(arrayPrototypeWith) NATIVE_FUNCTION(asyncFunctionConstructor) NATIVE_FUNCTION(atob) diff --git a/include/hermes/VM/PredefinedStrings.def b/include/hermes/VM/PredefinedStrings.def index 59a91a591c9..d5ef58ed508 100644 --- a/include/hermes/VM/PredefinedStrings.def +++ b/include/hermes/VM/PredefinedStrings.def @@ -200,6 +200,7 @@ STR(includes, "includes") STR(subarray, "subarray") STR(flat, "flat") STR(flatMap, "flatMap") +STR(toReversed, "toReversed") STR(with, "with") STR(ArrayBuffer, "ArrayBuffer") diff --git a/lib/VM/JSLib/Array.cpp b/lib/VM/JSLib/Array.cpp index 71e57c93468..68ce04cd55b 100644 --- a/lib/VM/JSLib/Array.cpp +++ b/lib/VM/JSLib/Array.cpp @@ -128,6 +128,13 @@ Handle createArrayConstructor(Runtime &runtime) { (void *)IterationKind::Entry, arrayPrototypeIterator, 0); + defineMethod( + runtime, + arrayPrototype, + Predefined::getSymbolID(Predefined::toReversed), + nullptr, + arrayPrototypeToReversed, + 0); defineMethod( runtime, arrayPrototype, @@ -3529,6 +3536,91 @@ arrayPrototypeIncludes(void *, Runtime &runtime, NativeArgs args) { return HermesValue::encodeBoolValue(false); } +/// ES14.0 23.1.3.33 +CallResult +arrayPrototypeToReversed(void *, Runtime &runtime, NativeArgs args) { + GCScope gcScope{runtime}; + + // 1. Let O be ? ToObject(this value). + auto oRes = toObject(runtime, args.getThisHandle()); + if (LLVM_UNLIKELY(oRes == ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + auto O = runtime.makeHandle(*oRes); + + // 2. Let len be ? LengthOfArrayLike(O). + Handle jsArr = Handle::dyn_vmcast(O); + auto lenRes = lengthOfArrayLike(runtime, O, jsArr); + if (LLVM_UNLIKELY(lenRes == ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + auto len = lenRes.getValue(); + + // 3. Let A be ArrayCreate(len). + auto ARes = JSArray::create(runtime, len, len); + if (LLVM_UNLIKELY(ARes == ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + auto A = ARes.getValue(); + + // 4. Let k be 0. + double k = 0; + + MutableHandle<> kHandle{runtime}; + MutableHandle<> fromHandle{runtime}; + MutableHandle<> fromValueHandle{runtime}; + + auto marker = gcScope.createMarker(); + // 5. Repeat, while k < len, + while (k < len) { + gcScope.flushToMarker(marker); + + double from = len - k - 1; + // 5a. Let from be ! ToString(𝔽(len - k - 1)). + fromHandle = HermesValue::encodeUntrustedNumberValue(from); + + // 5b. Let Pk be ! ToString(𝔽(k)). + kHandle = HermesValue::encodeTrustedNumberValue(k); + + // 5c. Let fromValue be ? Get(O, from). + if (LLVM_LIKELY(jsArr)) { + const SmallHermesValue elm = jsArr->at(runtime, from); + // If the element is not empty, we can return it directly here. + // Otherwise, we must proceed to the slow path. + if (!elm.isEmpty()) { + fromValueHandle = elm.unboxToHV(runtime); + } + } + // Slow path + else { + CallResult> propRes = + JSObject::getComputed_RJS(O, runtime, fromHandle); + if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + fromValueHandle = propRes->getHermesValue(); + } + + // 5d. Perform ! CreateDataPropertyOrThrow(A, Pk, fromValue). + if (LLVM_UNLIKELY( + JSObject::defineOwnComputedPrimitive( + A, + runtime, + kHandle, + DefinePropertyFlags::getDefaultNewPropertyFlags(), + fromValueHandle, + PropOpFlags().plusThrowOnError()) == + ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + + // 5e. Set k to k + 1. + ++k; + } + + return A.getHermesValue(); +} + /// ES14.0 23.1.3.39 CallResult arrayPrototypeWith(void *, Runtime &runtime, NativeArgs args) { diff --git a/test/hermes/array-functions.js b/test/hermes/array-functions.js index 20c6180f722..157522ae782 100644 --- a/test/hermes/array-functions.js +++ b/test/hermes/array-functions.js @@ -1115,6 +1115,21 @@ print(Array.prototype.at.call({length: 3, 0: 'a', 1: 'b', 2: 'c'}, -1)); print(Array.prototype.at.call({length: 30}, 5)); // CHECK-NEXT: undefined +print('toReversed'); +// CHECK-LABEL: toReversed +print(Array.prototype.toReversed.length); +// CHECK-NEXT: 0 +var a = [1,2,3,4]; +print(a.toReversed().toString()) +// CHECK-NEXT: 4,3,2,1 +print(a.toString()) +// CHECK-NEXT: 1,2,3,4 +print(arrayEquals([ 1, 2, 3 ].toReversed(), [ 3, 2, 1 ])); +// CHECK-NEXT: true +print(Array.prototype.toReversed.call({length : 3, 0 : 'a', 1 : 'b', 2 : 'c'}) + .toString()) +// CHECK-NEXT: c,b,a + print('with'); // CHECK-LABEL: with print(Array.prototype.with.length); From fbc68c3a91ef68ecbd728c3027235afa0c2c1bdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Pasi=C5=84ski?= Date: Sun, 28 Jan 2024 16:51:58 +0100 Subject: [PATCH 3/3] Implement ES2023 Array.prototype.toSpliced (https://262.ecma-international.org/14.0/#sec-array.prototype.tospliced) --- include/hermes/VM/NativeFunctions.def | 1 + include/hermes/VM/PredefinedStrings.def | 1 + lib/VM/JSLib/Array.cpp | 210 ++++++++++++++++++++++++ test/hermes/array-functions.js | 33 ++++ 4 files changed, 245 insertions(+) diff --git a/include/hermes/VM/NativeFunctions.def b/include/hermes/VM/NativeFunctions.def index b5a297f184b..514c7ebc893 100644 --- a/include/hermes/VM/NativeFunctions.def +++ b/include/hermes/VM/NativeFunctions.def @@ -58,6 +58,7 @@ NATIVE_FUNCTION(arrayPrototypeSome) NATIVE_FUNCTION(arrayPrototypeUnshift) NATIVE_FUNCTION(arrayPrototypeSplice) NATIVE_FUNCTION(arrayPrototypeToReversed) +NATIVE_FUNCTION(arrayPrototypeToSpliced) NATIVE_FUNCTION(arrayPrototypeWith) NATIVE_FUNCTION(asyncFunctionConstructor) NATIVE_FUNCTION(atob) diff --git a/include/hermes/VM/PredefinedStrings.def b/include/hermes/VM/PredefinedStrings.def index d5ef58ed508..9edb525cce9 100644 --- a/include/hermes/VM/PredefinedStrings.def +++ b/include/hermes/VM/PredefinedStrings.def @@ -201,6 +201,7 @@ STR(subarray, "subarray") STR(flat, "flat") STR(flatMap, "flatMap") STR(toReversed, "toReversed") +STR(toSpliced, "toSpliced") STR(with, "with") STR(ArrayBuffer, "ArrayBuffer") diff --git a/lib/VM/JSLib/Array.cpp b/lib/VM/JSLib/Array.cpp index 68ce04cd55b..df9af0dcd8d 100644 --- a/lib/VM/JSLib/Array.cpp +++ b/lib/VM/JSLib/Array.cpp @@ -135,6 +135,13 @@ Handle createArrayConstructor(Runtime &runtime) { nullptr, arrayPrototypeToReversed, 0); + defineMethod( + runtime, + arrayPrototype, + Predefined::getSymbolID(Predefined::toSpliced), + nullptr, + arrayPrototypeToSpliced, + 2); defineMethod( runtime, arrayPrototype, @@ -3621,6 +3628,209 @@ arrayPrototypeToReversed(void *, Runtime &runtime, NativeArgs args) { return A.getHermesValue(); } +/// Copies \p count elements from \p from (or \p fromArr if is simple array) +static inline CallResult arrayCopyHelper( + Runtime &runtime, + GCScope &gcScope, + Handle from, + Handle fromArr, + uint32_t fromStartIndex, + Handle to, + uint32_t toStartIndex, + uint32_t count) { + MutableHandle<> fromIndexHandle{runtime}; + MutableHandle<> toIndexHandle{runtime}; + MutableHandle<> fromValueHandle{runtime}; + + auto marker = gcScope.createMarker(); + double i = 0; + + while (i < count) { + gcScope.flushToMarker(marker); + auto fromIndex = fromStartIndex + i; + + if (LLVM_LIKELY(fromArr)) { + const SmallHermesValue elem = fromArr->at(runtime, fromIndex); + if (!elem.isEmpty()) { + fromValueHandle = elem.unboxToHV(runtime); + } + } + // Slow path + else { + fromIndexHandle = HermesValue::encodeTrustedNumberValue(fromIndex); + + CallResult> propRes = + JSObject::getComputed_RJS(from, runtime, fromIndexHandle); + if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + fromValueHandle = propRes->getHermesValue(); + } + + toIndexHandle = HermesValue::encodeTrustedNumberValue(toStartIndex + i); + + if (LLVM_UNLIKELY( + JSObject::defineOwnComputedPrimitive( + to, + runtime, + toIndexHandle, + DefinePropertyFlags::getDefaultNewPropertyFlags(), + fromValueHandle, + PropOpFlags().plusThrowOnError()) == + ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + + ++i; + } + + return i; +} + +/// ES14.0 23.1.3.35 +CallResult +arrayPrototypeToSpliced(void *, Runtime &runtime, NativeArgs args) { + GCScope gcScope{runtime}; + + // 1. Let O be ? ToObject(this value). + auto oRes = toObject(runtime, args.getThisHandle()); + if (LLVM_UNLIKELY(oRes == ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + auto O = runtime.makeHandle(*oRes); + + // 2. Let len be ? LengthOfArrayLike(O). + Handle jsArr = Handle::dyn_vmcast(O); + auto lenRes = lengthOfArrayLike(runtime, O, jsArr); + if (LLVM_UNLIKELY(lenRes == ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + double len = lenRes.getValue(); + + // 3. Let relativeIndex be ? ToIntegerOrInfinity(index). + auto relativeStartRes = toIntegerOrInfinity(runtime, args.getArgHandle(0)); + if (LLVM_UNLIKELY(relativeStartRes == ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + // Use double here, because ToInteger may return Infinity. + double relativeStart = relativeStartRes->getNumber(); + + // 4. If relativeStart is -∞, let actualStart be 0. + // 5. Else if relativeStart < 0, let actualStart be max(len + relativeStart, + // 0). + // 6. Else, let actualStart be min(relativeStart, len). + double actualStart = relativeStart < 0 ? std::max(len + relativeStart, 0.0) + : std::min(relativeStart, len); + + uint32_t argCount = args.getArgCount(); + uint64_t actualSkipCount; + uint64_t insertCount; + + switch (argCount) { + // 8. If start is not present, then + case 0: + insertCount = 0; + // 8a. Let actualSkipCount be 0. + actualSkipCount = 0; + break; + // 9. Else if skipCount is not present, then + case 1: + insertCount = 0; + // 9a. Let actualSkipCount be len - actualStart. + actualSkipCount = len - actualStart; + break; + // 10. Else + default: { + // 10a. Let sc be ? ToIntegerOrInfinity(skipCount). + auto skipCountRes = toIntegerOrInfinity(runtime, args.getArgHandle(1)); + if (LLVM_UNLIKELY(skipCountRes == ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + + insertCount = argCount - 2; + + // 10b. Let actualSkipCount be the result of clamping dc between 0 and len + // - actualStart. + actualSkipCount = + std::min(std::max(skipCountRes->getNumber(), 0.0), len - actualStart); + } + } + + // 11. Let newLen be len + insertCount - actualSkipCount. + auto lenAfterInsert = len + insertCount - actualSkipCount; + + // 12. If newLen > 253 - 1, throw a TypeError exception. + if (LLVM_UNLIKELY( + // lenAfterInsert < len || + lenAfterInsert - actualSkipCount > (1LLU << 53) - 1)) { + return runtime.raiseTypeError( + "Array.prototype.toSpliced result out of space"); + } + + // 13. Let A be ArrayCreate(len). + auto ARes = JSArray::create(runtime, lenAfterInsert, lenAfterInsert); + if (LLVM_UNLIKELY(ARes == ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + auto A = ARes.getValue(); + + // 14. Let i be 0 + double i = 0; + MutableHandle<> insertIndexHandle{runtime}; + + // 15. Let r be actualStart + actualSkipCount. + uint64_t r = actualStart + actualSkipCount; + + uint64_t paramIndex = 2; + + Handle fromArr = Handle::dyn_vmcast(O); + + // 16a - 16d + // Copy elements from original array O from beginning until actualStart into + // new array A + auto copyRes = + arrayCopyHelper(runtime, gcScope, O, fromArr, 0, A, 0, actualStart); + if (LLVM_UNLIKELY(copyRes == ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + i += copyRes.getValue(); + + // 17. For each element E of items, do + while (paramIndex < argCount) { + // 17a. Let Pi be ! ToString(𝔽(i)). + insertIndexHandle = HermesValue::encodeTrustedNumberValue(i); + + // 17b. Perform ! CreateDataPropertyOrThrow(A, Pi, E). + if (LLVM_UNLIKELY( + JSObject::defineOwnComputedPrimitive( + A, + runtime, + insertIndexHandle, + DefinePropertyFlags::getDefaultNewPropertyFlags(), + args.getArgHandle(paramIndex), + PropOpFlags().plusThrowOnError()) == + ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + + // 17c. Set i to i + 1. + ++i; + ++paramIndex; + } + + // 18a - 18f + // Copy remaining elements from original array O including skipCount into new + // array A + copyRes = arrayCopyHelper( + runtime, gcScope, O, fromArr, r, A, i, lenAfterInsert - i); + if (LLVM_UNLIKELY(copyRes == ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + i += copyRes.getValue(); + + return A.getHermesValue(); +} + /// ES14.0 23.1.3.39 CallResult arrayPrototypeWith(void *, Runtime &runtime, NativeArgs args) { diff --git a/test/hermes/array-functions.js b/test/hermes/array-functions.js index 157522ae782..026c8975ad1 100644 --- a/test/hermes/array-functions.js +++ b/test/hermes/array-functions.js @@ -1130,6 +1130,39 @@ print(Array.prototype.toReversed.call({length : 3, 0 : 'a', 1 : 'b', 2 : 'c'}) .toString()) // CHECK-NEXT: c,b,a +print('toSpliced'); +// CHECK-LABEL: toSpliced +print(Array.prototype.toSpliced.length); +// CHECK-NEXT: 2 +var a = [ 1, 2, 3, 4 ]; +print(a.toSpliced(0, 3, 7, 6, 5).toString()) +// CHECK-NEXT: 7,6,5,4 +print(a.toString()) +// CHECK-NEXT: 1,2,3,4 +print(arrayEquals([ 1, 2, 3, 4 ].toSpliced(), [ 1, 2, 3, 4 ])); +// CHECK-NEXT: true +print(arrayEquals([ 1, 2, 3, 4 ].toSpliced(2), [ 1, 2 ])); +// CHECK-NEXT: true +print(arrayEquals([ 1, 2, 3, 4 ].toSpliced(2, 2), [ 1, 2 ])); +// CHECK-NEXT: true +print(arrayEquals([ 1, 2, 3, 4 ].toSpliced(2, 1), [ 1, 2, 4 ])); +// CHECK-NEXT: true +print(arrayEquals([ 1, 2, 3, 4 ].toSpliced(2, 1, -3), [ 1, 2, -3, 4 ])); +// CHECK-NEXT: true +print(arrayEquals( + [ 1, 2, 3, 4 ].toSpliced(2, 1, '3a', '3b', '3c'), + [ 1, 2, '3a', '3b', '3c', 4 ])); +// CHECK-NEXT: true +print(arrayEquals([ 1, 2, 3, 4 ].toSpliced(2, 110, 'end'), [ 1, 2, 'end' ])); +// CHECK-NEXT: true +print(arrayEquals( + [ 1, 2, 3, 4 ].toSpliced(2, -110, 'notend'), [ 1, 2, 'notend', 3, 4 ])); +// CHECK-NEXT: true +print(Array.prototype + .toSpliced.call({length : 3, 0 : 'a', 1 : 'b', 2 : 'c'}, 0, 0, 0) + .toString()) +// CHECK-NEXT: 0,a,b,c + print('with'); // CHECK-LABEL: with print(Array.prototype.with.length);