Skip to content

Commit

Permalink
react-native-code-gen Add Enum Type support for C++ TurboModules
Browse files Browse the repository at this point in the history
Summary:
There are cases where we want to pass an enum type into a TurboModule method which is handy to restrict certain arguments to a restricted set of values, e.g. restricting quality to ```enum Quality { SD, HD, }```

Approach:
- We are not generating an ```enum``` type in C++ but rather just cast type safe to the corresponding member type.

- We don't support mixed enum types at this time, e.g. ```export enum StringOption { One = 'one', Two = 2, Three = 'three', };``` will not work. See: https://www.typescriptlang.org/docs/handbook/enums.html#heterogeneous-enums

- We only support untyped (default to String), String, and Number enum properties

This is for C++ only, Java and ObjC are not supported atm.

Changelog:
[General][Added] - react-native-code-gen Add Enum Type support for C++ TurboModules

Reviewed By: RSNara

Differential Revision: D38880963

fbshipit-source-id: f2399b29948306bc555429b6f96c43ea4c39c46e
  • Loading branch information
christophpurrer authored and facebook-github-bot committed Aug 30, 2022
1 parent 621f4cf commit b444f0e
Show file tree
Hide file tree
Showing 22 changed files with 361 additions and 15 deletions.
2 changes: 1 addition & 1 deletion packages/eslint-plugin-specs/react-native-modules.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ function rule(context) {
const [parsingErrors, tryParse] = createParserErrorCapturer();

const sourceCode = context.getSourceCode().getText();
const ast = flowParser.parse(sourceCode);
const ast = flowParser.parse(sourceCode, {enums: true});

tryParse(() => {
buildModuleSchema(hasteModuleName, ast, tryParse);
Expand Down
6 changes: 6 additions & 0 deletions packages/react-native-codegen/src/CodegenSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,11 @@ export type NativeModuleBooleanTypeAnnotation = $ReadOnly<{
type: 'BooleanTypeAnnotation',
}>;

export type NativeModuleEnumDeclaration = $ReadOnly<{
type: 'EnumDeclaration',
memberType: 'NumberTypeAnnotation' | 'StringTypeAnnotation',
}>;

export type NativeModuleGenericObjectTypeAnnotation = $ReadOnly<{
type: 'GenericObjectTypeAnnotation',
}>;
Expand Down Expand Up @@ -316,6 +321,7 @@ export type NativeModuleBaseTypeAnnotation =
| NativeModuleDoubleTypeAnnotation
| NativeModuleFloatTypeAnnotation
| NativeModuleBooleanTypeAnnotation
| NativeModuleEnumDeclaration
| NativeModuleGenericObjectTypeAnnotation
| ReservedTypeAnnotation
| NativeModuleTypeAliasTypeAnnotation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,17 @@ function serializeArg(
return wrap(val => `${val}.asString(rt)`);
case 'BooleanTypeAnnotation':
return wrap(val => `${val}.asBool()`);
case 'EnumDeclaration':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrap(val => `${val}.asNumber()`);
case 'StringTypeAnnotation':
return wrap(val => `${val}.asString(rt)`);
default:
throw new Error(
`Unknown enum type for "${arg.name}, found: ${realTypeAnnotation.type}"`,
);
}
case 'NumberTypeAnnotation':
return wrap(val => `${val}.asNumber()`);
case 'FloatTypeAnnotation':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,15 @@ function translatePrimitiveJSTypeToCpp(
return wrap('int');
case 'BooleanTypeAnnotation':
return wrap('bool');
case 'EnumDeclaration':
switch (realTypeAnnotation.memberType) {
case 'NumberTypeAnnotation':
return wrap('double');
case 'StringTypeAnnotation':
return wrap('jsi::String');
default:
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
case 'GenericObjectTypeAnnotation':
return wrap('jsi::Object');
case 'UnionTypeAnnotation':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,10 @@ function translateFunctionParamToJavaType(
imports.add('com.facebook.react.bridge.Callback');
return 'Callback';
default:
(realTypeAnnotation.type: 'MixedTypeAnnotation' | 'UnionTypeAnnotation');
(realTypeAnnotation.type:
| 'EnumDeclaration'
| 'MixedTypeAnnotation'
| 'UnionTypeAnnotation');
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
}
Expand Down Expand Up @@ -220,7 +223,10 @@ function translateFunctionReturnTypeToJavaType(
imports.add('com.facebook.react.bridge.WritableArray');
return wrapIntoNullableIfNeeded('WritableArray');
default:
(realTypeAnnotation.type: 'MixedTypeAnnotation' | 'UnionTypeAnnotation');
(realTypeAnnotation.type:
| 'EnumDeclaration'
| 'MixedTypeAnnotation'
| 'UnionTypeAnnotation');
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
}
Expand Down Expand Up @@ -272,7 +278,10 @@ function getFalsyReturnStatementFromReturnType(
case 'ArrayTypeAnnotation':
return 'return null;';
default:
(realTypeAnnotation.type: 'MixedTypeAnnotation' | 'UnionTypeAnnotation');
(realTypeAnnotation.type:
| 'EnumDeclaration'
| 'MixedTypeAnnotation'
| 'UnionTypeAnnotation');
throw new Error(createErrorMessage(realTypeAnnotation.type));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,10 @@ function translateReturnTypeToKind(
case 'ArrayTypeAnnotation':
return 'ArrayKind';
default:
(realTypeAnnotation.type: 'MixedTypeAnnotation' | 'UnionTypeAnnotation');
(realTypeAnnotation.type:
| 'EnumDeclaration'
| 'MixedTypeAnnotation'
| 'UnionTypeAnnotation');
throw new Error(
`Unknown prop type for returning value, found: ${realTypeAnnotation.type}"`,
);
Expand Down Expand Up @@ -226,7 +229,10 @@ function translateParamTypeToJniType(
case 'FunctionTypeAnnotation':
return 'Lcom/facebook/react/bridge/Callback;';
default:
(realTypeAnnotation.type: 'MixedTypeAnnotation' | 'UnionTypeAnnotation');
(realTypeAnnotation.type:
| 'EnumDeclaration'
| 'MixedTypeAnnotation'
| 'UnionTypeAnnotation');
throw new Error(
`Unknown prop type for method arg, found: ${realTypeAnnotation.type}"`,
);
Expand Down Expand Up @@ -278,7 +284,10 @@ function translateReturnTypeToJniType(
case 'ArrayTypeAnnotation':
return 'Lcom/facebook/react/bridge/WritableArray;';
default:
(realTypeAnnotation.type: 'MixedTypeAnnotation' | 'UnionTypeAnnotation');
(realTypeAnnotation.type:
| 'EnumDeclaration'
| 'MixedTypeAnnotation'
| 'UnionTypeAnnotation');
throw new Error(
`Unknown prop type for method return type, found: ${realTypeAnnotation.type}"`,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ class StructCollector {
this._insertAlias(typeAnnotation.name, structContext, resolveAlias);
return wrapNullable(nullable, typeAnnotation);
}
case 'EnumDeclaration':
throw new Error('Enum types are unsupported in structs');
case 'MixedTypeAnnotation':
throw new Error('Mixed types are unsupported in structs');
case 'UnionTypeAnnotation':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,10 @@ function getReturnObjCType(
case 'GenericObjectTypeAnnotation':
return wrapIntoNullableIfNeeded('NSDictionary *');
default:
(typeAnnotation.type: 'MixedTypeAnnotation' | 'UnionTypeAnnotation');
(typeAnnotation.type:
| 'EnumDeclaration'
| 'MixedTypeAnnotation'
| 'UnionTypeAnnotation');
throw new Error(
`Unsupported return type for ${methodName}. Found: ${typeAnnotation.type}`,
);
Expand Down Expand Up @@ -378,7 +381,10 @@ function getReturnJSType(
case 'GenericObjectTypeAnnotation':
return 'ObjectKind';
default:
(typeAnnotation.type: 'MixedTypeAnnotation' | 'UnionTypeAnnotation');
(typeAnnotation.type:
| 'EnumDeclaration'
| 'MixedTypeAnnotation'
| 'UnionTypeAnnotation');
throw new Error(
`Unsupported return type for ${methodName}. Found: ${typeAnnotation.type}`,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1567,6 +1567,42 @@ const CXX_ONLY_NATIVE_MODULES: SchemaType = {
],
},
},
{
name: 'getEnums',
optional: false,
typeAnnotation: {
type: 'FunctionTypeAnnotation',
returnTypeAnnotation: {
type: 'StringTypeAnnotation',
},
params: [
{
name: 'enumInt',
optional: false,
typeAnnotation: {
type: 'EnumDeclaration',
memberType: 'NumberTypeAnnotation',
},
},
{
name: 'enumFloat',
optional: false,
typeAnnotation: {
type: 'EnumDeclaration',
memberType: 'NumberTypeAnnotation',
},
},
{
name: 'enumString',
optional: false,
typeAnnotation: {
type: 'EnumDeclaration',
memberType: 'StringTypeAnnotation',
},
},
],
},
},
{
name: 'getUnion',
optional: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ static jsi::Value __hostFunction_NativeSampleTurboModuleCxxSpecJSI_getNullableNu
auto result = static_cast<NativeSampleTurboModuleCxxSpecJSI *>(&turboModule)->getNullableNumberFromNullableAlias(rt, args[0].isNull() || args[0].isUndefined() ? std::nullopt : std::make_optional(args[0].asObject(rt)));
return result ? jsi::Value(std::move(*result)) : jsi::Value::null();
}
static jsi::Value __hostFunction_NativeSampleTurboModuleCxxSpecJSI_getEnums(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
return static_cast<NativeSampleTurboModuleCxxSpecJSI *>(&turboModule)->getEnums(rt, args[0].asNumber(), args[1].asNumber(), args[2].asString(rt));
}
static jsi::Value __hostFunction_NativeSampleTurboModuleCxxSpecJSI_getUnion(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
return static_cast<NativeSampleTurboModuleCxxSpecJSI *>(&turboModule)->getUnion(rt, args[0].asNumber(), args[1].asNumber(), args[2].asObject(rt), args[3].asString(rt));
}
Expand All @@ -123,6 +126,7 @@ NativeSampleTurboModuleCxxSpecJSI::NativeSampleTurboModuleCxxSpecJSI(std::shared
: TurboModule(\\"SampleTurboModuleCxx\\", jsInvoker) {
methodMap_[\\"getMixed\\"] = MethodMetadata {1, __hostFunction_NativeSampleTurboModuleCxxSpecJSI_getMixed};
methodMap_[\\"getNullableNumberFromNullableAlias\\"] = MethodMetadata {1, __hostFunction_NativeSampleTurboModuleCxxSpecJSI_getNullableNumberFromNullableAlias};
methodMap_[\\"getEnums\\"] = MethodMetadata {3, __hostFunction_NativeSampleTurboModuleCxxSpecJSI_getEnums};
methodMap_[\\"getUnion\\"] = MethodMetadata {4, __hostFunction_NativeSampleTurboModuleCxxSpecJSI_getUnion};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ protected:
public:
virtual jsi::Value getMixed(jsi::Runtime &rt, jsi::Value arg) = 0;
virtual std::optional<double> getNullableNumberFromNullableAlias(jsi::Runtime &rt, std::optional<jsi::Object> a) = 0;
virtual jsi::String getEnums(jsi::Runtime &rt, double enumInt, double enumFloat, jsi::String enumString) = 0;
virtual jsi::Object getUnion(jsi::Runtime &rt, double chooseInt, double chooseFloat, jsi::Object chooseObject, jsi::String chooseString) = 0;
};
Expand Down Expand Up @@ -247,6 +248,14 @@ private:
return bridging::callFromJs<std::optional<double>>(
rt, &T::getNullableNumberFromNullableAlias, jsInvoker_, instance_, std::move(a));
}
jsi::String getEnums(jsi::Runtime &rt, double enumInt, double enumFloat, jsi::String enumString) override {
static_assert(
bridging::getParameterCount(&T::getEnums) == 4,
\\"Expected getEnums(...) to have 4 parameters\\");
return bridging::callFromJs<jsi::String>(
rt, &T::getEnums, jsInvoker_, instance_, std::move(enumInt), std::move(enumFloat), std::move(enumString));
}
jsi::Object getUnion(jsi::Runtime &rt, double chooseInt, double chooseFloat, jsi::Object chooseObject, jsi::String chooseString) override {
static_assert(
bridging::getParameterCount(&T::getUnion) == 5,
Expand Down
2 changes: 1 addition & 1 deletion packages/react-native-codegen/src/parsers/flow/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ function buildSchema(contents: string, filename: ?string): SchemaType {
return {modules: {}};
}

const ast = flowParser.parse(contents);
const ast = flowParser.parse(contents, {enums: true});
const configType = getConfigType(ast);

switch (configType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -598,9 +598,31 @@ export type ChooseFloat = 1.44 | 2.88 | 5.76;
export type ChooseObject = {} | {low: string};
export type ChooseString = 'One' | 'Two' | 'Three';
export enum Quality {
SD,
HD,
}
export enum Resolution {
Low = 720,
High = 1080,
}
export enum Floppy {
LowDensity = 0.72,
HighDensity = 1.44,
}
export enum StringOptions {
One = 'one',
Two = 'two',
Three = 'three',
}
export interface Spec extends TurboModule {
+getCallback: () => () => void;
+getMixed: (arg: mixed) => mixed;
+getEnums: (quality: Quality, resolution?: Resolution, floppy: Floppy, stringOptions: StringOptions) => string;
+getUnion: (chooseInt: ChooseInt, chooseFloat: ChooseFloat, chooseObject: ChooseObject, chooseString: ChooseString) => ChooseObject;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,50 @@ exports[`RN Codegen Flow Parser can generate fixture CXX_ONLY_NATIVE_MODULE 1`]
]
}
},
{
'name': 'getEnums',
'optional': false,
'typeAnnotation': {
'type': 'FunctionTypeAnnotation',
'returnTypeAnnotation': {
'type': 'StringTypeAnnotation'
},
'params': [
{
'name': 'quality',
'optional': false,
'typeAnnotation': {
'type': 'EnumDeclaration',
'memberType': 'StringTypeAnnotation'
}
},
{
'name': 'resolution',
'optional': true,
'typeAnnotation': {
'type': 'EnumDeclaration',
'memberType': 'NumberTypeAnnotation'
}
},
{
'name': 'floppy',
'optional': false,
'typeAnnotation': {
'type': 'EnumDeclaration',
'memberType': 'NumberTypeAnnotation'
}
},
{
'name': 'stringOptions',
'optional': false,
'typeAnnotation': {
'type': 'EnumDeclaration',
'memberType': 'StringTypeAnnotation'
}
}
]
}
},
{
'name': 'getUnion',
'optional': false,
Expand Down
19 changes: 19 additions & 0 deletions packages/react-native-codegen/src/parsers/flow/modules/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,24 @@ class UnsupportedFunctionReturnTypeAnnotationParserError extends ParserError {
}
}

/**
* Enum parsing errors
*/

class UnsupportedEnumDeclarationParserError extends ParserError {
constructor(
hasteModuleName: string,
arrayElementTypeAST: $FlowFixMe,
memberType: string,
) {
super(
hasteModuleName,
arrayElementTypeAST,
`Unexpected enum member type ${memberType}. Only string and number enum members are supported`,
);
}
}

/**
* Union parsing errors
*/
Expand Down Expand Up @@ -347,6 +365,7 @@ module.exports = {
UnsupportedFlowTypeAnnotationParserError,
UnsupportedFunctionParamTypeAnnotationParserError,
UnsupportedFunctionReturnTypeAnnotationParserError,
UnsupportedEnumDeclarationParserError,
UnsupportedUnionTypeAnnotationParserError,
UnsupportedModulePropertyParserError,
UnsupportedObjectPropertyTypeAnnotationParserError,
Expand Down
Loading

0 comments on commit b444f0e

Please sign in to comment.