diff --git a/include/hermes/VM/NativeFunctions.def b/include/hermes/VM/NativeFunctions.def index 8a9643cb5a3..dfa4a45a193 100644 --- a/include/hermes/VM/NativeFunctions.def +++ b/include/hermes/VM/NativeFunctions.def @@ -27,6 +27,7 @@ NATIVE_FUNCTION(arrayIsArray) NATIVE_FUNCTION(arrayFrom) NATIVE_FUNCTION(arrayOf) NATIVE_FUNCTION(arrayIteratorPrototypeNext) +NATIVE_FUNCTION(arrayPrototypeAt) NATIVE_FUNCTION(arrayPrototypeConcat) NATIVE_FUNCTION(arrayPrototypeForEach) NATIVE_FUNCTION(arrayPrototypeIterator) @@ -315,6 +316,7 @@ NATIVE_FUNCTION(stringRaw) NATIVE_FUNCTION(stringFromCharCode) NATIVE_FUNCTION(stringFromCodePoint) NATIVE_FUNCTION(stringIteratorPrototypeNext) +NATIVE_FUNCTION(stringPrototypeAt) NATIVE_FUNCTION(stringPrototypeCharCodeAt) NATIVE_FUNCTION(stringPrototypeCodePointAt) NATIVE_FUNCTION(stringPrototypeConcat) @@ -356,6 +358,7 @@ NATIVE_FUNCTION(throwTypeError) NATIVE_FUNCTION(typedArrayBaseConstructor) NATIVE_FUNCTION(typedArrayFrom) NATIVE_FUNCTION(typedArrayOf) +NATIVE_FUNCTION(typedArrayPrototypeAt) NATIVE_FUNCTION(typedArrayPrototypeBuffer) NATIVE_FUNCTION(typedArrayPrototypeByteLength) NATIVE_FUNCTION(typedArrayPrototypeByteOffset) diff --git a/include/hermes/VM/PredefinedStrings.def b/include/hermes/VM/PredefinedStrings.def index 67212120edb..20edd5c8433 100644 --- a/include/hermes/VM/PredefinedStrings.def +++ b/include/hermes/VM/PredefinedStrings.def @@ -173,6 +173,7 @@ STR(isToplevel, "isToplevel") STR(Array, "Array") STR(ArrayIterator, "Array Iterator") STR(isArray, "isArray") +STR(at, "at") STR(join, "join") STR(push, "push") STR(pop, "pop") diff --git a/lib/VM/JSLib/Array.cpp b/lib/VM/JSLib/Array.cpp index d9acbff53b2..36c6bd5e1c2 100644 --- a/lib/VM/JSLib/Array.cpp +++ b/lib/VM/JSLib/Array.cpp @@ -45,6 +45,13 @@ Handle createArrayConstructor(Runtime &runtime) { nullptr, arrayPrototypeToLocaleString, 0); + defineMethod( + runtime, + arrayPrototype, + Predefined::getSymbolID(Predefined::at), + nullptr, + arrayPrototypeAt, + 1); defineMethod( runtime, arrayPrototype, @@ -534,6 +541,78 @@ arrayPrototypeToLocaleString(void *, Runtime &runtime, NativeArgs args) { return HermesValue::encodeStringValue(*builder->getStringPrimitive()); } +// 23.1.3.1 +CallResult +arrayPrototypeAt(void *, Runtime &runtime, NativeArgs args) { + GCScope gcScope(runtime); + // 1. Let O be ? ToObject(this value). + auto objRes = toObject(runtime, args.getThisHandle()); + if (LLVM_UNLIKELY(objRes == ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + auto O = runtime.makeHandle(objRes.getValue()); + + // 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(); + } + + // 3. Let relativeIndex be ? ToIntegerOrInfinity(index). + auto idx = args.getArgHandle(0); + auto relativeIndexRes = toIntegerOrInfinity(runtime, idx); + if (relativeIndexRes == ExecutionStatus::EXCEPTION) { + return ExecutionStatus::EXCEPTION; + } + const double relativeIndex = relativeIndexRes->getNumber(); + + double k; + // 4. If relativeIndex ≥ 0, then + if (relativeIndex >= 0) { + // a. Let k be relativeIndex. + k = relativeIndex; + } else { + // 5. Else, + // a. Let k be len + relativeIndex. + k = len + relativeIndex; + } + + // 6. If k < 0 or k ≥ len, return undefined. + if (k < 0 || k >= len) { + return HermesValue::encodeUndefinedValue(); + } + + // 7. Return ? Get(O, ! ToString(𝔽(k))). + if (LLVM_LIKELY(jsArr)) { + const SmallHermesValue elm = jsArr->at(runtime, k); + if (elm.isEmpty()) { + return HermesValue::encodeUndefinedValue(); + } else { + return elm.unboxToHV(runtime); + } + } + CallResult> propRes = JSObject::getComputed_RJS( + O, runtime, runtime.makeHandle(HermesValue::encodeDoubleValue(k))); + if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + return propRes->getHermesValue(); +} + CallResult arrayPrototypeConcat(void *, Runtime &runtime, NativeArgs args) { GCScope gcScope(runtime); diff --git a/lib/VM/JSLib/String.cpp b/lib/VM/JSLib/String.cpp index fadda963bf3..461ea45e289 100644 --- a/lib/VM/JSLib/String.cpp +++ b/lib/VM/JSLib/String.cpp @@ -55,6 +55,13 @@ Handle createStringConstructor(Runtime &runtime) { ctx, stringPrototypeToString, 0); + defineMethod( + runtime, + stringPrototype, + Predefined::getSymbolID(Predefined::at), + ctx, + stringPrototypeAt, + 1); defineMethod( runtime, stringPrototype, @@ -564,6 +571,59 @@ CallResult stringRaw(void *, Runtime &runtime, NativeArgs args) { //===----------------------------------------------------------------------===// /// String.prototype. +/// 22.1.3.1 +CallResult +stringPrototypeAt(void *, Runtime &runtime, NativeArgs args) { + GCScope gcScope(runtime); + // 1. Let O be RequireObjectCoercible(this value). + if (LLVM_UNLIKELY( + checkObjectCoercible(runtime, args.getThisHandle()) == + ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + + // 2. Let S be ToString(O). + auto strRes = toString_RJS(runtime, args.getThisHandle()); + if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + auto S = runtime.makeHandle(std::move(*strRes)); + + // 3. Let len be the length of S. + double len = S->getStringLength(); + + // 4. Let relativeIndex be ? ToIntegerOrInfinity(index). + auto idx = args.getArgHandle(0); + auto relativeIndexRes = toIntegerOrInfinity(runtime, idx); + if (LLVM_UNLIKELY(relativeIndexRes == ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + const double relativeIndex = relativeIndexRes->getNumber(); + + double k; + // 5. If relativeIndex ≥ 0, then + if (relativeIndex >= 0) { + // a. Let k be relativeIndex. + k = relativeIndex; + } else { + // 6. Else, + // a. Let k be len + relativeIndex. + k = len + relativeIndex; + } + + // 6. If k < 0 or k ≥ len, return undefined. + if (k < 0 || k >= len) { + return HermesValue::encodeUndefinedValue(); + } + + // 8. Return the substring of S from k to k + 1. + auto sliceRes = StringPrimitive::slice(runtime, S, k, 1); + if (LLVM_UNLIKELY(sliceRes == ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + return sliceRes; +} + CallResult stringPrototypeToString(void *, Runtime &runtime, NativeArgs args) { if (args.getThisArg().isString()) { diff --git a/lib/VM/JSLib/TypedArray.cpp b/lib/VM/JSLib/TypedArray.cpp index 4b09690525a..5a3d8ee4cf9 100644 --- a/lib/VM/JSLib/TypedArray.cpp +++ b/lib/VM/JSLib/TypedArray.cpp @@ -759,6 +759,68 @@ typedArrayPrototypeByteOffset(void *, Runtime &runtime, NativeArgs args) { : 0); } +/// ES6 23.2.3.1 +CallResult +typedArrayPrototypeAt(void *, Runtime &runtime, NativeArgs args) { + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + if (LLVM_UNLIKELY( + JSTypedArrayBase::validateTypedArray( + runtime, args.getThisHandle(), true) == + ExecutionStatus::EXCEPTION)) { + return ExecutionStatus::EXCEPTION; + } + GCScope gcScope{runtime}; + + auto O = args.vmcastThis(); + + // 3. Let len be O.[[ArrayLength]]. + // The this object’s [[ArrayLength]] internal slot is accessed in place of + // performing a [[Get]] of "length". + double len = O->getLength(); + + // 4. Let relativeIndex be ? ToIntegerOrInfinity(index). + auto idx = args.getArgHandle(0); + auto relativeIndexRes = toIntegerOrInfinity(runtime, idx); + if (relativeIndexRes == ExecutionStatus::EXCEPTION) { + return ExecutionStatus::EXCEPTION; + } + const double relativeIndex = relativeIndexRes->getNumber(); + + double k; + // 5. If relativeIndex ≥ 0, then + if (relativeIndex >= 0) { + // a. Let k be relativeIndex. + k = relativeIndex; + } else { + // 6. Else, + // a. Let k be len + relativeIndex. + k = len + relativeIndex; + } + + // 7. If k < 0 or k ≥ len, return undefined. + if (k < 0 || k >= len) { + return HermesValue::encodeUndefinedValue(); + } + + // 8. Return ? Get(O, ! ToString(𝔽(k))). + // Since we know we have a TypedArray, we can directly call JSTypedArray::at + // rather than getComputed_RJS like the spec mandates. +#define TYPED_ARRAY(name, type) \ + case CellKind::name##ArrayKind: { \ + auto *arr = vmcast>(*O); \ + if (!arr->attached(runtime)) { \ + return runtime.raiseTypeError("Underlying ArrayBuffer detached"); \ + } \ + return HermesValue::encodeNumberValue(arr->at(runtime, k)); \ + } + switch (O->getKind()) { +#include "hermes/VM/TypedArrays.def" + default: + llvm_unreachable("Invalid TypedArray after ValidateTypedArray call"); + } +} + /// ES6 22.2.3.5 CallResult typedArrayPrototypeCopyWithin(void *, Runtime &runtime, NativeArgs args) { @@ -1752,6 +1814,13 @@ Handle createTypedArrayBaseConstructor(Runtime &runtime) { false, true); // Methods. + defineMethod( + runtime, + proto, + Predefined::getSymbolID(Predefined::at), + nullptr, + typedArrayPrototypeAt, + 1); defineMethod( runtime, proto, diff --git a/test/hermes/TypedArray.js b/test/hermes/TypedArray.js index 3a38d6d69a8..4955e420cad 100644 --- a/test/hermes/TypedArray.js +++ b/test/hermes/TypedArray.js @@ -443,6 +443,19 @@ cons.forEach(function(TypedArray) { }); /// @} +/// @name %TypedArray%.prototype.at +/// @{ +cons.forEach(function(TA) { + var ta = new TA([1, 2, 3, 4, 5]); + assert.equal(ta.at(0).toString(), '1'); + assert.equal(ta.at(-1).toString(), '5'); + assert.equal(ta.at(10), undefined); + assert.throws(function(){TA.prototype.at.call(ta, 1n)}, TypeError); + assert.throws(function(){TA.prototype.at.call("hi", 1)}, TypeError); + assert.throws(function(){TA.prototype.at.call([1, "f", 3], 1)}, TypeError); +}); +// @} + /// @name %TypedArray%.prototype.copyWithin /// @{ diff --git a/test/hermes/array-functions.js b/test/hermes/array-functions.js index 973a482b07b..0fb56f5e753 100644 --- a/test/hermes/array-functions.js +++ b/test/hermes/array-functions.js @@ -1099,3 +1099,24 @@ print(arrayEquals([1,2,3].flatMap(function(x) { return [x, x+this]; }, 100), [1,101,2,102,3,103])); // CHECK-NEXT: true + +print([1, 2, 3, 4, 5].at(1)); +// CHECK-NEXT: 2 +print(Array.prototype.at.call({length: 3, 0: 'a', 1: 'b', 2: 'c'}, 1)); +// CHECK-NEXT: b +print([1, 2, 3, 4, 5].at(6)); +// CHECK-NEXT: undefined +print(Array.prototype.at.call({length: 0, 0: 'a', 1: 'b', 2: 'c'}, 1)); +// CHECK-NEXT: undefined +try { [].at(1n); } catch(e) { print(e.name) } +// CHECK-NEXT: TypeError +print([1, 2, 3, 4, 5].at(-1)); +// CHECK-NEXT: 5 +print([1, 2, 3, 4, 5].at(-5)); +// CHECK-NEXT: 1 +print([1, 2, 3, 4, 5].at(-6)); +// CHECK-NEXT: undefined +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 diff --git a/test/hermes/string-functions.js b/test/hermes/string-functions.js index 4dffa96934d..1fc88b62e0d 100644 --- a/test/hermes/string-functions.js +++ b/test/hermes/string-functions.js @@ -492,6 +492,20 @@ print('empty', res, res.length); print(String.prototype.trimEnd === String.prototype.trimRight); // CHECK-NEXT: true + +print('at'); +// CHECK-NEXT: at +print("abc".at(1)); +// CHECK-NEXT: b +print("abc".at(-1)); +// CHECK-NEXT: c +print("abc".at(false)); +// CHECK-NEXT: a +print(String.prototype.at.call(true, -1)); +// CHECK-NEXT: e +print("".at(0)); +// CHECK-NEXT: undefined + print('indexOf'); // CHECK-LABEL: indexOf print('abc'.indexOf('a')) diff --git a/utils/testsuite/testsuite_skiplist.py b/utils/testsuite/testsuite_skiplist.py index 2112e1afdb1..c9bfa48e716 100644 --- a/utils/testsuite/testsuite_skiplist.py +++ b/utils/testsuite/testsuite_skiplist.py @@ -432,10 +432,6 @@ "test262/test/built-ins/Array/prototype/splice/create-ctor-non-object.js", "test262/test/built-ins/Array/prototype/flat/non-object-ctor-throws.js", "test262/test/built-ins/Array/prototype/flatMap/this-value-ctor-non-object.js", - # TODO(T76109235) proposal-relative-indexing-method - "test262/test/built-ins/Array/prototype/at/", - "test262/test/built-ins/TypedArray/prototype/at/", - "test262/test/built-ins/String/prototype/at/", # TODO(T90541287) array length coercion order "test262/test/built-ins/Array/length/define-own-prop-length-coercion-order-set.js", "test262/test/built-ins/Array/length/define-own-prop-length-coercion-order.js",