Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,7 @@ import type {AliasResolver} from './Utils';
const {unwrapNullable} = require('../../parsers/parsers-commons');
const {wrapOptional} = require('../TypeUtils/Java');
const {parseValidUnionType, toPascalCase} = require('../Utils');
const {
createAliasResolver,
getModules,
throwIfUnsupportedPromiseArrayBuffer,
} = require('./Utils');
const {createAliasResolver, getModules} = require('./Utils');

type FilesOutput = Map<string, string>;

Expand Down Expand Up @@ -590,11 +586,6 @@ module.exports = {
method.typeAnnotation,
);

throwIfUnsupportedPromiseArrayBuffer(
method.name,
methodTypeAnnotation.returnTypeAnnotation,
);

// Handle return type
const translatedReturnType = translateFunctionReturnTypeToJavaType(
methodTypeAnnotation.returnTypeAnnotation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,7 @@ import type {AliasResolver} from './Utils';

const {unwrapNullable} = require('../../parsers/parsers-commons');
const {parseValidUnionType} = require('../Utils');
const {
createAliasResolver,
getModules,
throwIfUnsupportedPromiseArrayBuffer,
} = require('./Utils');
const {createAliasResolver, getModules} = require('./Utils');

type FilesOutput = Map<string, string>;

Expand All @@ -47,15 +43,20 @@ const HostFunctionTemplate = ({
propertyName,
jniSignature,
jsReturnType,
promiseResolveSupportsArrayBuffer,
}: Readonly<{
hasteModuleName: string,
propertyName: string,
jniSignature: string,
jsReturnType: JSReturnType,
promiseResolveSupportsArrayBuffer: boolean,
}>) => {
const promiseResolveSupportsArrayBufferArg = `, ${
promiseResolveSupportsArrayBuffer ? 'true' : 'false'
}`;
return `static facebook::jsi::Value __hostFunction_${hasteModuleName}SpecJSI_${propertyName}(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, ${jsReturnType}, "${propertyName}", "${jniSignature}", args, count, cachedMethodId);
return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, ${jsReturnType}, "${propertyName}", "${jniSignature}", args, count, cachedMethodId${promiseResolveSupportsArrayBufferArg});
}`;
};

Expand Down Expand Up @@ -406,6 +407,23 @@ function translateReturnTypeToJniType(
}
}

function doesPromiseResolveSupportArrayBuffer(
nullableReturnTypeAnnotation: Nullable<NativeModuleReturnTypeAnnotation>,
): boolean {
const [returnTypeAnnotation] =
unwrapNullable<NativeModuleReturnTypeAnnotation>(
nullableReturnTypeAnnotation,
);
if (returnTypeAnnotation.type !== 'PromiseTypeAnnotation') {
return false;
}

let elementType = returnTypeAnnotation.elementType;
[elementType] = unwrapNullable(elementType);

return elementType.type === 'ArrayBufferTypeAnnotation';
}

function translateMethodTypeToJniSignature(
property: NativeModulePropertyShape,
resolveAlias: AliasResolver,
Expand Down Expand Up @@ -453,8 +471,6 @@ function translateMethodForImplementation(
unwrapNullable<NativeModuleFunctionTypeAnnotation>(property.typeAnnotation);
const {returnTypeAnnotation} = propertyTypeAnnotation;

throwIfUnsupportedPromiseArrayBuffer(property.name, returnTypeAnnotation);

if (
property.name === 'getConstants' &&
returnTypeAnnotation.type === 'ObjectTypeAnnotation' &&
Expand All @@ -468,6 +484,8 @@ function translateMethodForImplementation(
propertyName: property.name,
jniSignature: translateMethodTypeToJniSignature(property, resolveAlias),
jsReturnType: translateReturnTypeToKind(returnTypeAnnotation, resolveAlias),
promiseResolveSupportsArrayBuffer:
doesPromiseResolveSupportArrayBuffer(returnTypeAnnotation),
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ const {
} = require('../../../parsers/parsers-commons');
const {wrapOptional} = require('../../TypeUtils/Objective-C');
const {capitalize, parseValidUnionType} = require('../../Utils');
const {throwIfUnsupportedPromiseArrayBuffer} = require('../Utils');
const {getNamespacedStructName} = require('./Utils');
const invariant = require('invariant');

Expand Down Expand Up @@ -104,11 +103,6 @@ function serializeMethod(
}
});

throwIfUnsupportedPromiseArrayBuffer(
methodName,
propertyTypeAnnotation.returnTypeAnnotation,
);

// Unwrap returnTypeAnnotation, so we check if the return type is Promise
// TODO(T76719514): Disallow nullable PromiseTypeAnnotations
const [returnTypeAnnotation] = unwrapNullable(
Expand Down
42 changes: 0 additions & 42 deletions packages/react-native-codegen/src/generators/modules/Utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import type {
NativeModuleAliasMap,
NativeModuleObjectTypeAnnotation,
NativeModuleReturnTypeAnnotation,
NativeModuleSchema,
NativeModuleTypeAnnotation,
Nullable,
Expand Down Expand Up @@ -78,50 +77,9 @@ function isArrayRecursiveMember(
);
}

// Platform-native (Java/Kotlin and ObjC) TurboModules copy ArrayBuffer
// arguments and return ArrayBuffers zero-copy from synchronous methods, but
// `Promise<ArrayBuffer>` is not part of their contract.
//
// On Android it cannot work: the resolve path serializes through
// folly::dynamic, which cannot carry raw bytes. On iOS the resolve path is a
// direct ObjC->jsi conversion that would in fact produce an ArrayBuffer for an
// NSMutableData, so the limitation there is not technical — the guard is
// applied to ObjC as well to keep one cross-platform contract, so a spec that
// compiles for iOS cannot fail to build for Android.
//
// Reject `Promise<ArrayBuffer>` at codegen time for both native platforms so
// the unsupported case surfaces as a build error rather than a runtime failure
// or a silent iOS/Android divergence.
function throwIfUnsupportedPromiseArrayBuffer(
methodName: string,
nullableReturnTypeAnnotation: Nullable<NativeModuleReturnTypeAnnotation>,
): void {
const [returnTypeAnnotation] =
unwrapNullable<NativeModuleReturnTypeAnnotation>(
nullableReturnTypeAnnotation,
);
if (returnTypeAnnotation.type !== 'PromiseTypeAnnotation') {
return;
}
let elementType = returnTypeAnnotation.elementType;
if (elementType.type === 'NullableTypeAnnotation') {
elementType = elementType.typeAnnotation;
}
if (elementType.type === 'ArrayBufferTypeAnnotation') {
throw new Error(
`Unsupported return type for method "${methodName}": Promise<ArrayBuffer> is not ` +
'supported for Android (Java/Kotlin) or iOS (ObjC) TurboModules. Use a C++ ' +
'(Cxx) TurboModule, return the ArrayBuffer from a synchronous method, or resolve ' +
'the Promise with a different type. ArrayBuffer is still supported as a method ' +
'argument and as a synchronous return value on all platforms.',
);
}
}

module.exports = {
createAliasResolver,
getModules,
isDirectRecursiveMember,
isArrayRecursiveMember,
throwIfUnsupportedPromiseArrayBuffer,
};
Original file line number Diff line number Diff line change
Expand Up @@ -2661,25 +2661,6 @@ const ARRAY_BUFFER_NATIVE_MODULE: SchemaType = {
],
},
},
],
},
moduleName: 'SampleTurboModule',
},
},
};

// Promise<ArrayBuffer> is only supported by C++ (Cxx) TurboModules (see
// throwIfUnsupportedPromiseArrayBuffer), so this fixture is excluded on both
// Android and iOS. It keeps C++ codegen coverage for the async-return case.
const ARRAY_BUFFER_PROMISE_NATIVE_MODULE: SchemaType = {
modules: {
NativeSampleTurboModule: {
type: 'NativeModule',
aliasMap: {},
enumMap: {},
spec: {
eventEmitters: [],
methods: [
{
name: 'promiseArrayBuffer',
optional: false,
Expand All @@ -2694,10 +2675,26 @@ const ARRAY_BUFFER_PROMISE_NATIVE_MODULE: SchemaType = {
params: [],
},
},
{
name: 'promiseNullableArrayBuffer',
optional: false,
typeAnnotation: {
type: 'FunctionTypeAnnotation',
returnTypeAnnotation: {
type: 'PromiseTypeAnnotation',
elementType: {
type: 'NullableTypeAnnotation',
typeAnnotation: {
type: 'ArrayBufferTypeAnnotation',
},
},
},
params: [],
},
},
],
},
moduleName: 'SampleTurboModule',
excludedPlatforms: ['android', 'iOS'],
},
},
};
Expand Down Expand Up @@ -2896,7 +2893,6 @@ const STRING_LITERALS: SchemaType = {

module.exports = {
array_buffer_native_module: ARRAY_BUFFER_NATIVE_MODULE,
array_buffer_promise_native_module: ARRAY_BUFFER_PROMISE_NATIVE_MODULE,
complex_objects: COMPLEX_OBJECTS,
two_modules_different_files: TWO_MODULES_DIFFERENT_FILES,
empty_native_modules: EMPTY_NATIVE_MODULES,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@

'use strict';

import type {SchemaType} from '../../../CodegenSchema';

const fixtures = require('../__test_fixtures__/fixtures.js');
const generator = require('../GenerateModuleObjCpp');

Expand All @@ -33,42 +31,4 @@ describe('GenerateModuleHObjCpp', () => {
).toMatchSnapshot();
});
});

it('throws for a method returning Promise<ArrayBuffer> (unsupported on iOS)', () => {
const schema: SchemaType = {
modules: {
NativeSampleTurboModule: {
type: 'NativeModule',
aliasMap: {},
enumMap: {},
spec: {
eventEmitters: [],
methods: [
{
name: 'getAsyncBuffer',
optional: false,
typeAnnotation: {
type: 'FunctionTypeAnnotation',
returnTypeAnnotation: {
type: 'PromiseTypeAnnotation',
elementType: {type: 'ArrayBufferTypeAnnotation'},
},
params: [],
},
},
],
},
moduleName: 'SampleTurboModule',
},
},
};
expect(() =>
generator.generate(
'array_buffer_promise_throws',
schema,
'com.facebook.fbreact.specs',
false,
),
).toThrow(/Promise<ArrayBuffer> is not supported/);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@

'use strict';

import type {SchemaType} from '../../../CodegenSchema';

const fixtures = require('../__test_fixtures__/fixtures.js');
const generator = require('../GenerateModuleJavaSpec.js');

Expand All @@ -31,37 +29,4 @@ describe('GenerateModuleJavaSpec', () => {
).toMatchSnapshot();
});
});

it('throws for a method returning Promise<ArrayBuffer> (unsupported on Android)', () => {
const schema: SchemaType = {
modules: {
NativeSampleTurboModule: {
type: 'NativeModule',
aliasMap: {},
enumMap: {},
spec: {
eventEmitters: [],
methods: [
{
name: 'getAsyncBuffer',
optional: false,
typeAnnotation: {
type: 'FunctionTypeAnnotation',
returnTypeAnnotation: {
type: 'PromiseTypeAnnotation',
elementType: {type: 'ArrayBufferTypeAnnotation'},
},
params: [],
},
},
],
},
moduleName: 'SampleTurboModule',
},
},
};
expect(() =>
generator.generate('array_buffer_promise_throws', schema),
).toThrow(/Promise<ArrayBuffer> is not supported/);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@

'use strict';

import type {SchemaType} from '../../../CodegenSchema';

const fixtures = require('../__test_fixtures__/fixtures.js');
const generator = require('../GenerateModuleJniCpp.js');

Expand All @@ -31,41 +29,4 @@ describe('GenerateModuleJniCpp', () => {
).toMatchSnapshot();
});
});

it('throws for a method returning Promise<ArrayBuffer> (unsupported on Android)', () => {
const schema: SchemaType = {
modules: {
NativeSampleTurboModule: {
type: 'NativeModule',
aliasMap: {},
enumMap: {},
spec: {
eventEmitters: [],
methods: [
{
name: 'getAsyncBuffer',
optional: false,
typeAnnotation: {
type: 'FunctionTypeAnnotation',
returnTypeAnnotation: {
type: 'PromiseTypeAnnotation',
elementType: {type: 'ArrayBufferTypeAnnotation'},
},
params: [],
},
},
],
},
moduleName: 'SampleTurboModule',
},
},
};
expect(() =>
generator.generate(
'array_buffer_promise_throws',
schema,
'com.facebook.fbreact.specs',
),
).toThrow(/Promise<ArrayBuffer> is not supported/);
});
});
Loading
Loading