mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Add codegen for C++ TurboModule automatic type conversions
Summary: This adds the *option* for C++ TurboModules to use a `*CxxSpec<T>` base class that extends the existing (and unchanged) corresponding `*CxxSpecJSI` base class with code-generated methods that use `bridging::calFromJs` to safely convert types between JSI and C++. If a type conversion cannot be made, then it will fail to compile. Changelog: [General][Added] - Automatic type conversions for C++ TurboModules Reviewed By: christophpurrer Differential Revision: D34780512 fbshipit-source-id: 58b34533c40652db8e3aea43804ceb73bcbe97a5
This commit is contained in:
committed by
Facebook GitHub Bot
parent
6697b7bb90
commit
31f0796237
@@ -15,6 +15,7 @@ import type {
|
||||
SchemaType,
|
||||
NativeModuleTypeAnnotation,
|
||||
NativeModuleFunctionTypeAnnotation,
|
||||
NativeModulePropertyShape,
|
||||
} from '../../CodegenSchema';
|
||||
|
||||
import type {AliasResolver} from './Utils';
|
||||
@@ -27,21 +28,58 @@ type FilesOutput = Map<string, string>;
|
||||
const ModuleClassDeclarationTemplate = ({
|
||||
hasteModuleName,
|
||||
moduleProperties,
|
||||
}: $ReadOnly<{hasteModuleName: string, moduleProperties: string}>) => {
|
||||
}: $ReadOnly<{hasteModuleName: string, moduleProperties: string[]}>) => {
|
||||
return `class JSI_EXPORT ${hasteModuleName}CxxSpecJSI : public TurboModule {
|
||||
protected:
|
||||
${hasteModuleName}CxxSpecJSI(std::shared_ptr<CallInvoker> jsInvoker);
|
||||
|
||||
public:
|
||||
${indent(moduleProperties, 2)}
|
||||
${indent(moduleProperties.join('\n'), 2)}
|
||||
|
||||
};`;
|
||||
};
|
||||
|
||||
const ModuleSpecClassDeclarationTemplate = ({
|
||||
hasteModuleName,
|
||||
moduleName,
|
||||
moduleProperties,
|
||||
}: $ReadOnly<{
|
||||
hasteModuleName: string,
|
||||
moduleName: string,
|
||||
moduleProperties: string[],
|
||||
}>) => {
|
||||
return `template <typename T>
|
||||
class JSI_EXPORT ${hasteModuleName}CxxSpec : public TurboModule {
|
||||
public:
|
||||
jsi::Value get(jsi::Runtime &rt, const jsi::PropNameID &propName) override {
|
||||
return delegate_.get(rt, propName);
|
||||
}
|
||||
|
||||
protected:
|
||||
${hasteModuleName}CxxSpec(std::shared_ptr<CallInvoker> jsInvoker)
|
||||
: TurboModule("${moduleName}", jsInvoker),
|
||||
delegate_(static_cast<T*>(this), jsInvoker) {}
|
||||
|
||||
private:
|
||||
class Delegate : public ${hasteModuleName}CxxSpecJSI {
|
||||
public:
|
||||
Delegate(T *instance, std::shared_ptr<CallInvoker> jsInvoker) :
|
||||
${hasteModuleName}CxxSpecJSI(std::move(jsInvoker)), instance_(instance) {}
|
||||
|
||||
${indent(moduleProperties.join('\n'), 4)}
|
||||
|
||||
private:
|
||||
T *instance_;
|
||||
};
|
||||
|
||||
Delegate delegate_;
|
||||
};`;
|
||||
};
|
||||
|
||||
const FileTemplate = ({
|
||||
modules,
|
||||
}: $ReadOnly<{
|
||||
modules: string,
|
||||
modules: string[],
|
||||
}>) => {
|
||||
return `/**
|
||||
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
|
||||
@@ -55,11 +93,12 @@ const FileTemplate = ({
|
||||
#pragma once
|
||||
|
||||
#include <ReactCommon/TurboModule.h>
|
||||
#include <react/bridging/Bridging.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
${modules}
|
||||
${modules.join('\n\n')}
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
||||
@@ -118,8 +157,52 @@ function translatePrimitiveJSTypeToCpp(
|
||||
}
|
||||
}
|
||||
|
||||
const propertyTemplate =
|
||||
'virtual ::_RETURN_VALUE_:: ::_PROPERTY_NAME_::(jsi::Runtime &rt::_ARGS_::) = 0;';
|
||||
function translatePropertyToCpp(
|
||||
prop: NativeModulePropertyShape,
|
||||
resolveAlias: AliasResolver,
|
||||
abstract: boolean = false,
|
||||
) {
|
||||
const [propTypeAnnotation] =
|
||||
unwrapNullable<NativeModuleFunctionTypeAnnotation>(prop.typeAnnotation);
|
||||
|
||||
const params = propTypeAnnotation.params.map(
|
||||
param => `std::move(${param.name})`,
|
||||
);
|
||||
|
||||
const paramTypes = propTypeAnnotation.params.map(param => {
|
||||
const translatedParam = translatePrimitiveJSTypeToCpp(
|
||||
param.typeAnnotation,
|
||||
typeName =>
|
||||
`Unsupported type for param "${param.name}" in ${prop.name}. Found: ${typeName}`,
|
||||
resolveAlias,
|
||||
);
|
||||
return `${translatedParam} ${param.name}`;
|
||||
});
|
||||
|
||||
const returnType = translatePrimitiveJSTypeToCpp(
|
||||
propTypeAnnotation.returnTypeAnnotation,
|
||||
typeName => `Unsupported return type for ${prop.name}. Found: ${typeName}`,
|
||||
resolveAlias,
|
||||
);
|
||||
|
||||
// The first param will always be the runtime reference.
|
||||
paramTypes.unshift('jsi::Runtime &rt');
|
||||
|
||||
const method = `${returnType} ${prop.name}(${paramTypes.join(', ')})`;
|
||||
|
||||
if (abstract) {
|
||||
return `virtual ${method} = 0;`;
|
||||
}
|
||||
|
||||
return `${method} override {
|
||||
static_assert(
|
||||
bridging::getParameterCount(&T::${prop.name}) == ${paramTypes.length},
|
||||
"Expected ${prop.name}(...) to have ${paramTypes.length} parameters");
|
||||
|
||||
return bridging::callFromJs<${returnType}>(
|
||||
rt, &T::${prop.name}, jsInvoker_, ${['instance_', ...params].join(', ')});
|
||||
}`;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
generate(
|
||||
@@ -130,55 +213,30 @@ module.exports = {
|
||||
): FilesOutput {
|
||||
const nativeModules = getModules(schema);
|
||||
|
||||
const modules = Object.keys(nativeModules)
|
||||
.map(hasteModuleName => {
|
||||
const {
|
||||
aliases,
|
||||
spec: {properties},
|
||||
} = nativeModules[hasteModuleName];
|
||||
const resolveAlias = createAliasResolver(aliases);
|
||||
const modules = Object.keys(nativeModules).flatMap(hasteModuleName => {
|
||||
const {
|
||||
aliases,
|
||||
spec: {properties},
|
||||
moduleNames: [moduleName],
|
||||
} = nativeModules[hasteModuleName];
|
||||
const resolveAlias = createAliasResolver(aliases);
|
||||
|
||||
const traversedProperties = properties
|
||||
.map(prop => {
|
||||
const [propTypeAnnotation] =
|
||||
unwrapNullable<NativeModuleFunctionTypeAnnotation>(
|
||||
prop.typeAnnotation,
|
||||
);
|
||||
const traversedArgs = propTypeAnnotation.params
|
||||
.map(param => {
|
||||
const translatedParam = translatePrimitiveJSTypeToCpp(
|
||||
param.typeAnnotation,
|
||||
typeName =>
|
||||
`Unsupported type for param "${param.name}" in ${prop.name}. Found: ${typeName}`,
|
||||
resolveAlias,
|
||||
);
|
||||
return `${translatedParam} ${param.name}`;
|
||||
})
|
||||
.join(', ');
|
||||
return propertyTemplate
|
||||
.replace('::_PROPERTY_NAME_::', prop.name)
|
||||
.replace(
|
||||
'::_RETURN_VALUE_::',
|
||||
translatePrimitiveJSTypeToCpp(
|
||||
propTypeAnnotation.returnTypeAnnotation,
|
||||
typeName =>
|
||||
`Unsupported return type for ${prop.name}. Found: ${typeName}`,
|
||||
resolveAlias,
|
||||
),
|
||||
)
|
||||
.replace(
|
||||
'::_ARGS_::',
|
||||
traversedArgs === '' ? '' : ', ' + traversedArgs,
|
||||
);
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
return ModuleClassDeclarationTemplate({
|
||||
return [
|
||||
ModuleClassDeclarationTemplate({
|
||||
hasteModuleName,
|
||||
moduleProperties: traversedProperties,
|
||||
});
|
||||
})
|
||||
.join('\n');
|
||||
moduleProperties: properties.map(prop =>
|
||||
translatePropertyToCpp(prop, resolveAlias, true),
|
||||
),
|
||||
}),
|
||||
ModuleSpecClassDeclarationTemplate({
|
||||
hasteModuleName,
|
||||
moduleName,
|
||||
moduleProperties: properties.map(prop =>
|
||||
translatePropertyToCpp(prop, resolveAlias),
|
||||
),
|
||||
}),
|
||||
];
|
||||
});
|
||||
|
||||
const fileName = `${libraryName}JSI.h`;
|
||||
const replacedTemplate = FileTemplate({modules});
|
||||
|
||||
Reference in New Issue
Block a user