From 2e4176c2245ad04604040657cddea8d6687a72fb Mon Sep 17 00:00:00 2001 From: Christoph Purrer Date: Fri, 22 May 2026 21:56:50 -0700 Subject: [PATCH] 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`, preserving signedness. The wrapper is constructed from `jsi::BigInt` (checking `isInt64()`/`isUint64()` for lossless conversion) and provides `toJSBigInt()` for the reverse direction. A `Bridging` 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 { ``` or ``` struct Bridging { ``` ?????? 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 { // 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 --- .../ReactCommon/react/bridging/BigInt.h | 81 +++++++++++++++++ .../ReactCommon/react/bridging/Bridging.h | 1 + .../react/bridging/tests/BridgingTest.cpp | 87 +++++++++++++++++++ .../api-snapshots/ReactAndroidDebugCxx.api | 17 +++- .../api-snapshots/ReactAndroidReleaseCxx.api | 17 +++- .../api-snapshots/ReactAppleDebugCxx.api | 17 +++- .../api-snapshots/ReactAppleReleaseCxx.api | 17 +++- .../api-snapshots/ReactCommonDebugCxx.api | 17 +++- .../api-snapshots/ReactCommonReleaseCxx.api | 17 +++- 9 files changed, 259 insertions(+), 12 deletions(-) create mode 100644 packages/react-native/ReactCommon/react/bridging/BigInt.h diff --git a/packages/react-native/ReactCommon/react/bridging/BigInt.h b/packages/react-native/ReactCommon/react/bridging/BigInt.h new file mode 100644 index 000000000000..8c6156d1a39a --- /dev/null +++ b/packages/react-native/ReactCommon/react/bridging/BigInt.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include + +#include +#include + +namespace facebook::react { + +class BigInt { + public: + BigInt(jsi::Runtime &rt, const jsi::BigInt &bigint) + { + if (bigint.isInt64(rt)) { + value_ = bigint.asInt64(rt); + } else if (bigint.isUint64(rt)) { + value_ = bigint.asUint64(rt); + } else { + throw jsi::JSError(rt, "BigInt value cannot be losslessly represented as int64_t or uint64_t"); + } + } + + /* implicit */ BigInt(int64_t value) : value_(value) {} + /* implicit */ BigInt(uint64_t value) : value_(value) {} + + bool isInt64() const + { + return std::holds_alternative(value_); + } + + bool isUint64() const + { + return std::holds_alternative(value_); + } + + int64_t asInt64() const + { + return std::get(value_); + } + + uint64_t asUint64() const + { + return std::get(value_); + } + + jsi::BigInt toJSBigInt(jsi::Runtime &rt) const + { + if (isInt64()) { + return jsi::BigInt::fromInt64(rt, asInt64()); + } else { + return jsi::BigInt::fromUint64(rt, asUint64()); + } + } + + bool operator==(const BigInt &other) const = default; + + private: + std::variant value_; +}; + +template <> +struct Bridging { + static BigInt fromJs(jsi::Runtime &rt, const jsi::Value &value) + { + return {rt, value.getBigInt(rt)}; + } + + static jsi::BigInt toJs(jsi::Runtime &rt, const BigInt &value) + { + return value.toJSBigInt(rt); + } +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/bridging/Bridging.h b/packages/react-native/ReactCommon/react/bridging/Bridging.h index 7507c8ae3048..df1109a6a57f 100644 --- a/packages/react-native/ReactCommon/react/bridging/Bridging.h +++ b/packages/react-native/ReactCommon/react/bridging/Bridging.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include diff --git a/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp b/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp index 484226012c4d..6f79d82c7a9a 100644 --- a/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp +++ b/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp @@ -10,6 +10,10 @@ #include "BridgingTest.h" +#include +#include +#include + namespace facebook::react { using namespace std::literals; @@ -971,4 +975,87 @@ TEST_F(BridgingTest, highResTimeStampTest) { 1.000001, bridging::toJs(rt, HighResDuration::fromNanoseconds(1e6 + 1))); } +TEST_F(BridgingTest, bigintTest) { + // Test BigInt construction from int64_t + BigInt fromSigned(static_cast(42)); + EXPECT_TRUE(fromSigned.isInt64()); + EXPECT_FALSE(fromSigned.isUint64()); + EXPECT_EQ(42, fromSigned.asInt64()); + + // Test BigInt construction from uint64_t + BigInt fromUnsigned(static_cast(42)); + EXPECT_FALSE(fromUnsigned.isInt64()); + EXPECT_TRUE(fromUnsigned.isUint64()); + EXPECT_EQ(42ULL, fromUnsigned.asUint64()); + + // Test BigInt construction from jsi::BigInt with signed value + auto jsiBigint = jsi::BigInt::fromInt64(rt, -123456789012345LL); + BigInt fromJsi(rt, jsiBigint); + EXPECT_TRUE(fromJsi.isInt64()); + EXPECT_EQ(-123456789012345LL, fromJsi.asInt64()); + + // Test BigInt construction from jsi::BigInt with large unsigned value + // (doesn't fit in int64_t, so should be stored as uint64_t) + constexpr uint64_t uint64Max = std::numeric_limits::max(); + auto jsiUnsigned = jsi::BigInt::fromUint64(rt, uint64Max); + BigInt fromJsiUnsigned(rt, jsiUnsigned); + EXPECT_TRUE(fromJsiUnsigned.isUint64()); + EXPECT_EQ(uint64Max, fromJsiUnsigned.asUint64()); + + // Test BigInt construction from jsi::BigInt with small positive value + // (fits in both int64_t and uint64_t — should prefer int64_t) + auto jsiSmall = jsi::BigInt::fromInt64(rt, 5); + BigInt fromJsiSmall(rt, jsiSmall); + EXPECT_TRUE(fromJsiSmall.isInt64()); + EXPECT_EQ(5, fromJsiSmall.asInt64()); + + // Test toJSBigInt roundtrip for signed value + BigInt signedVal(static_cast(-42)); + auto jsResult = signedVal.toJSBigInt(rt); + EXPECT_EQ(-42, jsResult.asInt64(rt)); + + // Test toJSBigInt roundtrip for unsigned value + BigInt unsignedVal(uint64Max); + auto jsUnsignedResult = unsignedVal.toJSBigInt(rt); + EXPECT_EQ(uint64Max, jsUnsignedResult.asUint64(rt)); + + // Test Bridging::fromJs + constexpr int64_t int64Max = std::numeric_limits::max(); + auto jsBigint = jsi::BigInt::fromInt64(rt, int64Max); + auto bridged = + bridging::fromJs(rt, jsi::Value(rt, jsBigint), invoker); + EXPECT_TRUE(bridged.isInt64()); + EXPECT_EQ(int64Max, bridged.asInt64()); + + // Test Bridging::toJs + BigInt toConvert(static_cast(123456789012345LL)); + auto jsConverted = bridging::toJs(rt, toConvert); + EXPECT_EQ(123456789012345LL, jsConverted.asInt64(rt)); + + // Test roundtrip at extreme values via bridging + constexpr int64_t int64Min = std::numeric_limits::min(); + + auto roundtripMin = bridging::fromJs( + rt, jsi::Value(rt, bridging::toJs(rt, BigInt(int64Min))), invoker); + EXPECT_TRUE(roundtripMin.isInt64()); + EXPECT_EQ(int64Min, roundtripMin.asInt64()); + + auto roundtripMax = bridging::fromJs( + rt, jsi::Value(rt, bridging::toJs(rt, BigInt(int64Max))), invoker); + EXPECT_TRUE(roundtripMax.isInt64()); + EXPECT_EQ(int64Max, roundtripMax.asInt64()); + + auto roundtripUmax = bridging::fromJs( + rt, jsi::Value(rt, bridging::toJs(rt, BigInt(uint64Max))), invoker); + EXPECT_TRUE(roundtripUmax.isUint64()); + EXPECT_EQ(uint64Max, roundtripUmax.asUint64()); + + // Test equality + EXPECT_EQ(BigInt(static_cast(42)), BigInt(static_cast(42))); + EXPECT_EQ(BigInt(uint64Max), BigInt(uint64Max)); + // Same numeric value but different variant type are not equal + EXPECT_NE( + BigInt(static_cast(42)), BigInt(static_cast(42))); +} + } // namespace facebook::react diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index 9a10db992942..0af8b94fb22f 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -1834,6 +1834,18 @@ class facebook::react::BaseViewProps : public facebook::react::YogaStylableProps public void setProp(const facebook::react::PropsParserContext& context, facebook::react::RawPropsPropNameHash hash, const char* propName, const facebook::react::RawValue& value); } +class facebook::react::BigInt { + public BigInt(facebook::jsi::Runtime& rt, const facebook::jsi::BigInt& bigint); + public BigInt(int64_t value); + public BigInt(uint64_t value); + public bool isInt64() const; + public bool isUint64() const; + public bool operator==(const facebook::react::BigInt& other) const = default; + public facebook::jsi::BigInt toJSBigInt(facebook::jsi::Runtime& rt) const; + public int64_t asInt64() const; + public uint64_t asUint64() const; +} + class facebook::react::BigStringBuffer : public facebook::jsi::Buffer { public BigStringBuffer(std::unique_ptr script); public virtual const uint8_t* data() const override; @@ -9600,8 +9612,9 @@ struct facebook::react::Bridging { public static facebook::jsi::WeakObject fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::Object& value); } -struct facebook::react::Bridging { - public static facebook::jsi::Value toJs(facebook::jsi::Runtime& rt, facebook::react::AsyncArrayBuffer buffer); +struct facebook::react::Bridging { + public static facebook::jsi::BigInt toJs(facebook::jsi::Runtime& rt, const facebook::react::BigInt& value); + public static facebook::react::BigInt fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::Value& value); } struct facebook::react::Bridging : public facebook::react::NativeAnimatedTurboModuleEndResultBridging { diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index 93f507c31385..bfe6dccdbd15 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -1832,6 +1832,18 @@ class facebook::react::BaseViewProps : public facebook::react::YogaStylableProps public void setProp(const facebook::react::PropsParserContext& context, facebook::react::RawPropsPropNameHash hash, const char* propName, const facebook::react::RawValue& value); } +class facebook::react::BigInt { + public BigInt(facebook::jsi::Runtime& rt, const facebook::jsi::BigInt& bigint); + public BigInt(int64_t value); + public BigInt(uint64_t value); + public bool isInt64() const; + public bool isUint64() const; + public bool operator==(const facebook::react::BigInt& other) const = default; + public facebook::jsi::BigInt toJSBigInt(facebook::jsi::Runtime& rt) const; + public int64_t asInt64() const; + public uint64_t asUint64() const; +} + class facebook::react::BigStringBuffer : public facebook::jsi::Buffer { public BigStringBuffer(std::unique_ptr script); public virtual const uint8_t* data() const override; @@ -9453,8 +9465,9 @@ struct facebook::react::Bridging { public static facebook::jsi::WeakObject fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::Object& value); } -struct facebook::react::Bridging { - public static facebook::jsi::Value toJs(facebook::jsi::Runtime& rt, facebook::react::AsyncArrayBuffer buffer); +struct facebook::react::Bridging { + public static facebook::jsi::BigInt toJs(facebook::jsi::Runtime& rt, const facebook::react::BigInt& value); + public static facebook::react::BigInt fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::Value& value); } struct facebook::react::Bridging : public facebook::react::NativeAnimatedTurboModuleEndResultBridging { diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index a2b9bb40e125..13f3cc3e6680 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -4808,6 +4808,18 @@ class facebook::react::BaseViewProps : public facebook::react::YogaStylableProps public void setProp(const facebook::react::PropsParserContext& context, facebook::react::RawPropsPropNameHash hash, const char* propName, const facebook::react::RawValue& value); } +class facebook::react::BigInt { + public BigInt(facebook::jsi::Runtime& rt, const facebook::jsi::BigInt& bigint); + public BigInt(int64_t value); + public BigInt(uint64_t value); + public bool isInt64() const; + public bool isUint64() const; + public bool operator==(const facebook::react::BigInt& other) const = default; + public facebook::jsi::BigInt toJSBigInt(facebook::jsi::Runtime& rt) const; + public int64_t asInt64() const; + public uint64_t asUint64() const; +} + class facebook::react::BigStringBuffer : public facebook::jsi::Buffer { public BigStringBuffer(std::unique_ptr script); public virtual const uint8_t* data() const override; @@ -11904,8 +11916,9 @@ struct facebook::react::Bridging { public static facebook::jsi::WeakObject fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::Object& value); } -struct facebook::react::Bridging { - public static facebook::jsi::Value toJs(facebook::jsi::Runtime& rt, facebook::react::AsyncArrayBuffer buffer); +struct facebook::react::Bridging { + public static facebook::jsi::BigInt toJs(facebook::jsi::Runtime& rt, const facebook::react::BigInt& value); + public static facebook::react::BigInt fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::Value& value); } struct facebook::react::Bridging : public facebook::react::NativeAnimatedTurboModuleEndResultBridging { diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index 84098faa3185..65aaabb6167d 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -4806,6 +4806,18 @@ class facebook::react::BaseViewProps : public facebook::react::YogaStylableProps public void setProp(const facebook::react::PropsParserContext& context, facebook::react::RawPropsPropNameHash hash, const char* propName, const facebook::react::RawValue& value); } +class facebook::react::BigInt { + public BigInt(facebook::jsi::Runtime& rt, const facebook::jsi::BigInt& bigint); + public BigInt(int64_t value); + public BigInt(uint64_t value); + public bool isInt64() const; + public bool isUint64() const; + public bool operator==(const facebook::react::BigInt& other) const = default; + public facebook::jsi::BigInt toJSBigInt(facebook::jsi::Runtime& rt) const; + public int64_t asInt64() const; + public uint64_t asUint64() const; +} + class facebook::react::BigStringBuffer : public facebook::jsi::Buffer { public BigStringBuffer(std::unique_ptr script); public virtual const uint8_t* data() const override; @@ -11767,8 +11779,9 @@ struct facebook::react::Bridging { public static facebook::jsi::WeakObject fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::Object& value); } -struct facebook::react::Bridging { - public static facebook::jsi::Value toJs(facebook::jsi::Runtime& rt, facebook::react::AsyncArrayBuffer buffer); +struct facebook::react::Bridging { + public static facebook::jsi::BigInt toJs(facebook::jsi::Runtime& rt, const facebook::react::BigInt& value); + public static facebook::react::BigInt fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::Value& value); } struct facebook::react::Bridging : public facebook::react::NativeAnimatedTurboModuleEndResultBridging { diff --git a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api index 3a7473351c4b..1ee1ab76a3c9 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api @@ -1158,6 +1158,18 @@ class facebook::react::BaseViewProps : public facebook::react::YogaStylableProps public void setProp(const facebook::react::PropsParserContext& context, facebook::react::RawPropsPropNameHash hash, const char* propName, const facebook::react::RawValue& value); } +class facebook::react::BigInt { + public BigInt(facebook::jsi::Runtime& rt, const facebook::jsi::BigInt& bigint); + public BigInt(int64_t value); + public BigInt(uint64_t value); + public bool isInt64() const; + public bool isUint64() const; + public bool operator==(const facebook::react::BigInt& other) const = default; + public facebook::jsi::BigInt toJSBigInt(facebook::jsi::Runtime& rt) const; + public int64_t asInt64() const; + public uint64_t asUint64() const; +} + class facebook::react::BigStringBuffer : public facebook::jsi::Buffer { public BigStringBuffer(std::unique_ptr script); public virtual const uint8_t* data() const override; @@ -6669,8 +6681,9 @@ struct facebook::react::Bridging { public static facebook::jsi::WeakObject fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::Object& value); } -struct facebook::react::Bridging { - public static facebook::jsi::Value toJs(facebook::jsi::Runtime& rt, facebook::react::AsyncArrayBuffer buffer); +struct facebook::react::Bridging { + public static facebook::jsi::BigInt toJs(facebook::jsi::Runtime& rt, const facebook::react::BigInt& value); + public static facebook::react::BigInt fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::Value& value); } struct facebook::react::Bridging : public NativeAnimatedTurboModuleEndResultBridging { diff --git a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api index f64fbd2790a4..283301341a59 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api @@ -1156,6 +1156,18 @@ class facebook::react::BaseViewProps : public facebook::react::YogaStylableProps public void setProp(const facebook::react::PropsParserContext& context, facebook::react::RawPropsPropNameHash hash, const char* propName, const facebook::react::RawValue& value); } +class facebook::react::BigInt { + public BigInt(facebook::jsi::Runtime& rt, const facebook::jsi::BigInt& bigint); + public BigInt(int64_t value); + public BigInt(uint64_t value); + public bool isInt64() const; + public bool isUint64() const; + public bool operator==(const facebook::react::BigInt& other) const = default; + public facebook::jsi::BigInt toJSBigInt(facebook::jsi::Runtime& rt) const; + public int64_t asInt64() const; + public uint64_t asUint64() const; +} + class facebook::react::BigStringBuffer : public facebook::jsi::Buffer { public BigStringBuffer(std::unique_ptr script); public virtual const uint8_t* data() const override; @@ -6660,8 +6672,9 @@ struct facebook::react::Bridging { public static facebook::jsi::WeakObject fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::Object& value); } -struct facebook::react::Bridging { - public static facebook::jsi::Value toJs(facebook::jsi::Runtime& rt, facebook::react::AsyncArrayBuffer buffer); +struct facebook::react::Bridging { + public static facebook::jsi::BigInt toJs(facebook::jsi::Runtime& rt, const facebook::react::BigInt& value); + public static facebook::react::BigInt fromJs(facebook::jsi::Runtime& rt, const facebook::jsi::Value& value); } struct facebook::react::Bridging : public NativeAnimatedTurboModuleEndResultBridging {