Skip to content

Commit 31f0796

Browse files
appdenfacebook-github-bot
authored andcommitted
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
1 parent 6697b7b commit 31f0796

File tree

3 files changed

+634
-54
lines changed

3 files changed

+634
-54
lines changed

ReactCommon/react/bridging/Class.h

+10
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,14 @@ T callFromJs(
5252
}
5353
}
5454

55+
template <typename R, typename... Args>
56+
constexpr size_t getParameterCount(R (*)(Args...)) {
57+
return sizeof...(Args);
58+
}
59+
60+
template <typename C, typename R, typename... Args>
61+
constexpr size_t getParameterCount(R (C::*)(Args...)) {
62+
return sizeof...(Args);
63+
}
64+
5565
} // namespace facebook::react::bridging

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

+112-54
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {
1515
SchemaType,
1616
NativeModuleTypeAnnotation,
1717
NativeModuleFunctionTypeAnnotation,
18+
NativeModulePropertyShape,
1819
} from '../../CodegenSchema';
1920

2021
import type {AliasResolver} from './Utils';
@@ -27,21 +28,58 @@ type FilesOutput = Map<string, string>;
2728
const ModuleClassDeclarationTemplate = ({
2829
hasteModuleName,
2930
moduleProperties,
30-
}: $ReadOnly<{hasteModuleName: string, moduleProperties: string}>) => {
31+
}: $ReadOnly<{hasteModuleName: string, moduleProperties: string[]}>) => {
3132
return `class JSI_EXPORT ${hasteModuleName}CxxSpecJSI : public TurboModule {
3233
protected:
3334
${hasteModuleName}CxxSpecJSI(std::shared_ptr<CallInvoker> jsInvoker);
3435
3536
public:
36-
${indent(moduleProperties, 2)}
37+
${indent(moduleProperties.join('\n'), 2)}
3738
3839
};`;
3940
};
4041

42+
const ModuleSpecClassDeclarationTemplate = ({
43+
hasteModuleName,
44+
moduleName,
45+
moduleProperties,
46+
}: $ReadOnly<{
47+
hasteModuleName: string,
48+
moduleName: string,
49+
moduleProperties: string[],
50+
}>) => {
51+
return `template <typename T>
52+
class JSI_EXPORT ${hasteModuleName}CxxSpec : public TurboModule {
53+
public:
54+
jsi::Value get(jsi::Runtime &rt, const jsi::PropNameID &propName) override {
55+
return delegate_.get(rt, propName);
56+
}
57+
58+
protected:
59+
${hasteModuleName}CxxSpec(std::shared_ptr<CallInvoker> jsInvoker)
60+
: TurboModule("${moduleName}", jsInvoker),
61+
delegate_(static_cast<T*>(this), jsInvoker) {}
62+
63+
private:
64+
class Delegate : public ${hasteModuleName}CxxSpecJSI {
65+
public:
66+
Delegate(T *instance, std::shared_ptr<CallInvoker> jsInvoker) :
67+
${hasteModuleName}CxxSpecJSI(std::move(jsInvoker)), instance_(instance) {}
68+
69+
${indent(moduleProperties.join('\n'), 4)}
70+
71+
private:
72+
T *instance_;
73+
};
74+
75+
Delegate delegate_;
76+
};`;
77+
};
78+
4179
const FileTemplate = ({
4280
modules,
4381
}: $ReadOnly<{
44-
modules: string,
82+
modules: string[],
4583
}>) => {
4684
return `/**
4785
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
@@ -55,11 +93,12 @@ const FileTemplate = ({
5593
#pragma once
5694
5795
#include <ReactCommon/TurboModule.h>
96+
#include <react/bridging/Bridging.h>
5897
5998
namespace facebook {
6099
namespace react {
61100
62-
${modules}
101+
${modules.join('\n\n')}
63102
64103
} // namespace react
65104
} // namespace facebook
@@ -118,8 +157,52 @@ function translatePrimitiveJSTypeToCpp(
118157
}
119158
}
120159

121-
const propertyTemplate =
122-
'virtual ::_RETURN_VALUE_:: ::_PROPERTY_NAME_::(jsi::Runtime &rt::_ARGS_::) = 0;';
160+
function translatePropertyToCpp(
161+
prop: NativeModulePropertyShape,
162+
resolveAlias: AliasResolver,
163+
abstract: boolean = false,
164+
) {
165+
const [propTypeAnnotation] =
166+
unwrapNullable<NativeModuleFunctionTypeAnnotation>(prop.typeAnnotation);
167+
168+
const params = propTypeAnnotation.params.map(
169+
param => `std::move(${param.name})`,
170+
);
171+
172+
const paramTypes = propTypeAnnotation.params.map(param => {
173+
const translatedParam = translatePrimitiveJSTypeToCpp(
174+
param.typeAnnotation,
175+
typeName =>
176+
`Unsupported type for param "${param.name}" in ${prop.name}. Found: ${typeName}`,
177+
resolveAlias,
178+
);
179+
return `${translatedParam} ${param.name}`;
180+
});
181+
182+
const returnType = translatePrimitiveJSTypeToCpp(
183+
propTypeAnnotation.returnTypeAnnotation,
184+
typeName => `Unsupported return type for ${prop.name}. Found: ${typeName}`,
185+
resolveAlias,
186+
);
187+
188+
// The first param will always be the runtime reference.
189+
paramTypes.unshift('jsi::Runtime &rt');
190+
191+
const method = `${returnType} ${prop.name}(${paramTypes.join(', ')})`;
192+
193+
if (abstract) {
194+
return `virtual ${method} = 0;`;
195+
}
196+
197+
return `${method} override {
198+
static_assert(
199+
bridging::getParameterCount(&T::${prop.name}) == ${paramTypes.length},
200+
"Expected ${prop.name}(...) to have ${paramTypes.length} parameters");
201+
202+
return bridging::callFromJs<${returnType}>(
203+
rt, &T::${prop.name}, jsInvoker_, ${['instance_', ...params].join(', ')});
204+
}`;
205+
}
123206

124207
module.exports = {
125208
generate(
@@ -130,55 +213,30 @@ module.exports = {
130213
): FilesOutput {
131214
const nativeModules = getModules(schema);
132215

133-
const modules = Object.keys(nativeModules)
134-
.map(hasteModuleName => {
135-
const {
136-
aliases,
137-
spec: {properties},
138-
} = nativeModules[hasteModuleName];
139-
const resolveAlias = createAliasResolver(aliases);
140-
141-
const traversedProperties = properties
142-
.map(prop => {
143-
const [propTypeAnnotation] =
144-
unwrapNullable<NativeModuleFunctionTypeAnnotation>(
145-
prop.typeAnnotation,
146-
);
147-
const traversedArgs = propTypeAnnotation.params
148-
.map(param => {
149-
const translatedParam = translatePrimitiveJSTypeToCpp(
150-
param.typeAnnotation,
151-
typeName =>
152-
`Unsupported type for param "${param.name}" in ${prop.name}. Found: ${typeName}`,
153-
resolveAlias,
154-
);
155-
return `${translatedParam} ${param.name}`;
156-
})
157-
.join(', ');
158-
return propertyTemplate
159-
.replace('::_PROPERTY_NAME_::', prop.name)
160-
.replace(
161-
'::_RETURN_VALUE_::',
162-
translatePrimitiveJSTypeToCpp(
163-
propTypeAnnotation.returnTypeAnnotation,
164-
typeName =>
165-
`Unsupported return type for ${prop.name}. Found: ${typeName}`,
166-
resolveAlias,
167-
),
168-
)
169-
.replace(
170-
'::_ARGS_::',
171-
traversedArgs === '' ? '' : ', ' + traversedArgs,
172-
);
173-
})
174-
.join('\n');
175-
176-
return ModuleClassDeclarationTemplate({
216+
const modules = Object.keys(nativeModules).flatMap(hasteModuleName => {
217+
const {
218+
aliases,
219+
spec: {properties},
220+
moduleNames: [moduleName],
221+
} = nativeModules[hasteModuleName];
222+
const resolveAlias = createAliasResolver(aliases);
223+
224+
return [
225+
ModuleClassDeclarationTemplate({
226+
hasteModuleName,
227+
moduleProperties: properties.map(prop =>
228+
translatePropertyToCpp(prop, resolveAlias, true),
229+
),
230+
}),
231+
ModuleSpecClassDeclarationTemplate({
177232
hasteModuleName,
178-
moduleProperties: traversedProperties,
179-
});
180-
})
181-
.join('\n');
233+
moduleName,
234+
moduleProperties: properties.map(prop =>
235+
translatePropertyToCpp(prop, resolveAlias),
236+
),
237+
}),
238+
];
239+
});
182240

183241
const fileName = `${libraryName}JSI.h`;
184242
const replacedTemplate = FileTemplate({modules});

0 commit comments

Comments
 (0)