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 558d91335f6c..51c9b8b11699 100644 --- a/packages/react-native/ReactCommon/react/bridging/Bridging.h +++ b/packages/react-native/ReactCommon/react/bridging/Bridging.h @@ -9,6 +9,7 @@ #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 8faa39791316..fc279a6bfc7c 100644 --- a/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp +++ b/packages/react-native/ReactCommon/react/bridging/tests/BridgingTest.cpp @@ -7,6 +7,10 @@ #include "BridgingTest.h" +#include +#include +#include + namespace facebook::react { using namespace std::literals; @@ -798,4 +802,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