Skip to content

Commit

Permalink
Support at methods for Array, TypedArray, and String
Browse files Browse the repository at this point in the history
Summary: Solve the gap raised in #823. Add support for `.prototype.add` for Array, TypedArray, and String. The implementation closely follows the spec.

Reviewed By: jpporto

Differential Revision: D40497162

fbshipit-source-id: 0b317fda2d66ac566c10dee19626c728c4c8cc41
  • Loading branch information
Michael Anthony Leon authored and facebook-github-bot committed Oct 24, 2022
1 parent 0438399 commit ebe2915
Show file tree
Hide file tree
Showing 9 changed files with 260 additions and 4 deletions.
3 changes: 3 additions & 0 deletions include/hermes/VM/NativeFunctions.def
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions include/hermes/VM/PredefinedStrings.def
Expand Up @@ -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")
Expand Down
79 changes: 79 additions & 0 deletions lib/VM/JSLib/Array.cpp
Expand Up @@ -50,6 +50,13 @@ Handle<JSObject> createArrayConstructor(Runtime &runtime) {
nullptr,
arrayPrototypeToLocaleString,
0);
defineMethod(
runtime,
arrayPrototype,
Predefined::getSymbolID(Predefined::at),
nullptr,
arrayPrototypeAt,
1);
defineMethod(
runtime,
arrayPrototype,
Expand Down Expand Up @@ -539,6 +546,78 @@ arrayPrototypeToLocaleString(void *, Runtime &runtime, NativeArgs args) {
return HermesValue::encodeStringValue(*builder->getStringPrimitive());
}

// 23.1.3.1
CallResult<HermesValue>
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<JSObject>(objRes.getValue());

// 2. Let len be ? LengthOfArrayLike(O).
Handle<JSArray> jsArr = Handle<JSArray>::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<PseudoHandle<>> 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<PseudoHandle<>> propRes = JSObject::getComputed_RJS(
O, runtime, runtime.makeHandle(HermesValue::encodeDoubleValue(k)));
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
return propRes->getHermesValue();
}

CallResult<HermesValue>
arrayPrototypeConcat(void *, Runtime &runtime, NativeArgs args) {
GCScope gcScope(runtime);
Expand Down
60 changes: 60 additions & 0 deletions lib/VM/JSLib/String.cpp
Expand Up @@ -60,6 +60,13 @@ Handle<JSObject> createStringConstructor(Runtime &runtime) {
ctx,
stringPrototypeToString,
0);
defineMethod(
runtime,
stringPrototype,
Predefined::getSymbolID(Predefined::at),
ctx,
stringPrototypeAt,
1);
defineMethod(
runtime,
stringPrototype,
Expand Down Expand Up @@ -569,6 +576,59 @@ CallResult<HermesValue> stringRaw(void *, Runtime &runtime, NativeArgs args) {
//===----------------------------------------------------------------------===//
/// String.prototype.

/// 22.1.3.1
CallResult<HermesValue>
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<HermesValue>
stringPrototypeToString(void *, Runtime &runtime, NativeArgs args) {
if (args.getThisArg().isString()) {
Expand Down
69 changes: 69 additions & 0 deletions lib/VM/JSLib/TypedArray.cpp
Expand Up @@ -764,6 +764,68 @@ typedArrayPrototypeByteOffset(void *, Runtime &runtime, NativeArgs args) {
: 0);
}

/// ES6 23.2.3.1
CallResult<HermesValue>
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<JSTypedArrayBase>();

// 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<JSTypedArray<type, CellKind::name##ArrayKind>>(*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<HermesValue>
typedArrayPrototypeCopyWithin(void *, Runtime &runtime, NativeArgs args) {
Expand Down Expand Up @@ -1757,6 +1819,13 @@ Handle<JSObject> createTypedArrayBaseConstructor(Runtime &runtime) {
false,
true);
// Methods.
defineMethod(
runtime,
proto,
Predefined::getSymbolID(Predefined::at),
nullptr,
typedArrayPrototypeAt,
1);
defineMethod(
runtime,
proto,
Expand Down
13 changes: 13 additions & 0 deletions test/hermes/TypedArray.js
Expand Up @@ -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
/// @{

Expand Down
21 changes: 21 additions & 0 deletions test/hermes/array-functions.js
Expand Up @@ -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
14 changes: 14 additions & 0 deletions test/hermes/string-functions.js
Expand Up @@ -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'))
Expand Down
4 changes: 0 additions & 4 deletions utils/testsuite/testsuite_skiplist.py
Expand Up @@ -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",
Expand Down

0 comments on commit ebe2915

Please sign in to comment.