Skip to content

Commit a853fac

Browse files
tsaichienfacebook-github-bot
authored andcommitted
Add Array.push API (#53762)
Summary: Currently, the size of a jsi `Array` is immutable. Once the `Array` is created, users can only set the element at an existing index, but not append to it. This change adds an `Array::push` API to improve ergonomics. The default implementation is also provided in `jsi::Runtime` Changelog: [Internal] Reviewed By: lavenzg Differential Revision: D82254334
1 parent c57b46f commit a853fac

File tree

5 files changed

+159
-2
lines changed

5 files changed

+159
-2
lines changed

packages/react-native/ReactCommon/jsi/jsi/decorator.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,10 @@ class RuntimeDecorator : public Base, private jsi::Instrumentation {
400400
plain_.setValueAtIndexImpl(a, i, value);
401401
}
402402

403+
size_t push(const Array& a, const Value* elements, size_t count) override {
404+
return plain_.push(a, elements, count);
405+
}
406+
403407
Function createFunctionFromHostFunction(
404408
const PropNameID& name,
405409
unsigned int paramCount,
@@ -969,6 +973,10 @@ class WithRuntimeDecorator : public RuntimeDecorator<Plain, Base> {
969973
Around around{with_};
970974
RD::setValueAtIndexImpl(a, i, value);
971975
}
976+
size_t push(const Array& a, const Value* elements, size_t count) override {
977+
Around around{with_};
978+
return RD::push(a, elements, count);
979+
}
972980

973981
Function createFunctionFromHostFunction(
974982
const PropNameID& name,

packages/react-native/ReactCommon/jsi/jsi/jsi-inl.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,22 @@ inline Array Array::createWithElements(IRuntime& runtime, Args&&... args) {
362362
runtime, {detail::toValue(runtime, std::forward<Args>(args))...});
363363
}
364364

365+
template <typename... Args>
366+
inline size_t Array::push(IRuntime& runtime, Args&&... args) {
367+
return push(runtime, {detail::toValue(runtime, std::forward<Args>(args))...});
368+
}
369+
370+
inline size_t Array::push(
371+
IRuntime& runtime,
372+
std::initializer_list<Value> elements) {
373+
return push(runtime, elements.begin(), elements.size());
374+
}
375+
376+
inline size_t
377+
Array::push(IRuntime& runtime, const Value* elements, size_t count) {
378+
return runtime.push(*this, elements, count);
379+
}
380+
365381
template <typename... Args>
366382
inline std::vector<PropNameID> PropNameID::names(
367383
IRuntime& runtime,

packages/react-native/ReactCommon/jsi/jsi/jsi.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,16 @@ void Runtime::deleteProperty(const Object& object, const Value& name) {
459459
}
460460
}
461461

462+
size_t Runtime::push(const Array& arr, const Value* elements, size_t count) {
463+
size_t newSize = size(arr);
464+
for (size_t i = 0; i < count; i++) {
465+
arr.setProperty(*this, Value((int)newSize), elements[i]);
466+
++newSize;
467+
}
468+
arr.setProperty(*this, "length", Value((int)newSize));
469+
return newSize;
470+
}
471+
462472
void Runtime::setRuntimeDataImpl(
463473
const UUID& uuid,
464474
const void* data,

packages/react-native/ReactCommon/jsi/jsi/jsi.h

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,7 @@ class JSI_EXPORT IRuntime : public ICast {
566566
virtual Value getValueAtIndex(const Array&, size_t i) = 0;
567567
virtual void
568568
setValueAtIndexImpl(const Array&, size_t i, const Value& value) = 0;
569+
virtual size_t push(const Array&, const Value*, size_t) = 0;
569570

570571
virtual Function createFunctionFromHostFunction(
571572
const PropNameID& name,
@@ -710,6 +711,8 @@ class JSI_EXPORT Runtime : public IRuntime {
710711
void* ctx,
711712
void (*cb)(void* ctx, bool ascii, const void* data, size_t num)) override;
712713

714+
size_t push(const Array&, const Value*, size_t) override;
715+
713716
protected:
714717
friend class Pointer;
715718
friend class PropNameID;
@@ -1394,8 +1397,18 @@ class JSI_EXPORT Array : public Object {
13941397
template <typename T>
13951398
void setValueAtIndex(IRuntime& runtime, size_t i, T&& value) const;
13961399

1397-
/// There is no current API for changing the size of an array once
1398-
/// created. We'll probably need that eventually.
1400+
/// Appends provides values to the end of the Array in the order they appear.
1401+
/// Returns the new length of the array.
1402+
template <typename... Args>
1403+
size_t push(IRuntime& runtime, Args&&... args);
1404+
1405+
/// Appends everything in \p elements to the end of the Array in the order
1406+
/// they appear. Returns the new length of the array.
1407+
size_t push(IRuntime& runtime, std::initializer_list<Value> elements);
1408+
1409+
/// Appends \p count elements at \p elements to the end of the Array in the
1410+
/// order they appear.
1411+
size_t push(IRuntime& runtime, const Value* elements, size_t count);
13991412

14001413
/// Creates a new Array instance from provided values
14011414
template <typename... Args>

packages/react-native/ReactCommon/jsi/jsi/test/testlib.cpp

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2011,6 +2011,116 @@ TEST_P(JSITest, DeleteProperty) {
20112011
EXPECT_TRUE(hasRes);
20122012
}
20132013

2014+
TEST_P(JSITest, ArrayPush) {
2015+
// This Runtime Decorator is used to test the default implementation of
2016+
// Runtime::push
2017+
class RD : public RuntimeDecorator<Runtime, Runtime> {
2018+
public:
2019+
explicit RD(Runtime& rt) : RuntimeDecorator(rt) {}
2020+
2021+
size_t push(const Array& arr, const Value* elements, size_t count)
2022+
override {
2023+
return Runtime::push(arr, elements, count);
2024+
}
2025+
};
2026+
RD rd = RD(rt);
2027+
2028+
// Push to an empty array
2029+
Array arr(rd, 0);
2030+
size_t newLength = arr.push(rd, 1, 2, 3);
2031+
EXPECT_EQ(newLength, 3);
2032+
EXPECT_EQ(arr.length(rd), 3);
2033+
2034+
EXPECT_EQ(arr.getValueAtIndex(rd, 0).getNumber(), 1);
2035+
EXPECT_EQ(arr.getValueAtIndex(rd, 1).getNumber(), 2);
2036+
EXPECT_EQ(arr.getValueAtIndex(rd, 2).getNumber(), 3);
2037+
2038+
// Push to an array already containing elements
2039+
arr = Array::createWithElements(rd, 1, true);
2040+
Object obj(rd);
2041+
newLength = arr.push(rd, "foobar", obj);
2042+
EXPECT_EQ(newLength, 4);
2043+
EXPECT_EQ(arr.length(rd), 4);
2044+
2045+
EXPECT_EQ(arr.getValueAtIndex(rd, 0).getNumber(), 1);
2046+
EXPECT_TRUE(arr.getValueAtIndex(rd, 1).getBool());
2047+
EXPECT_EQ(arr.getValueAtIndex(rd, 2).getString(rd).utf8(rd), "foobar");
2048+
EXPECT_TRUE(
2049+
Object::strictEquals(rd, arr.getValueAtIndex(rd, 3).getObject(rd), obj));
2050+
2051+
// Push to a Proxy of a JS Array
2052+
arr = eval("new Proxy([1], {})").getObject(rd).getArray(rd);
2053+
EXPECT_EQ(arr.length(rd), 1);
2054+
2055+
newLength = arr.push(rd, true, "foobar");
2056+
EXPECT_EQ(newLength, 3);
2057+
EXPECT_EQ(arr.length(rd), 3);
2058+
2059+
EXPECT_EQ(arr.getValueAtIndex(rd, 0).getNumber(), 1);
2060+
EXPECT_TRUE(arr.getValueAtIndex(rd, 1).getBool());
2061+
EXPECT_EQ(arr.getValueAtIndex(rd, 2).getString(rd).utf8(rd), "foobar");
2062+
2063+
// Push to a Proxy of a JS Array, where getting the length returns a custom
2064+
// value
2065+
arr = eval(
2066+
"var arr = [1];"
2067+
"var handler = {"
2068+
" get(target, property) {"
2069+
" if (property == 'length') {"
2070+
" return target.length + 1;"
2071+
" }"
2072+
" return Reflect.get(target, property);"
2073+
" }"
2074+
"};"
2075+
"var proxy = new Proxy(arr, handler);"
2076+
"proxy;")
2077+
.getObject(rd)
2078+
.getArray(rd);
2079+
// The handler returns the underlying array's length plus 1
2080+
EXPECT_EQ(arr.length(rd), 2);
2081+
2082+
// The elements will be adding starting at element 2
2083+
newLength = arr.push(rd, 3, 4);
2084+
EXPECT_EQ(newLength, 4);
2085+
2086+
EXPECT_EQ(arr.length(rd), 5);
2087+
EXPECT_EQ(arr.getValueAtIndex(rd, 0).getNumber(), 1);
2088+
EXPECT_TRUE(arr.getValueAtIndex(rd, 1).isUndefined());
2089+
EXPECT_EQ(arr.getValueAtIndex(rd, 2).getNumber(), 3);
2090+
EXPECT_EQ(arr.getValueAtIndex(rd, 3).getNumber(), 4);
2091+
2092+
// Push to a Proxy of a JS Array, where setting the 'length' property is
2093+
// customized
2094+
arr = eval(
2095+
"var arr = [1];"
2096+
"var handler = {"
2097+
" set(target, property, value) {"
2098+
" if (property == 'length') {"
2099+
" return Reflect.set(target, property, value + 1);"
2100+
" }"
2101+
" return Reflect.set(target, property, value);"
2102+
" }"
2103+
"};"
2104+
"var proxy = new Proxy(arr, handler);"
2105+
"proxy;")
2106+
.getObject(rd)
2107+
.getArray(rd);
2108+
2109+
EXPECT_EQ(arr.length(rd), 1);
2110+
EXPECT_EQ(arr.getValueAtIndex(rd, 0).getNumber(), 1);
2111+
2112+
newLength = arr.push(rd, 2, 3);
2113+
EXPECT_EQ(newLength, 3);
2114+
2115+
// When setting the 'length' property, the handler will actually set it to 3 +
2116+
// 1
2117+
EXPECT_EQ(arr.length(rd), 4);
2118+
EXPECT_EQ(arr.getValueAtIndex(rd, 0).getNumber(), 1);
2119+
EXPECT_EQ(arr.getValueAtIndex(rd, 1).getNumber(), 2);
2120+
EXPECT_EQ(arr.getValueAtIndex(rd, 2).getNumber(), 3);
2121+
EXPECT_TRUE(arr.getValueAtIndex(rd, 3).isUndefined());
2122+
}
2123+
20142124
INSTANTIATE_TEST_CASE_P(
20152125
Runtimes,
20162126
JSITest,

0 commit comments

Comments
 (0)