Skip to content

Commit 6c24bab

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 07c00f4 commit 6c24bab

File tree

3 files changed

+169
-0
lines changed

3 files changed

+169
-0
lines changed
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;
@@ -798,4 +802,87 @@ TEST_F(BridgingTest, highResTimeStampTest) {
798802
1.000001, bridging::toJs(rt, HighResDuration::fromNanoseconds(1e6 + 1)));
799803
}
800804

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

0 commit comments

Comments
 (0)