Skip to content

Commit 3b4953a

Browse files
committed
feat: support Promise<ArrayBuffer> in Java and ObjC TurboModules
1 parent 4433cdb commit 3b4953a

26 files changed

Lines changed: 447 additions & 466 deletions

File tree

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

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,7 @@ import type {AliasResolver} from './Utils';
2525
const {unwrapNullable} = require('../../parsers/parsers-commons');
2626
const {wrapOptional} = require('../TypeUtils/Java');
2727
const {parseValidUnionType, toPascalCase} = require('../Utils');
28-
const {
29-
createAliasResolver,
30-
getModules,
31-
throwIfUnsupportedPromiseArrayBuffer,
32-
} = require('./Utils');
28+
const {createAliasResolver, getModules} = require('./Utils');
3329

3430
type FilesOutput = Map<string, string>;
3531

@@ -590,11 +586,6 @@ module.exports = {
590586
method.typeAnnotation,
591587
);
592588

593-
throwIfUnsupportedPromiseArrayBuffer(
594-
method.name,
595-
methodTypeAnnotation.returnTypeAnnotation,
596-
);
597-
598589
// Handle return type
599590
const translatedReturnType = translateFunctionReturnTypeToJavaType(
600591
methodTypeAnnotation.returnTypeAnnotation,

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

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,7 @@ import type {AliasResolver} from './Utils';
2424

2525
const {unwrapNullable} = require('../../parsers/parsers-commons');
2626
const {parseValidUnionType} = require('../Utils');
27-
const {
28-
createAliasResolver,
29-
getModules,
30-
throwIfUnsupportedPromiseArrayBuffer,
31-
} = require('./Utils');
27+
const {createAliasResolver, getModules} = require('./Utils');
3228

3329
type FilesOutput = Map<string, string>;
3430

@@ -47,15 +43,20 @@ const HostFunctionTemplate = ({
4743
propertyName,
4844
jniSignature,
4945
jsReturnType,
46+
promiseResolveSupportsArrayBuffer,
5047
}: Readonly<{
5148
hasteModuleName: string,
5249
propertyName: string,
5350
jniSignature: string,
5451
jsReturnType: JSReturnType,
52+
promiseResolveSupportsArrayBuffer: boolean,
5553
}>) => {
54+
const promiseResolveSupportsArrayBufferArg = `, ${
55+
promiseResolveSupportsArrayBuffer ? 'true' : 'false'
56+
}`;
5657
return `static facebook::jsi::Value __hostFunction_${hasteModuleName}SpecJSI_${propertyName}(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
5758
static jmethodID cachedMethodId = nullptr;
58-
return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, ${jsReturnType}, "${propertyName}", "${jniSignature}", args, count, cachedMethodId);
59+
return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, ${jsReturnType}, "${propertyName}", "${jniSignature}", args, count, cachedMethodId${promiseResolveSupportsArrayBufferArg});
5960
}`;
6061
};
6162

@@ -406,6 +407,19 @@ function translateReturnTypeToJniType(
406407
}
407408
}
408409

410+
function promiseResolveSupportsArrayBuffer(
411+
returnTypeAnnotation: NativeModuleReturnTypeAnnotation,
412+
): boolean {
413+
if (returnTypeAnnotation.type !== 'PromiseTypeAnnotation') {
414+
return false;
415+
}
416+
417+
let elementType = returnTypeAnnotation.elementType;
418+
[elementType] = unwrapNullable(elementType);
419+
420+
return elementType.type === 'ArrayBufferTypeAnnotation';
421+
}
422+
409423
function translateMethodTypeToJniSignature(
410424
property: NativeModulePropertyShape,
411425
resolveAlias: AliasResolver,
@@ -453,8 +467,6 @@ function translateMethodForImplementation(
453467
unwrapNullable<NativeModuleFunctionTypeAnnotation>(property.typeAnnotation);
454468
const {returnTypeAnnotation} = propertyTypeAnnotation;
455469

456-
throwIfUnsupportedPromiseArrayBuffer(property.name, returnTypeAnnotation);
457-
458470
if (
459471
property.name === 'getConstants' &&
460472
returnTypeAnnotation.type === 'ObjectTypeAnnotation' &&
@@ -468,6 +480,9 @@ function translateMethodForImplementation(
468480
propertyName: property.name,
469481
jniSignature: translateMethodTypeToJniSignature(property, resolveAlias),
470482
jsReturnType: translateReturnTypeToKind(returnTypeAnnotation, resolveAlias),
483+
promiseResolveSupportsArrayBuffer: promiseResolveSupportsArrayBuffer(
484+
returnTypeAnnotation,
485+
),
471486
});
472487
}
473488

packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/serializeMethod.js

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ const {
2626
} = require('../../../parsers/parsers-commons');
2727
const {wrapOptional} = require('../../TypeUtils/Objective-C');
2828
const {capitalize, parseValidUnionType} = require('../../Utils');
29-
const {throwIfUnsupportedPromiseArrayBuffer} = require('../Utils');
3029
const {getNamespacedStructName} = require('./Utils');
3130
const invariant = require('invariant');
3231

@@ -104,11 +103,6 @@ function serializeMethod(
104103
}
105104
});
106105

107-
throwIfUnsupportedPromiseArrayBuffer(
108-
methodName,
109-
propertyTypeAnnotation.returnTypeAnnotation,
110-
);
111-
112106
// Unwrap returnTypeAnnotation, so we check if the return type is Promise
113107
// TODO(T76719514): Disallow nullable PromiseTypeAnnotations
114108
const [returnTypeAnnotation] = unwrapNullable(

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

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import type {
1414
NativeModuleAliasMap,
1515
NativeModuleObjectTypeAnnotation,
16-
NativeModuleReturnTypeAnnotation,
1716
NativeModuleSchema,
1817
NativeModuleTypeAnnotation,
1918
Nullable,
@@ -78,50 +77,9 @@ function isArrayRecursiveMember(
7877
);
7978
}
8079

81-
// Platform-native (Java/Kotlin and ObjC) TurboModules copy ArrayBuffer
82-
// arguments and return ArrayBuffers zero-copy from synchronous methods, but
83-
// `Promise<ArrayBuffer>` is not part of their contract.
84-
//
85-
// On Android it cannot work: the resolve path serializes through
86-
// folly::dynamic, which cannot carry raw bytes. On iOS the resolve path is a
87-
// direct ObjC->jsi conversion that would in fact produce an ArrayBuffer for an
88-
// NSMutableData, so the limitation there is not technical — the guard is
89-
// applied to ObjC as well to keep one cross-platform contract, so a spec that
90-
// compiles for iOS cannot fail to build for Android.
91-
//
92-
// Reject `Promise<ArrayBuffer>` at codegen time for both native platforms so
93-
// the unsupported case surfaces as a build error rather than a runtime failure
94-
// or a silent iOS/Android divergence.
95-
function throwIfUnsupportedPromiseArrayBuffer(
96-
methodName: string,
97-
nullableReturnTypeAnnotation: Nullable<NativeModuleReturnTypeAnnotation>,
98-
): void {
99-
const [returnTypeAnnotation] =
100-
unwrapNullable<NativeModuleReturnTypeAnnotation>(
101-
nullableReturnTypeAnnotation,
102-
);
103-
if (returnTypeAnnotation.type !== 'PromiseTypeAnnotation') {
104-
return;
105-
}
106-
let elementType = returnTypeAnnotation.elementType;
107-
if (elementType.type === 'NullableTypeAnnotation') {
108-
elementType = elementType.typeAnnotation;
109-
}
110-
if (elementType.type === 'ArrayBufferTypeAnnotation') {
111-
throw new Error(
112-
`Unsupported return type for method "${methodName}": Promise<ArrayBuffer> is not ` +
113-
'supported for Android (Java/Kotlin) or iOS (ObjC) TurboModules. Use a C++ ' +
114-
'(Cxx) TurboModule, return the ArrayBuffer from a synchronous method, or resolve ' +
115-
'the Promise with a different type. ArrayBuffer is still supported as a method ' +
116-
'argument and as a synchronous return value on all platforms.',
117-
);
118-
}
119-
}
120-
12180
module.exports = {
12281
createAliasResolver,
12382
getModules,
12483
isDirectRecursiveMember,
12584
isArrayRecursiveMember,
126-
throwIfUnsupportedPromiseArrayBuffer,
12785
};

packages/react-native-codegen/src/generators/modules/__test_fixtures__/fixtures.js

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2661,25 +2661,6 @@ const ARRAY_BUFFER_NATIVE_MODULE: SchemaType = {
26612661
],
26622662
},
26632663
},
2664-
],
2665-
},
2666-
moduleName: 'SampleTurboModule',
2667-
},
2668-
},
2669-
};
2670-
2671-
// Promise<ArrayBuffer> is only supported by C++ (Cxx) TurboModules (see
2672-
// throwIfUnsupportedPromiseArrayBuffer), so this fixture is excluded on both
2673-
// Android and iOS. It keeps C++ codegen coverage for the async-return case.
2674-
const ARRAY_BUFFER_PROMISE_NATIVE_MODULE: SchemaType = {
2675-
modules: {
2676-
NativeSampleTurboModule: {
2677-
type: 'NativeModule',
2678-
aliasMap: {},
2679-
enumMap: {},
2680-
spec: {
2681-
eventEmitters: [],
2682-
methods: [
26832664
{
26842665
name: 'promiseArrayBuffer',
26852666
optional: false,
@@ -2694,10 +2675,26 @@ const ARRAY_BUFFER_PROMISE_NATIVE_MODULE: SchemaType = {
26942675
params: [],
26952676
},
26962677
},
2678+
{
2679+
name: 'promiseNullableArrayBuffer',
2680+
optional: false,
2681+
typeAnnotation: {
2682+
type: 'FunctionTypeAnnotation',
2683+
returnTypeAnnotation: {
2684+
type: 'PromiseTypeAnnotation',
2685+
elementType: {
2686+
type: 'NullableTypeAnnotation',
2687+
typeAnnotation: {
2688+
type: 'ArrayBufferTypeAnnotation',
2689+
},
2690+
},
2691+
},
2692+
params: [],
2693+
},
2694+
},
26972695
],
26982696
},
26992697
moduleName: 'SampleTurboModule',
2700-
excludedPlatforms: ['android', 'iOS'],
27012698
},
27022699
},
27032700
};
@@ -2896,7 +2893,6 @@ const STRING_LITERALS: SchemaType = {
28962893

28972894
module.exports = {
28982895
array_buffer_native_module: ARRAY_BUFFER_NATIVE_MODULE,
2899-
array_buffer_promise_native_module: ARRAY_BUFFER_PROMISE_NATIVE_MODULE,
29002896
complex_objects: COMPLEX_OBJECTS,
29012897
two_modules_different_files: TWO_MODULES_DIFFERENT_FILES,
29022898
empty_native_modules: EMPTY_NATIVE_MODULES,

packages/react-native-codegen/src/generators/modules/__tests__/GenerateModuleHObjCpp-test.js

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010

1111
'use strict';
1212

13-
import type {SchemaType} from '../../../CodegenSchema';
14-
1513
const fixtures = require('../__test_fixtures__/fixtures.js');
1614
const generator = require('../GenerateModuleObjCpp');
1715

@@ -33,42 +31,4 @@ describe('GenerateModuleHObjCpp', () => {
3331
).toMatchSnapshot();
3432
});
3533
});
36-
37-
it('throws for a method returning Promise<ArrayBuffer> (unsupported on iOS)', () => {
38-
const schema: SchemaType = {
39-
modules: {
40-
NativeSampleTurboModule: {
41-
type: 'NativeModule',
42-
aliasMap: {},
43-
enumMap: {},
44-
spec: {
45-
eventEmitters: [],
46-
methods: [
47-
{
48-
name: 'getAsyncBuffer',
49-
optional: false,
50-
typeAnnotation: {
51-
type: 'FunctionTypeAnnotation',
52-
returnTypeAnnotation: {
53-
type: 'PromiseTypeAnnotation',
54-
elementType: {type: 'ArrayBufferTypeAnnotation'},
55-
},
56-
params: [],
57-
},
58-
},
59-
],
60-
},
61-
moduleName: 'SampleTurboModule',
62-
},
63-
},
64-
};
65-
expect(() =>
66-
generator.generate(
67-
'array_buffer_promise_throws',
68-
schema,
69-
'com.facebook.fbreact.specs',
70-
false,
71-
),
72-
).toThrow(/Promise<ArrayBuffer> is not supported/);
73-
});
7434
});

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

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010

1111
'use strict';
1212

13-
import type {SchemaType} from '../../../CodegenSchema';
14-
1513
const fixtures = require('../__test_fixtures__/fixtures.js');
1614
const generator = require('../GenerateModuleJavaSpec.js');
1715

@@ -31,37 +29,4 @@ describe('GenerateModuleJavaSpec', () => {
3129
).toMatchSnapshot();
3230
});
3331
});
34-
35-
it('throws for a method returning Promise<ArrayBuffer> (unsupported on Android)', () => {
36-
const schema: SchemaType = {
37-
modules: {
38-
NativeSampleTurboModule: {
39-
type: 'NativeModule',
40-
aliasMap: {},
41-
enumMap: {},
42-
spec: {
43-
eventEmitters: [],
44-
methods: [
45-
{
46-
name: 'getAsyncBuffer',
47-
optional: false,
48-
typeAnnotation: {
49-
type: 'FunctionTypeAnnotation',
50-
returnTypeAnnotation: {
51-
type: 'PromiseTypeAnnotation',
52-
elementType: {type: 'ArrayBufferTypeAnnotation'},
53-
},
54-
params: [],
55-
},
56-
},
57-
],
58-
},
59-
moduleName: 'SampleTurboModule',
60-
},
61-
},
62-
};
63-
expect(() =>
64-
generator.generate('array_buffer_promise_throws', schema),
65-
).toThrow(/Promise<ArrayBuffer> is not supported/);
66-
});
6732
});

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

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010

1111
'use strict';
1212

13-
import type {SchemaType} from '../../../CodegenSchema';
14-
1513
const fixtures = require('../__test_fixtures__/fixtures.js');
1614
const generator = require('../GenerateModuleJniCpp.js');
1715

@@ -31,41 +29,4 @@ describe('GenerateModuleJniCpp', () => {
3129
).toMatchSnapshot();
3230
});
3331
});
34-
35-
it('throws for a method returning Promise<ArrayBuffer> (unsupported on Android)', () => {
36-
const schema: SchemaType = {
37-
modules: {
38-
NativeSampleTurboModule: {
39-
type: 'NativeModule',
40-
aliasMap: {},
41-
enumMap: {},
42-
spec: {
43-
eventEmitters: [],
44-
methods: [
45-
{
46-
name: 'getAsyncBuffer',
47-
optional: false,
48-
typeAnnotation: {
49-
type: 'FunctionTypeAnnotation',
50-
returnTypeAnnotation: {
51-
type: 'PromiseTypeAnnotation',
52-
elementType: {type: 'ArrayBufferTypeAnnotation'},
53-
},
54-
params: [],
55-
},
56-
},
57-
],
58-
},
59-
moduleName: 'SampleTurboModule',
60-
},
61-
},
62-
};
63-
expect(() =>
64-
generator.generate(
65-
'array_buffer_promise_throws',
66-
schema,
67-
'com.facebook.fbreact.specs',
68-
),
69-
).toThrow(/Promise<ArrayBuffer> is not supported/);
70-
});
7132
});

0 commit comments

Comments
 (0)