mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
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
247 lines
6.6 KiB
JavaScript
247 lines
6.6 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow strict
|
|
* @format
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
import type {
|
|
Nullable,
|
|
SchemaType,
|
|
NativeModuleTypeAnnotation,
|
|
NativeModuleFunctionTypeAnnotation,
|
|
NativeModulePropertyShape,
|
|
} from '../../CodegenSchema';
|
|
|
|
import type {AliasResolver} from './Utils';
|
|
const {createAliasResolver, getModules} = require('./Utils');
|
|
const {indent} = require('../Utils');
|
|
const {unwrapNullable} = require('../../parsers/flow/modules/utils');
|
|
|
|
type FilesOutput = Map<string, string>;
|
|
|
|
const ModuleClassDeclarationTemplate = ({
|
|
hasteModuleName,
|
|
moduleProperties,
|
|
}: $ReadOnly<{hasteModuleName: string, moduleProperties: string[]}>) => {
|
|
return `class JSI_EXPORT ${hasteModuleName}CxxSpecJSI : public TurboModule {
|
|
protected:
|
|
${hasteModuleName}CxxSpecJSI(std::shared_ptr<CallInvoker> jsInvoker);
|
|
|
|
public:
|
|
${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[],
|
|
}>) => {
|
|
return `/**
|
|
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
|
|
*
|
|
* Do not edit this file as changes may cause incorrect behavior and will be lost
|
|
* once the code is regenerated.
|
|
*
|
|
* ${'@'}generated by codegen project: GenerateModuleH.js
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <ReactCommon/TurboModule.h>
|
|
#include <react/bridging/Bridging.h>
|
|
|
|
namespace facebook {
|
|
namespace react {
|
|
|
|
${modules.join('\n\n')}
|
|
|
|
} // namespace react
|
|
} // namespace facebook
|
|
`;
|
|
};
|
|
|
|
function translatePrimitiveJSTypeToCpp(
|
|
nullableTypeAnnotation: Nullable<NativeModuleTypeAnnotation>,
|
|
createErrorMessage: (typeName: string) => string,
|
|
resolveAlias: AliasResolver,
|
|
) {
|
|
const [typeAnnotation] = unwrapNullable<NativeModuleTypeAnnotation>(
|
|
nullableTypeAnnotation,
|
|
);
|
|
let realTypeAnnotation = typeAnnotation;
|
|
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
|
|
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
|
|
}
|
|
|
|
switch (realTypeAnnotation.type) {
|
|
case 'ReservedTypeAnnotation':
|
|
switch (realTypeAnnotation.name) {
|
|
case 'RootTag':
|
|
return 'double';
|
|
default:
|
|
(realTypeAnnotation.name: empty);
|
|
throw new Error(createErrorMessage(realTypeAnnotation.name));
|
|
}
|
|
case 'VoidTypeAnnotation':
|
|
return 'void';
|
|
case 'StringTypeAnnotation':
|
|
return 'jsi::String';
|
|
case 'NumberTypeAnnotation':
|
|
return 'double';
|
|
case 'DoubleTypeAnnotation':
|
|
return 'double';
|
|
case 'FloatTypeAnnotation':
|
|
return 'double';
|
|
case 'Int32TypeAnnotation':
|
|
return 'int';
|
|
case 'BooleanTypeAnnotation':
|
|
return 'bool';
|
|
case 'GenericObjectTypeAnnotation':
|
|
return 'jsi::Object';
|
|
case 'ObjectTypeAnnotation':
|
|
return 'jsi::Object';
|
|
case 'ArrayTypeAnnotation':
|
|
return 'jsi::Array';
|
|
case 'FunctionTypeAnnotation':
|
|
return 'jsi::Function';
|
|
case 'PromiseTypeAnnotation':
|
|
return 'jsi::Value';
|
|
default:
|
|
(realTypeAnnotation.type: empty);
|
|
throw new Error(createErrorMessage(realTypeAnnotation.type));
|
|
}
|
|
}
|
|
|
|
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(
|
|
libraryName: string,
|
|
schema: SchemaType,
|
|
packageName?: string,
|
|
assumeNonnull: boolean = false,
|
|
): FilesOutput {
|
|
const nativeModules = getModules(schema);
|
|
|
|
const modules = Object.keys(nativeModules).flatMap(hasteModuleName => {
|
|
const {
|
|
aliases,
|
|
spec: {properties},
|
|
moduleNames: [moduleName],
|
|
} = nativeModules[hasteModuleName];
|
|
const resolveAlias = createAliasResolver(aliases);
|
|
|
|
return [
|
|
ModuleClassDeclarationTemplate({
|
|
hasteModuleName,
|
|
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});
|
|
|
|
return new Map([[fileName, replacedTemplate]]);
|
|
},
|
|
};
|