Skip to content

Commit 7410dd2

Browse files
christophpurrermeta-codesync[bot]
authored andcommitted
Add BigInt Support to Java Turbo Module (#56018)
Summary: Pull Request resolved: #56018 This diff adds a getBigInt method to the Java/Kotlin sample Turbo Module, demonstrating how to use JavaScript BigInt with BigInt bridging on Android. Changes: - Updated NativeSampleTurboModule.js with getBigInt method declaration - Added getBigInt Java method in NativeSampleTurboModuleSpec.java - Implemented getBigInt in SampleTurboModule.kt using Kotlin's Long type - Added getBigInt test case in SampleTurboModuleExample.js On the C++ side, `facebook::react::BigInt` is used as the bridging type. The Java/JNI layer maps this to Java's `long` type (64-bit signed integer). JavaScript BigInt values are automatically bridged to/from Java long via the BigIntKind return type in the JNI bindings. ## Conversion Table | Type | Bits | Signed | Min | Max | | --- | --- | --- | --- | --- | | Java long | 64 | ✅ | -9.2 × 10¹⁸ | 9.2 × 10¹⁸ | | C++ int64_t | 64 | ✅ | -9.2 × 10¹⁸ | 9.2 × 10¹⁸ | | C++ uint64_t | 64 | ❌ | 0 | 1.8 × 10¹⁹ | | Java BigInteger | Arbitrary | ✅ | Unbounded | Unbounded | RESULT -> Any uint64_t value greater than 9,223,372,036,854,775,807 (2⁶³ - 1) cannot be correctly represented in a Java long ## Example: ```java // Java spec (hand-written, codegen not yet supported for BigInt) DoNotStrip public BigInteger getBigInt(BigInteger arg) { return 0; } ``` ```kotlin // Kotlin implementation override fun getBigInt(arg: BigInteger): BigInteger = arg ``` Reviewed By: sammy-SC Differential Revision: D95232272
1 parent e54363d commit 7410dd2

9 files changed

Lines changed: 133 additions & 20 deletions

File tree

packages/react-native-codegen/src/generators/modules/GenerateModuleJavaSpec.js

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -271,11 +271,8 @@ function translateFunctionParamToJavaType(
271271
imports.add('com.facebook.react.bridge.Callback');
272272
return wrapOptional('Callback', isRequired);
273273
case 'BigIntTypeAnnotation':
274-
throw new Error(
275-
createErrorMessage(
276-
`${realTypeAnnotation.type} is not supported in Java TurboModules yet`,
277-
),
278-
);
274+
imports.add('java.math.BigInteger');
275+
return wrapOptional('BigInteger', isRequired);
279276
default:
280277
realTypeAnnotation.type as 'MixedTypeAnnotation';
281278
throw new Error(createErrorMessage(realTypeAnnotation.type));
@@ -371,11 +368,8 @@ function translateFunctionReturnTypeToJavaType(
371368
imports.add('com.facebook.react.bridge.WritableArray');
372369
return wrapOptional('WritableArray', isRequired);
373370
case 'BigIntTypeAnnotation':
374-
throw new Error(
375-
createErrorMessage(
376-
`${realTypeAnnotation.type} is not supported in Java TurboModules yet`,
377-
),
378-
);
371+
imports.add('java.math.BigInteger');
372+
return wrapOptional('BigInteger', isRequired);
379373
default:
380374
realTypeAnnotation.type as 'MixedTypeAnnotation';
381375
throw new Error(createErrorMessage(realTypeAnnotation.type));
@@ -459,7 +453,7 @@ function getFalsyReturnStatementFromReturnType(
459453
case 'ArrayTypeAnnotation':
460454
return 'return null;';
461455
case 'BigIntTypeAnnotation':
462-
throw new Error(createErrorMessage(realTypeAnnotation.type));
456+
return nullable ? 'return null;' : 'return BigInteger.ZERO;';
463457
default:
464458
realTypeAnnotation.type as 'MixedTypeAnnotation';
465459
throw new Error(createErrorMessage(realTypeAnnotation.type));

packages/react-native-codegen/src/generators/modules/GenerateModuleJniCpp.js

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -299,9 +299,7 @@ function translateParamTypeToJniType(
299299
case 'Int32TypeAnnotation':
300300
return !isRequired ? 'Ljava/lang/Double;' : 'D';
301301
case 'BigIntTypeAnnotation':
302-
throw new Error(
303-
`${realTypeAnnotation.type} is not supported in Java TurboModules yet`,
304-
);
302+
return 'Ljava/math/BigInteger;';
305303
case 'GenericObjectTypeAnnotation':
306304
return 'Lcom/facebook/react/bridge/ReadableMap;';
307305
case 'ObjectTypeAnnotation':
@@ -387,9 +385,7 @@ function translateReturnTypeToJniType(
387385
case 'Int32TypeAnnotation':
388386
return nullable ? 'Ljava/lang/Double;' : 'D';
389387
case 'BigIntTypeAnnotation':
390-
throw new Error(
391-
`${realTypeAnnotation.type} is not supported in Java TurboModules yet`,
392-
);
388+
return 'Ljava/math/BigInteger;';
393389
case 'PromiseTypeAnnotation':
394390
return 'Lcom/facebook/react/bridge/Promise;';
395391
case 'GenericObjectTypeAnnotation':

packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8+
#include <array>
9+
#include <cstdint>
10+
#include <limits>
811
#include <memory>
912
#include <string>
1013

@@ -169,6 +172,53 @@ struct JPromiseImpl : public jni::JavaClass<JPromiseImpl> {
169172
}
170173
};
171174

175+
struct JBigInteger : public jni::JavaClass<JBigInteger> {
176+
constexpr static auto kJavaDescriptor = "Ljava/math/BigInteger;";
177+
178+
static jni::local_ref<javaobject> valueOf(jlong val) {
179+
static const auto cls = javaClassStatic();
180+
static const auto method =
181+
cls->getStaticMethod<javaobject(jlong)>("valueOf");
182+
return method(cls, val);
183+
}
184+
185+
static jni::local_ref<javaobject> fromUint64(uint64_t val) {
186+
// For values that fit in int64, use the fast path.
187+
if (val <= static_cast<uint64_t>(std::numeric_limits<int64_t>::max())) {
188+
return valueOf(static_cast<jlong>(val));
189+
}
190+
// For values with the high bit set, construct from a big-endian byte
191+
// array with a leading zero byte to ensure a positive signum.
192+
std::array<jbyte, 9> bytes{};
193+
bytes[0] = 0; // sign byte: ensures BigInteger treats this as positive
194+
for (int i = 8; i >= 1; --i) {
195+
bytes[i] = static_cast<jbyte>(val & 0xFF);
196+
val >>= 8;
197+
}
198+
auto env = jni::Environment::current();
199+
auto byteArray = env->NewByteArray(9);
200+
env->SetByteArrayRegion(byteArray, 0, 9, bytes.data());
201+
return newInstance(byteArray);
202+
}
203+
204+
jlong longValue() const {
205+
static const auto method =
206+
javaClassStatic()->getMethod<jlong()>("longValue");
207+
return method(self());
208+
}
209+
210+
jint bitLength() const {
211+
static const auto method =
212+
javaClassStatic()->getMethod<jint()>("bitLength");
213+
return method(self());
214+
}
215+
216+
jint signum() const {
217+
static const auto method = javaClassStatic()->getMethod<jint()>("signum");
218+
return method(self());
219+
}
220+
};
221+
172222
// This is used for generating short exception strings.
173223
std::string stringifyJSIValue(const jsi::Value& v, jsi::Runtime* rt = nullptr) {
174224
if (v.isUndefined()) {
@@ -322,7 +372,7 @@ JNIArgs convertJSIArgsToJNIArgs(
322372
return obj;
323373
};
324374

325-
for (unsigned int argIndex = 0; argIndex < count; argIndex += 1) {
375+
for (int argIndex = 0; argIndex < static_cast<int>(count); argIndex += 1) {
326376
const std::string& type = methodArgTypes.at(argIndex);
327377

328378
const jsi::Value* arg = &args[argIndex];
@@ -397,6 +447,23 @@ JNIArgs convertJSIArgsToJNIArgs(
397447
jarg->l = makeGlobalIfNecessary(
398448
jni::JBoolean::valueOf(static_cast<unsigned char>(arg->getBool()))
399449
.release());
450+
} else if (type == "Ljava/math/BigInteger;") {
451+
if (!arg->isBigInt()) {
452+
throw JavaTurboModuleArgumentConversionException(
453+
"bigint", argIndex, methodName, arg, &rt);
454+
}
455+
auto bigint = arg->getBigInt(rt);
456+
jni::local_ref<JBigInteger::javaobject> javaBigInt;
457+
if (bigint.isInt64(rt)) {
458+
javaBigInt = JBigInteger::valueOf(bigint.getInt64(rt));
459+
} else if (bigint.isUint64(rt)) {
460+
javaBigInt = JBigInteger::fromUint64(bigint.getUint64(rt));
461+
} else {
462+
throw jsi::JSError(
463+
rt,
464+
"BigInt value cannot be losslessly represented as int64_t or uint64_t");
465+
}
466+
jarg->l = makeGlobalIfNecessary(javaBigInt.release());
400467
} else if (type == "Ljava/lang/String;") {
401468
if (!arg->isString()) {
402469
throw JavaTurboModuleArgumentConversionException(
@@ -963,6 +1030,42 @@ jsi::Value JavaTurboModule::invokeJavaMethod(
9631030
TMPL::asyncMethodCallEnd(moduleName, methodName);
9641031
return jsPromise;
9651032
}
1033+
case BigIntKind: {
1034+
auto returnObject =
1035+
env->CallObjectMethodA(instance, methodID, jargs.data());
1036+
checkJNIErrorForMethodCall();
1037+
1038+
TMPL::syncMethodCallExecutionEnd(moduleName, methodName);
1039+
TMPL::syncMethodCallReturnConversionStart(moduleName, methodName);
1040+
1041+
auto returnValue = jsi::Value::null();
1042+
if (returnObject != nullptr) {
1043+
auto bigIntObj = jni::adopt_local(
1044+
static_cast<JBigInteger::javaobject>(returnObject));
1045+
1046+
// bitLength() returns bits excluding sign bit.
1047+
// A value fits in int64_t when bitLength() <= 63.
1048+
// A positive value fits in uint64_t when bitLength() <= 64.
1049+
auto bits = bigIntObj->bitLength();
1050+
if (bits <= 63) {
1051+
returnValue = jsi::BigInt::fromInt64(
1052+
runtime, static_cast<int64_t>(bigIntObj->longValue()));
1053+
} else if (bits <= 64 && bigIntObj->signum() >= 0) {
1054+
// Unsigned 64-bit value: extract low 64 bits via longValue()
1055+
// and reinterpret as uint64_t.
1056+
returnValue = jsi::BigInt::fromUint64(
1057+
runtime, static_cast<uint64_t>(bigIntObj->longValue()));
1058+
} else {
1059+
throw jsi::JSError(
1060+
runtime,
1061+
"BigInt value cannot be losslessly represented as int64_t or uint64_t");
1062+
}
1063+
}
1064+
1065+
TMPL::syncMethodCallReturnConversionEnd(moduleName, methodName);
1066+
TMPL::syncMethodCallEnd(moduleName, methodName);
1067+
return returnValue;
1068+
}
9661069
default:
9671070
throw std::runtime_error(
9681071
"Unable to find method module: " + methodNameStr + "(" +

packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/SampleTurboModule.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import com.facebook.react.bridge.WritableNativeMap
2727
import com.facebook.react.module.annotations.ReactModule
2828
import com.facebook.react.turbomodule.core.interfaces.BindingsInstallerHolder
2929
import com.facebook.react.turbomodule.core.interfaces.TurboModuleWithJSIBindings
30+
import java.math.BigInteger
3031
import java.util.UUID
3132

3233
@DoNotStrip
@@ -225,6 +226,12 @@ public class SampleTurboModule(private val context: ReactApplicationContext) :
225226
assert(false) { "Intentional assert from JVM promiseAssert" }
226227
}
227228

229+
@DoNotStrip
230+
override fun getBigInt(arg: BigInteger): BigInteger {
231+
log("getBigInt", arg, arg)
232+
return arg
233+
}
234+
228235
@DoNotStrip
229236
@Suppress("unused")
230237
override fun getImageUrl(promise: Promise) {

packages/react-native/ReactCommon/react/nativemodule/samples/platform/ios/ReactCommon/RCTSampleTurboModule.mm

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,11 @@ - (void)installJSIBindingsWithRuntime:(facebook::jsi::Runtime &)runtime
210210
RCTAssert(false, @"Intentional assert from ObjC promiseAssert");
211211
}
212212

213+
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getBigInt : (NSNumber *)arg)
214+
{
215+
return arg;
216+
}
217+
213218
@end
214219

215220
Class _Nonnull RCTSampleTurboModuleCls(void)

packages/react-native/src/private/specs_DEPRECATED/modules/NativeSampleTurboModule.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@ export interface Spec extends TurboModule {
4343
};
4444
+voidFunc: () => void;
4545
+getBool: (arg: boolean) => boolean;
46-
+getEnum?: (arg: EnumInt) => EnumInt;
46+
+getEnum: (arg: EnumInt) => EnumInt;
4747
+getNumber: (arg: number) => number;
48+
+getBigInt: (arg: bigint) => bigint;
4849
+getString: (arg: string) => string;
4950
+getArray: (arg: Array<any>) => Array<any>;
5051
+getObject: (arg: Object) => Object;

packages/rn-tester/js/examples/TurboModule/SampleTurboModuleExample.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ type Examples =
4141
| 'getStrEnum'
4242
| 'getMap'
4343
| 'getNumber'
44+
| 'getBigInt'
4445
| 'getObject'
4546
| 'getSet'
4647
| 'getString'
@@ -94,6 +95,8 @@ class SampleTurboModuleExample extends React.Component<{}, State> {
9495
? NativeSampleTurboModule.getEnum(EnumInt.A)
9596
: null,
9697
getNumber: () => NativeSampleTurboModule.getNumber(99.95),
98+
getBigInt: () =>
99+
NativeSampleTurboModule?.getBigInt(BigInt('9223372036854775807')),
97100
getString: () => NativeSampleTurboModule.getString('Hello'),
98101
getArray: () =>
99102
NativeSampleTurboModule.getArray([
@@ -195,7 +198,9 @@ class SampleTurboModuleExample extends React.Component<{}, State> {
195198
return (
196199
<View style={styles.result}>
197200
<RNTesterText style={[styles.value]}>
198-
{JSON.stringify(result.value)}
201+
{typeof result.value === 'bigint'
202+
? result.value.toString()
203+
: JSON.stringify(result.value)}
199204
</RNTesterText>
200205
<RNTesterText style={[styles.type]}>{result.type}</RNTesterText>
201206
</View>

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2854,6 +2854,7 @@ protocol NativeSampleTurboModuleSpec : public NSObjectRCTBridgeModule, public RC
28542854
public virtual NSDictionary* getObjectThrows:(NSDictionary* arg);
28552855
public virtual NSDictionary* getUnsafeObject:(NSDictionary* arg);
28562856
public virtual NSDictionary* getValue:y:z:(double x, NSString* y, NSDictionary* z);
2857+
public virtual NSNumber* getBigInt:(NSNumber* arg);
28572858
public virtual NSNumber* getBool:(BOOL arg);
28582859
public virtual NSNumber* getEnum:(double arg);
28592860
public virtual NSNumber* getNumber:(double arg);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2854,6 +2854,7 @@ protocol NativeSampleTurboModuleSpec : public NSObjectRCTBridgeModule, public RC
28542854
public virtual NSDictionary* getObjectThrows:(NSDictionary* arg);
28552855
public virtual NSDictionary* getUnsafeObject:(NSDictionary* arg);
28562856
public virtual NSDictionary* getValue:y:z:(double x, NSString* y, NSDictionary* z);
2857+
public virtual NSNumber* getBigInt:(NSNumber* arg);
28572858
public virtual NSNumber* getBool:(BOOL arg);
28582859
public virtual NSNumber* getEnum:(double arg);
28592860
public virtual NSNumber* getNumber:(double arg);

0 commit comments

Comments
 (0)