Skip to content

Commit 662caef

Browse files
christophpurrermeta-codesync[bot]
authored andcommitted
Add BigInt Support to Java Turbo Module
Summary: 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 ``` Differential Revision: D95232272
1 parent d5e5e7d commit 662caef

8 files changed

Lines changed: 135 additions & 21 deletions

File tree

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

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,8 @@ function translateEventEmitterTypeToJavaType(
156156
case 'Int32TypeAnnotation':
157157
return 'double';
158158
case 'BigIntTypeAnnotation':
159-
return 'long';
159+
imports.add('java.math.BigInteger');
160+
return 'BigInteger';
160161
case 'BooleanTypeAnnotation':
161162
case 'BooleanLiteralTypeAnnotation':
162163
return 'boolean';
@@ -270,11 +271,8 @@ function translateFunctionParamToJavaType(
270271
imports.add('com.facebook.react.bridge.Callback');
271272
return wrapOptional('Callback', isRequired);
272273
case 'BigIntTypeAnnotation':
273-
throw new Error(
274-
createErrorMessage(
275-
`${realTypeAnnotation.type} is not supported in Java TurboModules yet`,
276-
),
277-
);
274+
imports.add('java.math.BigInteger');
275+
return wrapOptional('BigInteger', isRequired);
278276
default:
279277
(realTypeAnnotation.type: 'MixedTypeAnnotation');
280278
throw new Error(createErrorMessage(realTypeAnnotation.type));
@@ -370,11 +368,8 @@ function translateFunctionReturnTypeToJavaType(
370368
imports.add('com.facebook.react.bridge.WritableArray');
371369
return wrapOptional('WritableArray', isRequired);
372370
case 'BigIntTypeAnnotation':
373-
throw new Error(
374-
createErrorMessage(
375-
`${realTypeAnnotation.type} is not supported in Java TurboModules yet`,
376-
),
377-
);
371+
imports.add('java.math.BigInteger');
372+
return wrapOptional('BigInteger', isRequired);
378373
default:
379374
(realTypeAnnotation.type: 'MixedTypeAnnotation');
380375
throw new Error(createErrorMessage(realTypeAnnotation.type));
@@ -458,7 +453,7 @@ function getFalsyReturnStatementFromReturnType(
458453
case 'ArrayTypeAnnotation':
459454
return 'return null;';
460455
case 'BigIntTypeAnnotation':
461-
throw new Error(createErrorMessage(realTypeAnnotation.type));
456+
return nullable ? 'return null;' : 'return BigInteger.ZERO;';
462457
default:
463458
(realTypeAnnotation.type: 'MixedTypeAnnotation');
464459
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: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,39 @@ struct JPromiseImpl : public jni::JavaClass<JPromiseImpl> {
169169
}
170170
};
171171

172+
struct JBigInteger : public jni::JavaClass<JBigInteger> {
173+
constexpr static auto kJavaDescriptor = "Ljava/math/BigInteger;";
174+
175+
static jni::local_ref<javaobject> valueOf(jlong val) {
176+
static const auto cls = javaClassStatic();
177+
static const auto method =
178+
cls->getStaticMethod<javaobject(jlong)>("valueOf");
179+
return method(cls, val);
180+
}
181+
182+
static jni::local_ref<javaobject> fromString(const std::string& str) {
183+
return newInstance(jni::make_jstring(str));
184+
}
185+
186+
jlong longValue() const {
187+
static const auto method =
188+
javaClassStatic()->getMethod<jlong()>("longValue");
189+
return method(self());
190+
}
191+
192+
jint bitLength() const {
193+
static const auto method =
194+
javaClassStatic()->getMethod<jint()>("bitLength");
195+
return method(self());
196+
}
197+
198+
std::string toStdString() const {
199+
static const auto method =
200+
javaClassStatic()->getMethod<jni::JString::javaobject()>("toString");
201+
return method(self())->toStdString();
202+
}
203+
};
204+
172205
// This is used for generating short exception strings.
173206
std::string stringifyJSIValue(const jsi::Value& v, jsi::Runtime* rt = nullptr) {
174207
if (v.isUndefined()) {
@@ -322,7 +355,7 @@ JNIArgs convertJSIArgsToJNIArgs(
322355
return obj;
323356
};
324357

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

328361
const jsi::Value* arg = &args[argIndex];
@@ -397,6 +430,22 @@ JNIArgs convertJSIArgsToJNIArgs(
397430
jarg->l = makeGlobalIfNecessary(
398431
jni::JBoolean::valueOf(static_cast<unsigned char>(arg->getBool()))
399432
.release());
433+
} else if (type == "Ljava/math/BigInteger;") {
434+
if (!arg->isBigInt()) {
435+
throw JavaTurboModuleArgumentConversionException(
436+
"bigint", argIndex, methodName, arg, &rt);
437+
}
438+
auto bigint = arg->getBigInt(rt);
439+
jni::local_ref<JBigInteger::javaobject> javaBigInt;
440+
if (bigint.isInt64(rt)) {
441+
// Fast path: value fits in int64
442+
javaBigInt = JBigInteger::valueOf(bigint.getInt64(rt));
443+
} else {
444+
// Slow path: marshal via decimal string
445+
auto str = bigint.toString(rt, 10);
446+
javaBigInt = JBigInteger::fromString(str.utf8(rt));
447+
}
448+
jarg->l = makeGlobalIfNecessary(javaBigInt.release());
400449
} else if (type == "Ljava/lang/String;") {
401450
if (!arg->isString()) {
402451
throw JavaTurboModuleArgumentConversionException(
@@ -963,6 +1012,39 @@ jsi::Value JavaTurboModule::invokeJavaMethod(
9631012
TMPL::asyncMethodCallEnd(moduleName, methodName);
9641013
return jsPromise;
9651014
}
1015+
case BigIntKind: {
1016+
auto returnObject =
1017+
env->CallObjectMethodA(instance, methodID, jargs.data());
1018+
checkJNIErrorForMethodCall();
1019+
1020+
TMPL::syncMethodCallExecutionEnd(moduleName, methodName);
1021+
TMPL::syncMethodCallReturnConversionStart(moduleName, methodName);
1022+
1023+
auto returnValue = jsi::Value::null();
1024+
if (returnObject != nullptr) {
1025+
auto bigIntObj = jni::adopt_local(
1026+
static_cast<JBigInteger::javaobject>(returnObject));
1027+
1028+
// bitLength() returns bits excluding sign bit.
1029+
// A value fits in int64_t when bitLength() <= 63.
1030+
if (bigIntObj->bitLength() <= 63) {
1031+
// Fast path
1032+
returnValue = jsi::BigInt::fromInt64(
1033+
runtime, static_cast<int64_t>(bigIntObj->longValue()));
1034+
} else {
1035+
// Slow path: string round-trip through JS BigInt() constructor
1036+
auto str = bigIntObj->toStdString();
1037+
jsi::Function bigIntCtor =
1038+
runtime.global().getPropertyAsFunction(runtime, "BigInt");
1039+
returnValue = bigIntCtor.call(
1040+
runtime, jsi::String::createFromUtf8(runtime, str));
1041+
}
1042+
}
1043+
1044+
TMPL::syncMethodCallReturnConversionEnd(moduleName, methodName);
1045+
TMPL::syncMethodCallEnd(moduleName, methodName);
1046+
return returnValue;
1047+
}
9661048
default:
9671049
throw std::runtime_error(
9681050
"Unable to find method module: " + methodNameStr + "(" +

packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/NativeSampleTurboModuleSpec.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.facebook.react.bridge.WritableMap;
2222
import com.facebook.react.common.build.ReactBuildConfig;
2323
import com.facebook.react.turbomodule.core.interfaces.TurboModule;
24+
import java.math.BigInteger;
2425
import java.util.Arrays;
2526
import java.util.HashSet;
2627
import java.util.Map;
@@ -162,6 +163,12 @@ public WritableMap getObjectAssert(ReadableMap arg) {
162163
@DoNotStrip
163164
public void promiseAssert(Promise promise) {}
164165

166+
@ReactMethod(isBlockingSynchronousMethod = true)
167+
@DoNotStrip
168+
public BigInteger getBigInt(BigInteger arg) {
169+
return BigInteger.ZERO;
170+
}
171+
165172
@ReactMethod
166173
@DoNotStrip
167174
public void getImageUrl(Promise promise) {}

packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/ReactCommon/SampleTurboModuleSpec.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,24 @@ __hostFunction_NativeSampleTurboModuleSpecJSI_promiseAssert(
310310
cachedMethodId);
311311
}
312312

313+
static facebook::jsi::Value
314+
__hostFunction_NativeSampleTurboModuleSpecJSI_getBigInt(
315+
facebook::jsi::Runtime& rt,
316+
TurboModule& turboModule,
317+
const facebook::jsi::Value* args,
318+
size_t count) {
319+
static jmethodID cachedMethodId = nullptr;
320+
return static_cast<JavaTurboModule&>(turboModule)
321+
.invokeJavaMethod(
322+
rt,
323+
BigIntKind,
324+
"getBigInt",
325+
"(Ljava/math/BigInteger;)Ljava/math/BigInteger;",
326+
args,
327+
count,
328+
cachedMethodId);
329+
}
330+
313331
static facebook::jsi::Value
314332
__hostFunction_NativeSampleTurboModuleSpecJSI_getImageUrl(
315333
facebook::jsi::Runtime& rt,
@@ -393,6 +411,9 @@ NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(
393411
methodMap_["getImageUrl"] = MethodMetadata{
394412
.argCount = 0,
395413
.invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getImageUrl};
414+
methodMap_["getBigInt"] = MethodMetadata{
415+
.argCount = 1,
416+
.invoker = __hostFunction_NativeSampleTurboModuleSpecJSI_getBigInt};
396417
eventEmitterMap_["onPress"] =
397418
std::make_shared<AsyncEventEmitter<folly::dynamic>>();
398419
eventEmitterMap_["onClick"] =

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/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>

0 commit comments

Comments
 (0)