Skip to content

Commit 9c612de

Browse files
Add BigInt bridging support for Turbo Modules (#56008)
Summary: This diff adds native BigInt bridging support to React Native's C++ bridging layer. BigInt is a JavaScript primitive that represents integers with arbitrary precision, enabling safe handling of 64-bit integers that exceed JavaScript's Number.MAX_SAFE_INTEGER (2^53-1). This implementation introduces a `facebook::react::BigInt` C++ wrapper class that internally stores values as `std::variant<int64_t, uint64_t>`, preserving signedness. The wrapper is constructed from `jsi::BigInt` (checking `isInt64()`/`isUint64()` for lossless conversion) and provides `toJSBigInt()` for the reverse direction. A `Bridging<BigInt>` template specialization converts between `jsi::Value` and `facebook::react::BigInt`, enabling seamless use in Turbo Module method signatures. Example usage: ```cpp // In your Turbo Module BigInt getBigInt(jsi::Runtime &rt, BigInt arg) { return arg; // Receives BigInt from JS, returns BigInt to JS } ``` ```javascript // In JavaScript const result = nativeModule.getBigInt(BigInt('9223372036854775807')); ``` ## Hey, wait a moment .... Why are we not simply bridging like this: ``` struct Bridging<int64_t> { ``` or ``` struct Bridging<uint64_t> { ``` ?????? Reason: It is very likely that custom implementations are already present in many RN apps > See: https://reactnative.dev/docs/the-new-architecture/custom-cxx-types ``` template <> struct Bridging<int64_t> { // Converts from the JS representation to the C++ representation static int64_t fromJs(jsi::Runtime &rt, const jsi::String &value) { try { size_t pos; auto str = value.utf8(rt); auto num = std::stoll(str, &pos); if (pos != str.size()) { throw std::invalid_argument("Invalid number"); // don't support alphanumeric strings } return num; } catch (const std::logic_error &e) { throw jsi::JSError(rt, e.what()); } } // Converts from the C++ representation to the JS representation static jsi::String toJs(jsi::Runtime &rt, int64_t value) { return bridging::toJs(rt, std::to_string(value)); } }; ``` Changelog: [General][Added] - Add BigInt bridging support for Turbo Modules Differential Revision: D95706781
1 parent 6e26262 commit 9c612de

9 files changed

Lines changed: 271 additions & 0 deletions

File tree

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#pragma once
9+
10+
#include <react/bridging/Base.h>
11+
12+
#include <cstdint>
13+
#include <variant>
14+
15+
namespace facebook::react {
16+
17+
class BigInt {
18+
public:
19+
BigInt(jsi::Runtime &rt, const jsi::BigInt &bigint)
20+
{
21+
if (bigint.isInt64(rt)) {
22+
value_ = bigint.asInt64(rt);
23+
} else if (bigint.isUint64(rt)) {
24+
value_ = bigint.asUint64(rt);
25+
} else {
26+
throw jsi::JSError(rt, "BigInt value cannot be losslessly represented as int64_t or uint64_t");
27+
}
28+
}
29+
30+
/* implicit */ BigInt(int64_t value) : value_(value) {}
31+
/* implicit */ BigInt(uint64_t value) : value_(value) {}
32+
33+
bool isInt64() const
34+
{
35+
return std::holds_alternative<int64_t>(value_);
36+
}
37+
38+
bool isUint64() const
39+
{
40+
return std::holds_alternative<uint64_t>(value_);
41+
}
42+
43+
int64_t asInt64() const
44+
{
45+
return std::get<int64_t>(value_);
46+
}
47+
48+
uint64_t asUint64() const
49+
{
50+
return std::get<uint64_t>(value_);
51+
}
52+
53+
jsi::BigInt toJSBigInt(jsi::Runtime &rt) const
54+
{
55+
if (isInt64()) {
56+
return jsi::BigInt::fromInt64(rt, asInt64());
57+
} else {
58+
return jsi::BigInt::fromUint64(rt, asUint64());
59+
}
60+
}
61+
62+
bool operator==(const BigInt &other) const = default;
63+
64+
private:
65+
std::variant<int64_t, uint64_t> value_;
66+
};
67+
68+
template <>
69+
struct Bridging<BigInt> {
70+
static BigInt fromJs(jsi::Runtime &rt, const jsi::Value &value)
71+
{
72+
return {rt, value.getBigInt(rt)};
73+
}
74+
75+
static jsi::BigInt toJs(jsi::Runtime &rt, const BigInt &value)
76+
{
77+
return value.toJSBigInt(rt);
78+
}
79+
};
80+
81+
} // namespace facebook::react

packages/react-native/ReactCommon/react/bridging/Bridging.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include <react/bridging/AString.h>
1111
#include <react/bridging/Array.h>
12+
#include <react/bridging/BigInt.h>
1213
#include <react/bridging/Bool.h>
1314
#include <react/bridging/Class.h>
1415
#include <react/bridging/Dynamic.h>

packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77

88
#include "BridgingTest.h"
99

10+
#include <cstdint>
11+
#include <limits>
12+
#include <utility>
13+
1014
namespace facebook::react {
1115

1216
using namespace std::literals;
@@ -793,4 +797,87 @@ TEST_F(BridgingTest, highResTimeStampTest) {
793797
1.000001, bridging::toJs(rt, HighResDuration::fromNanoseconds(1e6 + 1)));
794798
}
795799

800+
TEST_F(BridgingTest, bigintTest) {
801+
// Test BigInt construction from int64_t
802+
BigInt fromSigned(static_cast<int64_t>(42));
803+
EXPECT_TRUE(fromSigned.isInt64());
804+
EXPECT_FALSE(fromSigned.isUint64());
805+
EXPECT_EQ(42, fromSigned.asInt64());
806+
807+
// Test BigInt construction from uint64_t
808+
BigInt fromUnsigned(static_cast<uint64_t>(42));
809+
EXPECT_FALSE(fromUnsigned.isInt64());
810+
EXPECT_TRUE(fromUnsigned.isUint64());
811+
EXPECT_EQ(42ULL, fromUnsigned.asUint64());
812+
813+
// Test BigInt construction from jsi::BigInt with signed value
814+
auto jsiBigint = jsi::BigInt::fromInt64(rt, -123456789012345LL);
815+
BigInt fromJsi(rt, jsiBigint);
816+
EXPECT_TRUE(fromJsi.isInt64());
817+
EXPECT_EQ(-123456789012345LL, fromJsi.asInt64());
818+
819+
// Test BigInt construction from jsi::BigInt with large unsigned value
820+
// (doesn't fit in int64_t, so should be stored as uint64_t)
821+
constexpr uint64_t uint64Max = std::numeric_limits<uint64_t>::max();
822+
auto jsiUnsigned = jsi::BigInt::fromUint64(rt, uint64Max);
823+
BigInt fromJsiUnsigned(rt, jsiUnsigned);
824+
EXPECT_TRUE(fromJsiUnsigned.isUint64());
825+
EXPECT_EQ(uint64Max, fromJsiUnsigned.asUint64());
826+
827+
// Test BigInt construction from jsi::BigInt with small positive value
828+
// (fits in both int64_t and uint64_t — should prefer int64_t)
829+
auto jsiSmall = jsi::BigInt::fromInt64(rt, 5);
830+
BigInt fromJsiSmall(rt, jsiSmall);
831+
EXPECT_TRUE(fromJsiSmall.isInt64());
832+
EXPECT_EQ(5, fromJsiSmall.asInt64());
833+
834+
// Test toJSBigInt roundtrip for signed value
835+
BigInt signedVal(static_cast<int64_t>(-42));
836+
auto jsResult = signedVal.toJSBigInt(rt);
837+
EXPECT_EQ(-42, jsResult.asInt64(rt));
838+
839+
// Test toJSBigInt roundtrip for unsigned value
840+
BigInt unsignedVal(uint64Max);
841+
auto jsUnsignedResult = unsignedVal.toJSBigInt(rt);
842+
EXPECT_EQ(uint64Max, jsUnsignedResult.asUint64(rt));
843+
844+
// Test Bridging<BigInt>::fromJs
845+
constexpr int64_t int64Max = std::numeric_limits<int64_t>::max();
846+
auto jsBigint = jsi::BigInt::fromInt64(rt, int64Max);
847+
auto bridged =
848+
bridging::fromJs<BigInt>(rt, jsi::Value(rt, jsBigint), invoker);
849+
EXPECT_TRUE(bridged.isInt64());
850+
EXPECT_EQ(int64Max, bridged.asInt64());
851+
852+
// Test Bridging<BigInt>::toJs
853+
BigInt toConvert(static_cast<int64_t>(123456789012345LL));
854+
auto jsConverted = bridging::toJs(rt, toConvert);
855+
EXPECT_EQ(123456789012345LL, jsConverted.asInt64(rt));
856+
857+
// Test roundtrip at extreme values via bridging
858+
constexpr int64_t int64Min = std::numeric_limits<int64_t>::min();
859+
860+
auto roundtripMin = bridging::fromJs<BigInt>(
861+
rt, jsi::Value(rt, bridging::toJs(rt, BigInt(int64Min))), invoker);
862+
EXPECT_TRUE(roundtripMin.isInt64());
863+
EXPECT_EQ(int64Min, roundtripMin.asInt64());
864+
865+
auto roundtripMax = bridging::fromJs<BigInt>(
866+
rt, jsi::Value(rt, bridging::toJs(rt, BigInt(int64Max))), invoker);
867+
EXPECT_TRUE(roundtripMax.isInt64());
868+
EXPECT_EQ(int64Max, roundtripMax.asInt64());
869+
870+
auto roundtripUmax = bridging::fromJs<BigInt>(
871+
rt, jsi::Value(rt, bridging::toJs(rt, BigInt(uint64Max))), invoker);
872+
EXPECT_TRUE(roundtripUmax.isUint64());
873+
EXPECT_EQ(uint64Max, roundtripUmax.asUint64());
874+
875+
// Test equality
876+
EXPECT_EQ(BigInt(static_cast<int64_t>(42)), BigInt(static_cast<int64_t>(42)));
877+
EXPECT_EQ(BigInt(uint64Max), BigInt(uint64Max));
878+
// Same numeric value but different variant type are not equal
879+
EXPECT_NE(
880+
BigInt(static_cast<int64_t>(42)), BigInt(static_cast<uint64_t>(42)));
881+
}
882+
796883
} // namespace facebook::react

scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1816,6 +1816,18 @@ class facebook::react::BaseViewProps : public facebook::react::YogaStylableProps
18161816
public void setProp(const facebook::react::PropsParserContext& context, facebook::react::RawPropsPropNameHash hash, const char* propName, const facebook::react::RawValue& value);
18171817
}
18181818

1819+
class facebook::react::BigInt {
1820+
public BigInt(facebook::jsi::Runtime& rt, const facebook::jsi::BigInt& bigint);
1821+
public BigInt(int64_t value);
1822+
public BigInt(uint64_t value);
1823+
public bool isInt64() const;
1824+
public bool isUint64() const;
1825+
public bool operator==(const facebook::react::BigInt& other) const = default;
1826+
public facebook::jsi::BigInt toJSBigInt(facebook::jsi::Runtime& rt) const;
1827+
public int64_t asInt64() const;
1828+
public uint64_t asUint64() const;
1829+
}
1830+
18191831
class facebook::react::BigStringBuffer : public facebook::jsi::Buffer {
18201832
public BigStringBuffer(std::unique_ptr<const facebook::react::JSBigString> script);
18211833
public virtual const uint8_t* data() const override;
@@ -9595,6 +9607,11 @@ struct facebook::react::Bridging<facebook::jsi::WeakObject> {
95959607
public static facebook::jsi::WeakObject fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::Object& value);
95969608
}
95979609

9610+
struct facebook::react::Bridging<facebook::react::BigInt> {
9611+
public static facebook::jsi::BigInt toJs(facebook::jsi::Runtime& rt, const facebook::react::BigInt& value);
9612+
public static facebook::react::BigInt fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::Value& value);
9613+
}
9614+
95989615
struct facebook::react::Bridging<facebook::react::EndResult> : public facebook::react::NativeAnimatedTurboModuleEndResultBridging<facebook::react::EndResult> {
95999616
}
96009617

scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1814,6 +1814,18 @@ class facebook::react::BaseViewProps : public facebook::react::YogaStylableProps
18141814
public void setProp(const facebook::react::PropsParserContext& context, facebook::react::RawPropsPropNameHash hash, const char* propName, const facebook::react::RawValue& value);
18151815
}
18161816

1817+
class facebook::react::BigInt {
1818+
public BigInt(facebook::jsi::Runtime& rt, const facebook::jsi::BigInt& bigint);
1819+
public BigInt(int64_t value);
1820+
public BigInt(uint64_t value);
1821+
public bool isInt64() const;
1822+
public bool isUint64() const;
1823+
public bool operator==(const facebook::react::BigInt& other) const = default;
1824+
public facebook::jsi::BigInt toJSBigInt(facebook::jsi::Runtime& rt) const;
1825+
public int64_t asInt64() const;
1826+
public uint64_t asUint64() const;
1827+
}
1828+
18171829
class facebook::react::BigStringBuffer : public facebook::jsi::Buffer {
18181830
public BigStringBuffer(std::unique_ptr<const facebook::react::JSBigString> script);
18191831
public virtual const uint8_t* data() const override;
@@ -9448,6 +9460,11 @@ struct facebook::react::Bridging<facebook::jsi::WeakObject> {
94489460
public static facebook::jsi::WeakObject fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::Object& value);
94499461
}
94509462

9463+
struct facebook::react::Bridging<facebook::react::BigInt> {
9464+
public static facebook::jsi::BigInt toJs(facebook::jsi::Runtime& rt, const facebook::react::BigInt& value);
9465+
public static facebook::react::BigInt fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::Value& value);
9466+
}
9467+
94519468
struct facebook::react::Bridging<facebook::react::EndResult> : public facebook::react::NativeAnimatedTurboModuleEndResultBridging<facebook::react::EndResult> {
94529469
}
94539470

scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4794,6 +4794,18 @@ class facebook::react::BaseViewProps : public facebook::react::YogaStylableProps
47944794
public void setProp(const facebook::react::PropsParserContext& context, facebook::react::RawPropsPropNameHash hash, const char* propName, const facebook::react::RawValue& value);
47954795
}
47964796

4797+
class facebook::react::BigInt {
4798+
public BigInt(facebook::jsi::Runtime& rt, const facebook::jsi::BigInt& bigint);
4799+
public BigInt(int64_t value);
4800+
public BigInt(uint64_t value);
4801+
public bool isInt64() const;
4802+
public bool isUint64() const;
4803+
public bool operator==(const facebook::react::BigInt& other) const = default;
4804+
public facebook::jsi::BigInt toJSBigInt(facebook::jsi::Runtime& rt) const;
4805+
public int64_t asInt64() const;
4806+
public uint64_t asUint64() const;
4807+
}
4808+
47974809
class facebook::react::BigStringBuffer : public facebook::jsi::Buffer {
47984810
public BigStringBuffer(std::unique_ptr<const facebook::react::JSBigString> script);
47994811
public virtual const uint8_t* data() const override;
@@ -11912,6 +11924,11 @@ struct facebook::react::Bridging<facebook::jsi::WeakObject> {
1191211924
public static facebook::jsi::WeakObject fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::Object& value);
1191311925
}
1191411926

11927+
struct facebook::react::Bridging<facebook::react::BigInt> {
11928+
public static facebook::jsi::BigInt toJs(facebook::jsi::Runtime& rt, const facebook::react::BigInt& value);
11929+
public static facebook::react::BigInt fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::Value& value);
11930+
}
11931+
1191511932
struct facebook::react::Bridging<facebook::react::EndResult> : public facebook::react::NativeAnimatedTurboModuleEndResultBridging<facebook::react::EndResult> {
1191611933
}
1191711934

scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4792,6 +4792,18 @@ class facebook::react::BaseViewProps : public facebook::react::YogaStylableProps
47924792
public void setProp(const facebook::react::PropsParserContext& context, facebook::react::RawPropsPropNameHash hash, const char* propName, const facebook::react::RawValue& value);
47934793
}
47944794

4795+
class facebook::react::BigInt {
4796+
public BigInt(facebook::jsi::Runtime& rt, const facebook::jsi::BigInt& bigint);
4797+
public BigInt(int64_t value);
4798+
public BigInt(uint64_t value);
4799+
public bool isInt64() const;
4800+
public bool isUint64() const;
4801+
public bool operator==(const facebook::react::BigInt& other) const = default;
4802+
public facebook::jsi::BigInt toJSBigInt(facebook::jsi::Runtime& rt) const;
4803+
public int64_t asInt64() const;
4804+
public uint64_t asUint64() const;
4805+
}
4806+
47954807
class facebook::react::BigStringBuffer : public facebook::jsi::Buffer {
47964808
public BigStringBuffer(std::unique_ptr<const facebook::react::JSBigString> script);
47974809
public virtual const uint8_t* data() const override;
@@ -11775,6 +11787,11 @@ struct facebook::react::Bridging<facebook::jsi::WeakObject> {
1177511787
public static facebook::jsi::WeakObject fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::Object& value);
1177611788
}
1177711789

11790+
struct facebook::react::Bridging<facebook::react::BigInt> {
11791+
public static facebook::jsi::BigInt toJs(facebook::jsi::Runtime& rt, const facebook::react::BigInt& value);
11792+
public static facebook::react::BigInt fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::Value& value);
11793+
}
11794+
1177811795
struct facebook::react::Bridging<facebook::react::EndResult> : public facebook::react::NativeAnimatedTurboModuleEndResultBridging<facebook::react::EndResult> {
1177911796
}
1178011797

scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,6 +1140,18 @@ class facebook::react::BaseViewProps : public facebook::react::YogaStylableProps
11401140
public void setProp(const facebook::react::PropsParserContext& context, facebook::react::RawPropsPropNameHash hash, const char* propName, const facebook::react::RawValue& value);
11411141
}
11421142

1143+
class facebook::react::BigInt {
1144+
public BigInt(facebook::jsi::Runtime& rt, const facebook::jsi::BigInt& bigint);
1145+
public BigInt(int64_t value);
1146+
public BigInt(uint64_t value);
1147+
public bool isInt64() const;
1148+
public bool isUint64() const;
1149+
public bool operator==(const facebook::react::BigInt& other) const = default;
1150+
public facebook::jsi::BigInt toJSBigInt(facebook::jsi::Runtime& rt) const;
1151+
public int64_t asInt64() const;
1152+
public uint64_t asUint64() const;
1153+
}
1154+
11431155
class facebook::react::BigStringBuffer : public facebook::jsi::Buffer {
11441156
public BigStringBuffer(std::unique_ptr<const facebook::react::JSBigString> script);
11451157
public virtual const uint8_t* data() const override;
@@ -6666,6 +6678,11 @@ struct facebook::react::Bridging<facebook::jsi::WeakObject> {
66666678
public static facebook::jsi::WeakObject fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::Object& value);
66676679
}
66686680

6681+
struct facebook::react::Bridging<facebook::react::BigInt> {
6682+
public static facebook::jsi::BigInt toJs(facebook::jsi::Runtime& rt, const facebook::react::BigInt& value);
6683+
public static facebook::react::BigInt fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::Value& value);
6684+
}
6685+
66696686
struct facebook::react::Bridging<facebook::react::EndResult> : public NativeAnimatedTurboModuleEndResultBridging<facebook::react::EndResult> {
66706687
}
66716688

scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1138,6 +1138,18 @@ class facebook::react::BaseViewProps : public facebook::react::YogaStylableProps
11381138
public void setProp(const facebook::react::PropsParserContext& context, facebook::react::RawPropsPropNameHash hash, const char* propName, const facebook::react::RawValue& value);
11391139
}
11401140

1141+
class facebook::react::BigInt {
1142+
public BigInt(facebook::jsi::Runtime& rt, const facebook::jsi::BigInt& bigint);
1143+
public BigInt(int64_t value);
1144+
public BigInt(uint64_t value);
1145+
public bool isInt64() const;
1146+
public bool isUint64() const;
1147+
public bool operator==(const facebook::react::BigInt& other) const = default;
1148+
public facebook::jsi::BigInt toJSBigInt(facebook::jsi::Runtime& rt) const;
1149+
public int64_t asInt64() const;
1150+
public uint64_t asUint64() const;
1151+
}
1152+
11411153
class facebook::react::BigStringBuffer : public facebook::jsi::Buffer {
11421154
public BigStringBuffer(std::unique_ptr<const facebook::react::JSBigString> script);
11431155
public virtual const uint8_t* data() const override;
@@ -6657,6 +6669,11 @@ struct facebook::react::Bridging<facebook::jsi::WeakObject> {
66576669
public static facebook::jsi::WeakObject fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::Object& value);
66586670
}
66596671

6672+
struct facebook::react::Bridging<facebook::react::BigInt> {
6673+
public static facebook::jsi::BigInt toJs(facebook::jsi::Runtime& rt, const facebook::react::BigInt& value);
6674+
public static facebook::react::BigInt fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::Value& value);
6675+
}
6676+
66606677
struct facebook::react::Bridging<facebook::react::EndResult> : public NativeAnimatedTurboModuleEndResultBridging<facebook::react::EndResult> {
66616678
}
66626679

0 commit comments

Comments
 (0)