Generate enum types that would be allowed to be used as well as string/number in c++ turbo modules generators (#36030)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/36030

Generate enum types in c++ turbo modules.

For enums in the ts schema file such as:
```
export enum NumEnum {
  ONE = 1,
  TWO = 2,
}
```
This would export enums and the relevant Bridging to js and from js code to the spec H files such as:
```
#pragma mark - SampleTurboModuleCxxNumEnum

enum SampleTurboModuleCxxNumEnum { ONE, TWO };

template <>
struct Bridging<SampleTurboModuleCxxNumEnum> {
  static SampleTurboModuleCxxNumEnum fromJs(jsi::Runtime &rt, int32_t value) {

    if (value == 1) {
      return SampleTurboModuleCxxNumEnum::ONE;
    } else if (value == 2) {
      return SampleTurboModuleCxxNumEnum::TWO;
    } else {
      throw jsi::JSError(rt, "No appropriate enum member found for value");
    }
  }

  static jsi::Value toJs(jsi::Runtime &rt, SampleTurboModuleCxxNumEnum value) {
    if (value == SampleTurboModuleCxxNumEnum::ONE) {
      return bridging::toJs(rt, 1);
    } else if (value == SampleTurboModuleCxxNumEnum::TWO) {
      return bridging::toJs(rt, 2);
    } else {
      throw jsi::JSError(rt, "No appropriate enum member found for enum value");
    }
  }
};

```
That code would allow us to use these enums in the cxx files like this:
```
  NativeCxxModuleExampleCxxEnumInt getNumEnum(
      jsi::Runtime &rt,
      NativeCxxModuleExampleCxxEnumInt arg);
```

Changelog: [General] [Added] Generate enum types that would be allowed to be used as well as string/number in c++ turbo modules generators

Reviewed By: christophpurrer

Differential Revision: D42884147

fbshipit-source-id: d34d1fc7ba268b570821dc108444196f69a431b2
This commit is contained in:
Vitali Zaidman
2023-02-13 15:09:44 -08:00
committed by Facebook GitHub Bot
parent bf3656b758
commit ceb1d0dea6
21 changed files with 973 additions and 106 deletions
@@ -17,10 +17,20 @@ import type {
NativeModuleFunctionTypeAnnotation,
NativeModulePropertyShape,
NativeModuleAliasMap,
NativeModuleEnumMap,
NativeModuleEnumMembers,
NativeModuleEnumMemberType,
} from '../../CodegenSchema';
import type {AliasResolver} from './Utils';
const {createAliasResolver, getModules} = require('./Utils');
const {getEnumName, toSafeCppString} = require('../Utils');
const {
createAliasResolver,
getModules,
getAreEnumMembersInteger,
} = require('./Utils');
const {indent} = require('../Utils');
const {unwrapNullable} = require('../../parsers/parsers-commons');
@@ -30,12 +40,15 @@ const ModuleClassDeclarationTemplate = ({
hasteModuleName,
moduleProperties,
structs,
enums,
}: $ReadOnly<{
hasteModuleName: string,
moduleProperties: string[],
structs: string,
enums: string,
}>) => {
return `${structs}class JSI_EXPORT ${hasteModuleName}CxxSpecJSI : public TurboModule {
return `${enums}
${structs}class JSI_EXPORT ${hasteModuleName}CxxSpecJSI : public TurboModule {
protected:
${hasteModuleName}CxxSpecJSI(std::shared_ptr<CallInvoker> jsInvoker);
@@ -112,10 +125,12 @@ ${modules.join('\n\n')}
};
function translatePrimitiveJSTypeToCpp(
moduleName: string,
nullableTypeAnnotation: Nullable<NativeModuleTypeAnnotation>,
optional: boolean,
createErrorMessage: (typeName: string) => string,
resolveAlias: AliasResolver,
enumMap: NativeModuleEnumMap,
) {
const [typeAnnotation, nullable] = unwrapNullable<NativeModuleTypeAnnotation>(
nullableTypeAnnotation,
@@ -157,7 +172,11 @@ function translatePrimitiveJSTypeToCpp(
case 'EnumDeclaration':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrap('double');
return getAreEnumMembersInteger(
enumMap[realTypeAnnotation.name].members,
)
? wrap('int')
: wrap('double');
case 'StringTypeAnnotation':
return wrap('jsi::String');
default:
@@ -192,10 +211,11 @@ function translatePrimitiveJSTypeToCpp(
}
}
function createStructs(
function createStructsString(
moduleName: string,
aliasMap: NativeModuleAliasMap,
resolveAlias: AliasResolver,
enumMap: NativeModuleEnumMap,
): string {
return Object.keys(aliasMap)
.map(alias => {
@@ -213,11 +233,13 @@ function createStructs(
const paramemterConversion = value.properties
.map((v, i) => {
const translatedParam = translatePrimitiveJSTypeToCpp(
moduleName,
v.typeAnnotation,
false,
typeName =>
`Unsupported type for param "${v.name}". Found: ${typeName}`,
resolveAlias,
enumMap,
);
return ` static ${translatedParam} ${v.name}ToJs(jsi::Runtime &rt, P${i} value) {
return bridging::toJs(rt, value);
@@ -281,9 +303,128 @@ ${paramemterConversion}
.join('\n');
}
type NativeEnumMemberValueType = 'std::string' | 'int32_t' | 'float';
const EnumTemplate = ({
enumName,
values,
fromCases,
toCases,
nativeEnumMemberType,
}: {
enumName: string,
values: string,
fromCases: string,
toCases: string,
nativeEnumMemberType: NativeEnumMemberValueType,
}) => {
const fromValue =
nativeEnumMemberType === 'std::string'
? 'const jsi::String &rawValue'
: `${nativeEnumMemberType} value`;
const fromValueConvertion =
nativeEnumMemberType === 'std::string'
? 'std::string value = rawValue.utf8(rt);'
: '';
const toValue =
nativeEnumMemberType === 'std::string' ? 'jsi::String' : 'jsi::Value';
return `
#pragma mark - ${enumName}
enum ${enumName} { ${values} };
template <>
struct Bridging<${enumName}> {
static ${enumName} fromJs(jsi::Runtime &rt, ${fromValue}) {
${fromValueConvertion}
${fromCases}
}
static ${toValue} toJs(jsi::Runtime &rt, ${enumName} value) {
${toCases}
}
};`;
};
function generateEnum(
moduleName: string,
origEnumName: string,
members: NativeModuleEnumMembers,
memberType: NativeModuleEnumMemberType,
): string {
const enumName = getEnumName(moduleName, origEnumName);
const nativeEnumMemberType: NativeEnumMemberValueType =
memberType === 'StringTypeAnnotation'
? 'std::string'
: getAreEnumMembersInteger(members)
? 'int32_t'
: 'float';
const getMemberValueAppearance = (value: string) =>
memberType === 'StringTypeAnnotation'
? `"${value}"`
: `${value}${nativeEnumMemberType === 'float' ? 'f' : ''}`;
const fromCases =
members
.map(
member => `if (value == ${getMemberValueAppearance(member.value)}) {
return ${enumName}::${toSafeCppString(member.name)};
}`,
)
.join(' else ') +
` else {
throw jsi::JSError(rt, "No appropriate enum member found for value");
}`;
const toCases =
members
.map(
member => `if (value == ${enumName}::${toSafeCppString(member.name)}) {
return bridging::toJs(rt, ${getMemberValueAppearance(member.value)});
}`,
)
.join(' else ') +
` else {
throw jsi::JSError(rt, "No appropriate enum member found for enum value");
}`;
return EnumTemplate({
enumName,
values: members.map(member => member.name).join(', '),
fromCases,
toCases,
nativeEnumMemberType,
});
}
function createEnums(
moduleName: string,
enumMap: NativeModuleEnumMap,
resolveAlias: AliasResolver,
): string {
return Object.entries(enumMap)
.map(([enumName, enumNode]) => {
return generateEnum(
moduleName,
enumName,
enumNode.members,
enumNode.memberType,
);
})
.filter(Boolean)
.join('\n');
}
function translatePropertyToCpp(
moduleName: string,
prop: NativeModulePropertyShape,
resolveAlias: AliasResolver,
enumMap: NativeModuleEnumMap,
abstract: boolean = false,
) {
const [propTypeAnnotation] =
@@ -295,20 +436,24 @@ function translatePropertyToCpp(
const paramTypes = propTypeAnnotation.params.map(param => {
const translatedParam = translatePrimitiveJSTypeToCpp(
moduleName,
param.typeAnnotation,
param.optional,
typeName =>
`Unsupported type for param "${param.name}" in ${prop.name}. Found: ${typeName}`,
resolveAlias,
enumMap,
);
return `${translatedParam} ${param.name}`;
});
const returnType = translatePrimitiveJSTypeToCpp(
moduleName,
propTypeAnnotation.returnTypeAnnotation,
false,
typeName => `Unsupported return type for ${prop.name}. Found: ${typeName}`,
resolveAlias,
enumMap,
);
// The first param will always be the runtime reference.
@@ -342,25 +487,39 @@ module.exports = {
const modules = Object.keys(nativeModules).flatMap(hasteModuleName => {
const {
aliasMap,
enumMap,
spec: {properties},
moduleName,
} = nativeModules[hasteModuleName];
const resolveAlias = createAliasResolver(aliasMap);
const structs = createStructs(moduleName, aliasMap, resolveAlias);
const structs = createStructsString(
moduleName,
aliasMap,
resolveAlias,
enumMap,
);
const enums = createEnums(moduleName, enumMap, resolveAlias);
return [
ModuleClassDeclarationTemplate({
hasteModuleName,
moduleProperties: properties.map(prop =>
translatePropertyToCpp(prop, resolveAlias, true),
translatePropertyToCpp(
moduleName,
prop,
resolveAlias,
enumMap,
true,
),
),
structs,
enums,
}),
ModuleSpecClassDeclarationTemplate({
hasteModuleName,
moduleName,
moduleProperties: properties.map(prop =>
translatePropertyToCpp(prop, resolveAlias),
translatePropertyToCpp(moduleName, prop, resolveAlias, enumMap),
),
}),
];