Skip to content

Commit ebe2915

Browse files
fbmal7facebook-github-bot
authored andcommitted
Support at methods for Array, TypedArray, and String
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
1 parent 0438399 commit ebe2915

File tree

9 files changed

+260
-4
lines changed

9 files changed

+260
-4
lines changed

include/hermes/VM/NativeFunctions.def

+3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ NATIVE_FUNCTION(arrayIsArray)
2727
NATIVE_FUNCTION(arrayFrom)
2828
NATIVE_FUNCTION(arrayOf)
2929
NATIVE_FUNCTION(arrayIteratorPrototypeNext)
30+
NATIVE_FUNCTION(arrayPrototypeAt)
3031
NATIVE_FUNCTION(arrayPrototypeConcat)
3132
NATIVE_FUNCTION(arrayPrototypeForEach)
3233
NATIVE_FUNCTION(arrayPrototypeIterator)
@@ -315,6 +316,7 @@ NATIVE_FUNCTION(stringRaw)
315316
NATIVE_FUNCTION(stringFromCharCode)
316317
NATIVE_FUNCTION(stringFromCodePoint)
317318
NATIVE_FUNCTION(stringIteratorPrototypeNext)
319+
NATIVE_FUNCTION(stringPrototypeAt)
318320
NATIVE_FUNCTION(stringPrototypeCharCodeAt)
319321
NATIVE_FUNCTION(stringPrototypeCodePointAt)
320322
NATIVE_FUNCTION(stringPrototypeConcat)
@@ -356,6 +358,7 @@ NATIVE_FUNCTION(throwTypeError)
356358
NATIVE_FUNCTION(typedArrayBaseConstructor)
357359
NATIVE_FUNCTION(typedArrayFrom)
358360
NATIVE_FUNCTION(typedArrayOf)
361+
NATIVE_FUNCTION(typedArrayPrototypeAt)
359362
NATIVE_FUNCTION(typedArrayPrototypeBuffer)
360363
NATIVE_FUNCTION(typedArrayPrototypeByteLength)
361364
NATIVE_FUNCTION(typedArrayPrototypeByteOffset)

include/hermes/VM/PredefinedStrings.def

+1
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ STR(isToplevel, "isToplevel")
173173
STR(Array, "Array")
174174
STR(ArrayIterator, "Array Iterator")
175175
STR(isArray, "isArray")
176+
STR(at, "at")
176177
STR(join, "join")
177178
STR(push, "push")
178179
STR(pop, "pop")

lib/VM/JSLib/Array.cpp

+79
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ Handle<JSObject> createArrayConstructor(Runtime &runtime) {
5050
nullptr,
5151
arrayPrototypeToLocaleString,
5252
0);
53+
defineMethod(
54+
runtime,
55+
arrayPrototype,
56+
Predefined::getSymbolID(Predefined::at),
57+
nullptr,
58+
arrayPrototypeAt,
59+
1);
5360
defineMethod(
5461
runtime,
5562
arrayPrototype,
@@ -539,6 +546,78 @@ arrayPrototypeToLocaleString(void *, Runtime &runtime, NativeArgs args) {
539546
return HermesValue::encodeStringValue(*builder->getStringPrimitive());
540547
}
541548

549+
// 23.1.3.1
550+
CallResult<HermesValue>
551+
arrayPrototypeAt(void *, Runtime &runtime, NativeArgs args) {
552+
GCScope gcScope(runtime);
553+
// 1. Let O be ? ToObject(this value).
554+
auto objRes = toObject(runtime, args.getThisHandle());
555+
if (LLVM_UNLIKELY(objRes == ExecutionStatus::EXCEPTION)) {
556+
return ExecutionStatus::EXCEPTION;
557+
}
558+
auto O = runtime.makeHandle<JSObject>(objRes.getValue());
559+
560+
// 2. Let len be ? LengthOfArrayLike(O).
561+
Handle<JSArray> jsArr = Handle<JSArray>::dyn_vmcast(O);
562+
uint32_t len = 0;
563+
if (LLVM_LIKELY(jsArr)) {
564+
// Fast path for getting the length.
565+
len = JSArray::getLength(jsArr.get(), runtime);
566+
} else {
567+
// Slow path
568+
CallResult<PseudoHandle<>> propRes = JSObject::getNamed_RJS(
569+
O, runtime, Predefined::getSymbolID(Predefined::length));
570+
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
571+
return ExecutionStatus::EXCEPTION;
572+
}
573+
auto lenRes = toLength(runtime, runtime.makeHandle(std::move(*propRes)));
574+
if (LLVM_UNLIKELY(lenRes == ExecutionStatus::EXCEPTION)) {
575+
return ExecutionStatus::EXCEPTION;
576+
}
577+
len = lenRes->getNumber();
578+
}
579+
580+
// 3. Let relativeIndex be ? ToIntegerOrInfinity(index).
581+
auto idx = args.getArgHandle(0);
582+
auto relativeIndexRes = toIntegerOrInfinity(runtime, idx);
583+
if (relativeIndexRes == ExecutionStatus::EXCEPTION) {
584+
return ExecutionStatus::EXCEPTION;
585+
}
586+
const double relativeIndex = relativeIndexRes->getNumber();
587+
588+
double k;
589+
// 4. If relativeIndex ≥ 0, then
590+
if (relativeIndex >= 0) {
591+
// a. Let k be relativeIndex.
592+
k = relativeIndex;
593+
} else {
594+
// 5. Else,
595+
// a. Let k be len + relativeIndex.
596+
k = len + relativeIndex;
597+
}
598+
599+
// 6. If k < 0 or k ≥ len, return undefined.
600+
if (k < 0 || k >= len) {
601+
return HermesValue::encodeUndefinedValue();
602+
}
603+
604+
// 7. Return ? Get(O, ! ToString(𝔽(k))).
605+
if (LLVM_LIKELY(jsArr)) {
606+
const SmallHermesValue elm = jsArr->at(runtime, k);
607+
if (elm.isEmpty()) {
608+
return HermesValue::encodeUndefinedValue();
609+
} else {
610+
return elm.unboxToHV(runtime);
611+
}
612+
}
613+
CallResult<PseudoHandle<>> propRes = JSObject::getComputed_RJS(
614+
O, runtime, runtime.makeHandle(HermesValue::encodeDoubleValue(k)));
615+
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
616+
return ExecutionStatus::EXCEPTION;
617+
}
618+
return propRes->getHermesValue();
619+
}
620+
542621
CallResult<HermesValue>
543622
arrayPrototypeConcat(void *, Runtime &runtime, NativeArgs args) {
544623
GCScope gcScope(runtime);

lib/VM/JSLib/String.cpp

+60
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@ Handle<JSObject> createStringConstructor(Runtime &runtime) {
6060
ctx,
6161
stringPrototypeToString,
6262
0);
63+
defineMethod(
64+
runtime,
65+
stringPrototype,
66+
Predefined::getSymbolID(Predefined::at),
67+
ctx,
68+
stringPrototypeAt,
69+
1);
6370
defineMethod(
6471
runtime,
6572
stringPrototype,
@@ -569,6 +576,59 @@ CallResult<HermesValue> stringRaw(void *, Runtime &runtime, NativeArgs args) {
569576
//===----------------------------------------------------------------------===//
570577
/// String.prototype.
571578

579+
/// 22.1.3.1
580+
CallResult<HermesValue>
581+
stringPrototypeAt(void *, Runtime &runtime, NativeArgs args) {
582+
GCScope gcScope(runtime);
583+
// 1. Let O be RequireObjectCoercible(this value).
584+
if (LLVM_UNLIKELY(
585+
checkObjectCoercible(runtime, args.getThisHandle()) ==
586+
ExecutionStatus::EXCEPTION)) {
587+
return ExecutionStatus::EXCEPTION;
588+
}
589+
590+
// 2. Let S be ToString(O).
591+
auto strRes = toString_RJS(runtime, args.getThisHandle());
592+
if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) {
593+
return ExecutionStatus::EXCEPTION;
594+
}
595+
auto S = runtime.makeHandle(std::move(*strRes));
596+
597+
// 3. Let len be the length of S.
598+
double len = S->getStringLength();
599+
600+
// 4. Let relativeIndex be ? ToIntegerOrInfinity(index).
601+
auto idx = args.getArgHandle(0);
602+
auto relativeIndexRes = toIntegerOrInfinity(runtime, idx);
603+
if (LLVM_UNLIKELY(relativeIndexRes == ExecutionStatus::EXCEPTION)) {
604+
return ExecutionStatus::EXCEPTION;
605+
}
606+
const double relativeIndex = relativeIndexRes->getNumber();
607+
608+
double k;
609+
// 5. If relativeIndex ≥ 0, then
610+
if (relativeIndex >= 0) {
611+
// a. Let k be relativeIndex.
612+
k = relativeIndex;
613+
} else {
614+
// 6. Else,
615+
// a. Let k be len + relativeIndex.
616+
k = len + relativeIndex;
617+
}
618+
619+
// 6. If k < 0 or k ≥ len, return undefined.
620+
if (k < 0 || k >= len) {
621+
return HermesValue::encodeUndefinedValue();
622+
}
623+
624+
// 8. Return the substring of S from k to k + 1.
625+
auto sliceRes = StringPrimitive::slice(runtime, S, k, 1);
626+
if (LLVM_UNLIKELY(sliceRes == ExecutionStatus::EXCEPTION)) {
627+
return ExecutionStatus::EXCEPTION;
628+
}
629+
return sliceRes;
630+
}
631+
572632
CallResult<HermesValue>
573633
stringPrototypeToString(void *, Runtime &runtime, NativeArgs args) {
574634
if (args.getThisArg().isString()) {

lib/VM/JSLib/TypedArray.cpp

+69
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,68 @@ typedArrayPrototypeByteOffset(void *, Runtime &runtime, NativeArgs args) {
764764
: 0);
765765
}
766766

767+
/// ES6 23.2.3.1
768+
CallResult<HermesValue>
769+
typedArrayPrototypeAt(void *, Runtime &runtime, NativeArgs args) {
770+
// 1. Let O be the this value.
771+
// 2. Perform ? ValidateTypedArray(O).
772+
if (LLVM_UNLIKELY(
773+
JSTypedArrayBase::validateTypedArray(
774+
runtime, args.getThisHandle(), true) ==
775+
ExecutionStatus::EXCEPTION)) {
776+
return ExecutionStatus::EXCEPTION;
777+
}
778+
GCScope gcScope{runtime};
779+
780+
auto O = args.vmcastThis<JSTypedArrayBase>();
781+
782+
// 3. Let len be O.[[ArrayLength]].
783+
// The this object’s [[ArrayLength]] internal slot is accessed in place of
784+
// performing a [[Get]] of "length".
785+
double len = O->getLength();
786+
787+
// 4. Let relativeIndex be ? ToIntegerOrInfinity(index).
788+
auto idx = args.getArgHandle(0);
789+
auto relativeIndexRes = toIntegerOrInfinity(runtime, idx);
790+
if (relativeIndexRes == ExecutionStatus::EXCEPTION) {
791+
return ExecutionStatus::EXCEPTION;
792+
}
793+
const double relativeIndex = relativeIndexRes->getNumber();
794+
795+
double k;
796+
// 5. If relativeIndex ≥ 0, then
797+
if (relativeIndex >= 0) {
798+
// a. Let k be relativeIndex.
799+
k = relativeIndex;
800+
} else {
801+
// 6. Else,
802+
// a. Let k be len + relativeIndex.
803+
k = len + relativeIndex;
804+
}
805+
806+
// 7. If k < 0 or k ≥ len, return undefined.
807+
if (k < 0 || k >= len) {
808+
return HermesValue::encodeUndefinedValue();
809+
}
810+
811+
// 8. Return ? Get(O, ! ToString(𝔽(k))).
812+
// Since we know we have a TypedArray, we can directly call JSTypedArray::at
813+
// rather than getComputed_RJS like the spec mandates.
814+
#define TYPED_ARRAY(name, type) \
815+
case CellKind::name##ArrayKind: { \
816+
auto *arr = vmcast<JSTypedArray<type, CellKind::name##ArrayKind>>(*O); \
817+
if (!arr->attached(runtime)) { \
818+
return runtime.raiseTypeError("Underlying ArrayBuffer detached"); \
819+
} \
820+
return HermesValue::encodeNumberValue(arr->at(runtime, k)); \
821+
}
822+
switch (O->getKind()) {
823+
#include "hermes/VM/TypedArrays.def"
824+
default:
825+
llvm_unreachable("Invalid TypedArray after ValidateTypedArray call");
826+
}
827+
}
828+
767829
/// ES6 22.2.3.5
768830
CallResult<HermesValue>
769831
typedArrayPrototypeCopyWithin(void *, Runtime &runtime, NativeArgs args) {
@@ -1757,6 +1819,13 @@ Handle<JSObject> createTypedArrayBaseConstructor(Runtime &runtime) {
17571819
false,
17581820
true);
17591821
// Methods.
1822+
defineMethod(
1823+
runtime,
1824+
proto,
1825+
Predefined::getSymbolID(Predefined::at),
1826+
nullptr,
1827+
typedArrayPrototypeAt,
1828+
1);
17601829
defineMethod(
17611830
runtime,
17621831
proto,

test/hermes/TypedArray.js

+13
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,19 @@ cons.forEach(function(TypedArray) {
443443
});
444444
/// @}
445445

446+
/// @name %TypedArray%.prototype.at
447+
/// @{
448+
cons.forEach(function(TA) {
449+
var ta = new TA([1, 2, 3, 4, 5]);
450+
assert.equal(ta.at(0).toString(), '1');
451+
assert.equal(ta.at(-1).toString(), '5');
452+
assert.equal(ta.at(10), undefined);
453+
assert.throws(function(){TA.prototype.at.call(ta, 1n)}, TypeError);
454+
assert.throws(function(){TA.prototype.at.call("hi", 1)}, TypeError);
455+
assert.throws(function(){TA.prototype.at.call([1, "f", 3], 1)}, TypeError);
456+
});
457+
// @}
458+
446459
/// @name %TypedArray%.prototype.copyWithin
447460
/// @{
448461

test/hermes/array-functions.js

+21
Original file line numberDiff line numberDiff line change
@@ -1099,3 +1099,24 @@ print(arrayEquals([1,2,3].flatMap(function(x) {
10991099
return [x, x+this];
11001100
}, 100), [1,101,2,102,3,103]));
11011101
// CHECK-NEXT: true
1102+
1103+
print([1, 2, 3, 4, 5].at(1));
1104+
// CHECK-NEXT: 2
1105+
print(Array.prototype.at.call({length: 3, 0: 'a', 1: 'b', 2: 'c'}, 1));
1106+
// CHECK-NEXT: b
1107+
print([1, 2, 3, 4, 5].at(6));
1108+
// CHECK-NEXT: undefined
1109+
print(Array.prototype.at.call({length: 0, 0: 'a', 1: 'b', 2: 'c'}, 1));
1110+
// CHECK-NEXT: undefined
1111+
try { [].at(1n); } catch(e) { print(e.name) }
1112+
// CHECK-NEXT: TypeError
1113+
print([1, 2, 3, 4, 5].at(-1));
1114+
// CHECK-NEXT: 5
1115+
print([1, 2, 3, 4, 5].at(-5));
1116+
// CHECK-NEXT: 1
1117+
print([1, 2, 3, 4, 5].at(-6));
1118+
// CHECK-NEXT: undefined
1119+
print(Array.prototype.at.call({length: 3, 0: 'a', 1: 'b', 2: 'c'}, -1));
1120+
// CHECK-NEXT: c
1121+
print(Array.prototype.at.call({length: 30}, 5));
1122+
// CHECK-NEXT: undefined

test/hermes/string-functions.js

+14
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,20 @@ print('empty', res, res.length);
492492
print(String.prototype.trimEnd === String.prototype.trimRight);
493493
// CHECK-NEXT: true
494494

495+
496+
print('at');
497+
// CHECK-NEXT: at
498+
print("abc".at(1));
499+
// CHECK-NEXT: b
500+
print("abc".at(-1));
501+
// CHECK-NEXT: c
502+
print("abc".at(false));
503+
// CHECK-NEXT: a
504+
print(String.prototype.at.call(true, -1));
505+
// CHECK-NEXT: e
506+
print("".at(0));
507+
// CHECK-NEXT: undefined
508+
495509
print('indexOf');
496510
// CHECK-LABEL: indexOf
497511
print('abc'.indexOf('a'))

utils/testsuite/testsuite_skiplist.py

-4
Original file line numberDiff line numberDiff line change
@@ -432,10 +432,6 @@
432432
"test262/test/built-ins/Array/prototype/splice/create-ctor-non-object.js",
433433
"test262/test/built-ins/Array/prototype/flat/non-object-ctor-throws.js",
434434
"test262/test/built-ins/Array/prototype/flatMap/this-value-ctor-non-object.js",
435-
# TODO(T76109235) proposal-relative-indexing-method
436-
"test262/test/built-ins/Array/prototype/at/",
437-
"test262/test/built-ins/TypedArray/prototype/at/",
438-
"test262/test/built-ins/String/prototype/at/",
439435
# TODO(T90541287) array length coercion order
440436
"test262/test/built-ins/Array/length/define-own-prop-length-coercion-order-set.js",
441437
"test262/test/built-ins/Array/length/define-own-prop-length-coercion-order.js",

0 commit comments

Comments
 (0)