From e60715764eb4f99bc763beba8cca4ce16e0984ef Mon Sep 17 00:00:00 2001 From: Neil Dhar Date: Fri, 23 Feb 2024 11:17:18 -0800 Subject: [PATCH] Add support for ES2023 `Array` non-mutating methods (#1286) (#1286) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Original Author: robert.pasinski@outlook.com Original Git: b30f0e453d82594654810730211e092415aa6a3c Original Reviewed By: avp Original Revision: D53206784 ### Overview This PR implements partial support for new `Array.prototype` non-mutating methods introduced in ES2023. These are: - [`Array.prototype.toReversed()`](https://262.ecma-international.org/14.0/#sec-array.prototype.toreversed) - [`Array.prototype.toSpliced(start, skipCount, ...items)`](https://262.ecma-international.org/14.0/#sec-array.prototype.tospliced) - [`Array.prototype.with(index, value)`](https://262.ecma-international.org/14.0/#sec-array.prototype.with) `Array.prototype.toSorted()` is implemented in separate PR https://github.com/facebook/hermes/issues/1298 as per request. Other Array methods introduced in ES14 seem to already be implemented (`findLast`, `findLastIndex`). Implementation for `TypedArray` methods are not included in this PR and will be provided in another one. ### Motivation Aforementioned methods see support in all major browsers for quite some time [^toReversed][^toSorted][^toSpliced][^with]. Adding support brings hermes closer to full ES2023 coverage and will improve interoperability with browsers. It also should provide slight performance gains across all applications upon adoption. obraz [^toReversed]: https://caniuse.com/?search=toReversed [^toSorted]: https://caniuse.com/?search=toSorted [^toSpliced]: https://caniuse.com/?search=toSpliced [^with]: https://caniuse.com/?search=array.with - [x] Implementation - [x] Add tests - [x] Format changes - [ ] Add documentation somewhere? ### Implementation/review notes This is my first contribution to this project. While I have some experience in C++ and have spent some time studying hermes codebase, there might be some slight misusages of your APIs (most notably handles and memory management) and breaking code-style. If so, please let me know and I will try fix them promptly 🙂 Most of the code was inspired/taken from already existing mutating functions. I also tried to apply optimisations that I've noticed in some of the methods (most notably fast path for array element accessing). Pull Request resolved: https://github.com/facebook/hermes/pull/1286 Pulled By: tmikov Reviewed By: avp Differential Revision: D54092069 fbshipit-source-id: cc4b58cf9d832538ff22df954e6655075a709aa3 --- include/hermes/VM/NativeFunctions.def | 3 + include/hermes/VM/PredefinedStrings.def | 3 + lib/VM/JSLib/Array.cpp | 462 +++++++++++++++++++++++- test/hermes/array-functions.js | 79 ++++ 4 files changed, 531 insertions(+), 16 deletions(-) diff --git a/include/hermes/VM/NativeFunctions.def b/include/hermes/VM/NativeFunctions.def index bd04037148d..f3108a8f736 100644 --- a/include/hermes/VM/NativeFunctions.def +++ b/include/hermes/VM/NativeFunctions.def @@ -57,6 +57,9 @@ NATIVE_FUNCTION(arrayPrototypeSlice) 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 d299f73de28..39b8b4f2127 100644 --- a/include/hermes/VM/PredefinedStrings.def +++ b/include/hermes/VM/PredefinedStrings.def @@ -200,6 +200,9 @@ STR(includes, "includes") STR(subarray, "subarray") STR(flat, "flat") STR(flatMap, "flatMap") +STR(toReversed, "toReversed") +STR(toSpliced, "toSpliced") +STR(with, "with") STR(ArrayBuffer, "ArrayBuffer") STR(byteLength, "byteLength") diff --git a/lib/VM/JSLib/Array.cpp b/lib/VM/JSLib/Array.cpp index 21e3f50849a..be3af3f07c5 100644 --- a/lib/VM/JSLib/Array.cpp +++ b/lib/VM/JSLib/Array.cpp @@ -123,6 +123,27 @@ Handle createArrayConstructor(Runtime &runtime) { (void *)IterationKind::Entry, arrayPrototypeIterator, 0); + defineMethod( + runtime, + arrayPrototype, + Predefined::getSymbolID(Predefined::toReversed), + nullptr, + arrayPrototypeToReversed, + 0); + defineMethod( + runtime, + arrayPrototype, + Predefined::getSymbolID(Predefined::toSpliced), + nullptr, + arrayPrototypeToSpliced, + 2); + defineMethod( + runtime, + arrayPrototype, + Predefined::getSymbolID(Predefined::with), + nullptr, + arrayPrototypeWith, + 2); auto propValue = runtime.ignoreAllocationFailure(JSObject::getNamed_RJS( arrayPrototype, runtime, Predefined::getSymbolID(Predefined::values))); @@ -541,6 +562,26 @@ arrayPrototypeToLocaleString(void *, Runtime &runtime, NativeArgs args) { return HermesValue::encodeStringValue(*builder->getStringPrimitive()); } +static inline CallResult +lengthOfArrayLike(Runtime &runtime, Handle O, 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) { @@ -554,23 +595,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); @@ -3504,6 +3533,407 @@ 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(); +} + +/// 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) { + 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 actualIndex = 0; + // 4. If relativeIndex ≥ 0, let actualIndex be relativeIndex. + if (relativeIndex >= 0) { + actualIndex = relativeIndex; + } + // 5. Else, let actualIndex be len + relativeIndex. + else { + actualIndex = len + relativeIndex; + } + + // 6. If actualIndex ≥ len or actualIndex < 0, throw a RangeError exception. + if (actualIndex < 0 || actualIndex >= 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 == actualIndex) { + 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 e77d3a73783..96303f5d2e6 100644 --- a/test/hermes/array-functions.js +++ b/test/hermes/array-functions.js @@ -1120,3 +1120,82 @@ 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('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('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); +// 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