diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js index 31b29998105..6c9786575f6 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js @@ -33,6 +33,8 @@ const { createAliasResolver, getAreEnumMembersInteger, getModules, + isArrayRecursiveMember, + isDirectRecursiveMember, } = require('./Utils'); type FilesOutput = Map; @@ -129,6 +131,7 @@ ${modules.join('\n\n')} function translatePrimitiveJSTypeToCpp( moduleName: string, + parentObjectAliasName: ?string, nullableTypeAnnotation: Nullable, optional: boolean, createErrorMessage: (typeName: string) => string, @@ -139,6 +142,10 @@ function translatePrimitiveJSTypeToCpp( nullableTypeAnnotation, ); const isRequired = !optional && !nullable; + const isRecursiveType = isDirectRecursiveMember( + parentObjectAliasName, + nullableTypeAnnotation, + ); let realTypeAnnotation = typeAnnotation; if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') { @@ -146,7 +153,7 @@ function translatePrimitiveJSTypeToCpp( } function wrap(type: string) { - return isRequired ? type : `std::optional<${type}>`; + return isRequired || isRecursiveType ? type : `std::optional<${type}>`; } switch (realTypeAnnotation.type) { @@ -221,10 +228,12 @@ function createStructsString( enumMap: NativeModuleEnumMap, ): string { const getCppType = ( + parentObjectAlias: string, v: NamedShape>, ) => translatePrimitiveJSTypeToCpp( moduleName, + parentObjectAlias, v.typeAnnotation, false, typeName => `Unsupported type for param "${v.name}". Found: ${typeName}`, @@ -250,7 +259,7 @@ function createStructsString( .join(', '); const debugParameterConversion = value.properties .map( - (v, i) => ` static ${getCppType(v)} ${ + (v, i) => ` static ${getCppType(alias, v)} ${ v.name }ToJs(jsi::Runtime &rt, P${i} value) { return bridging::toJs(rt, value); @@ -321,12 +330,39 @@ ${value.properties return ''; } const structName = `${moduleName}${alias}`; - const templateParameterWithTypename = value.properties + const templateParameter = value.properties.filter( + v => + !isDirectRecursiveMember(alias, v.typeAnnotation) && + !isArrayRecursiveMember(alias, v.typeAnnotation), + ); + const templateParameterWithTypename = templateParameter .map((v, i) => `typename P${i}`) .join(', '); + const templateParameterWithoutTypename = templateParameter + .map((v, i) => `P${i}`) + .join(', '); + let i = -1; + const templateMemberTypes = value.properties.map(v => { + if (isDirectRecursiveMember(alias, v.typeAnnotation)) { + return `std::unique_ptr<${structName}<${templateParameterWithoutTypename}>> ${v.name}`; + } else if (isArrayRecursiveMember(alias, v.typeAnnotation)) { + const [nullable] = unwrapNullable( + v.typeAnnotation, + ); + return ( + (nullable + ? `std::optional>>` + : `std::vector<${structName}<${templateParameterWithoutTypename}>>`) + + ` ${v.name}` + ); + } else { + i++; + return `P${i} ${v.name}`; + } + }); const debugParameterConversion = value.properties .map( - (v, i) => ` static ${getCppType(v)} ${ + v => ` static ${getCppType(alias, v)} ${ v.name }ToJs(jsi::Runtime &rt, decltype(types.${v.name}) value) { return bridging::toJs(rt, value); @@ -338,7 +374,7 @@ ${value.properties template <${templateParameterWithTypename}> struct ${structName} { -${value.properties.map((v, i) => ' P' + i + ' ' + v.name).join(';\n')}; +${templateMemberTypes.map(v => ' ' + v).join(';\n')}; bool operator==(const ${structName} &other) const { return ${value.properties .map(v => `${v.name} == other.${v.name}`) @@ -356,10 +392,13 @@ struct ${structName}Bridging { const std::shared_ptr &jsInvoker) { T result{ ${value.properties - .map( - (v, i) => - ` bridging::fromJs(rt, value.getProperty(rt, "${v.name}"), jsInvoker)`, - ) + .map(v => { + if (isDirectRecursiveMember(alias, v.typeAnnotation)) { + return ` value.hasProperty(rt, "${v.name}") ? std::make_unique(bridging::fromJs(rt, value.getProperty(rt, "${v.name}"), jsInvoker)) : nullptr`; + } else { + return ` bridging::fromJs(rt, value.getProperty(rt, "${v.name}"), jsInvoker)`; + } + }) .join(',\n')}}; return result; } @@ -374,8 +413,12 @@ ${debugParameterConversion} const std::shared_ptr &jsInvoker) { auto result = facebook::jsi::Object(rt); ${value.properties - .map((v, i) => { - if (v.optional) { + .map(v => { + if (isDirectRecursiveMember(alias, v.typeAnnotation)) { + return ` if (value.${v.name}) { + result.setProperty(rt, "${v.name}", bridging::toJs(rt, *value.${v.name}, jsInvoker)); + }`; + } else if (v.optional) { return ` if (value.${v.name}) { result.setProperty(rt, "${v.name}", bridging::toJs(rt, value.${v.name}.value(), jsInvoker)); }`; @@ -528,6 +571,7 @@ function translatePropertyToCpp( const paramTypes = propTypeAnnotation.params.map(param => { const translatedParam = translatePrimitiveJSTypeToCpp( moduleName, + null, param.typeAnnotation, param.optional, typeName => @@ -540,6 +584,7 @@ function translatePropertyToCpp( const returnType = translatePrimitiveJSTypeToCpp( moduleName, + null, propTypeAnnotation.returnTypeAnnotation, false, typeName => `Unsupported return type for ${prop.name}. Found: ${typeName}`, diff --git a/packages/react-native-codegen/src/generators/modules/Utils.js b/packages/react-native-codegen/src/generators/modules/Utils.js index c924a3c6236..20e7a482b24 100644 --- a/packages/react-native-codegen/src/generators/modules/Utils.js +++ b/packages/react-native-codegen/src/generators/modules/Utils.js @@ -15,9 +15,12 @@ import type { NativeModuleEnumMembers, NativeModuleObjectTypeAnnotation, NativeModuleSchema, + NativeModuleTypeAnnotation, + Nullable, SchemaType, } from '../../CodegenSchema'; +const {unwrapNullable} = require('../../parsers/parsers-commons'); const invariant = require('invariant'); export type AliasResolver = ( @@ -48,6 +51,33 @@ function getModules( ); } +function isDirectRecursiveMember( + parentObjectAliasName: ?string, + nullableTypeAnnotation: Nullable, +): boolean { + const [typeAnnotation] = unwrapNullable( + nullableTypeAnnotation, + ); + return ( + parentObjectAliasName !== undefined && + typeAnnotation.name === parentObjectAliasName + ); +} + +function isArrayRecursiveMember( + parentObjectAliasName: ?string, + nullableTypeAnnotation: Nullable, +): boolean { + const [typeAnnotation] = unwrapNullable( + nullableTypeAnnotation, + ); + return ( + parentObjectAliasName !== undefined && + typeAnnotation.type === 'ArrayTypeAnnotation' && + typeAnnotation.elementType?.name === parentObjectAliasName + ); +} + function getAreEnumMembersInteger(members: NativeModuleEnumMembers): boolean { return !members.some(m => `${m.value}`.includes('.')); } @@ -56,4 +86,6 @@ module.exports = { createAliasResolver, getModules, getAreEnumMembersInteger, + isDirectRecursiveMember, + isArrayRecursiveMember, }; diff --git a/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.h b/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.h index 15c7e720913..3e2e92692f8 100644 --- a/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.h +++ b/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.h @@ -91,84 +91,18 @@ struct CustomHostObjectRef { using CustomHostObject = HostObjectWrapper; #pragma mark - recursive objects -struct BinaryTreeNode { - std::unique_ptr left; - int32_t value; - std::unique_ptr right; -}; + +using BinaryTreeNode = NativeCxxModuleExampleCxxBinaryTreeNode; template <> -struct Bridging { - static BinaryTreeNode fromJs( - jsi::Runtime& rt, - const jsi::Object& value, - const std::shared_ptr& jsInvoker) { - BinaryTreeNode result{ - value.hasProperty(rt, "left") - ? std::make_unique(bridging::fromJs( - rt, value.getProperty(rt, "left"), jsInvoker)) - : nullptr, - bridging::fromJs( - rt, value.getProperty(rt, "value"), jsInvoker), - value.hasProperty(rt, "right") - ? std::make_unique(bridging::fromJs( - rt, value.getProperty(rt, "right"), jsInvoker)) - : nullptr}; - return result; - } +struct Bridging + : NativeCxxModuleExampleCxxBinaryTreeNodeBridging {}; - static jsi::Object toJs( - jsi::Runtime& rt, - const BinaryTreeNode& value, - const std::shared_ptr& jsInvoker) { - auto result = facebook::jsi::Object(rt); - if (value.left) { - result.setProperty( - rt, "left", bridging::toJs(rt, *value.left, jsInvoker)); - } - result.setProperty(rt, "value", bridging::toJs(rt, value.value, jsInvoker)); - if (value.right) { - result.setProperty( - rt, "right", bridging::toJs(rt, *value.right, jsInvoker)); - } - return result; - } -}; - -struct GraphNode { - std::string label; - std::optional> neighbors; -}; +using GraphNode = NativeCxxModuleExampleCxxGraphNode; template <> -struct Bridging { - static GraphNode fromJs( - jsi::Runtime& rt, - const jsi::Object& value, - const std::shared_ptr& jsInvoker) { - GraphNode result{ - bridging::fromJs( - rt, value.getProperty(rt, "label"), jsInvoker), - bridging::fromJs>>( - rt, value.getProperty(rt, "neighbors"), jsInvoker)}; - return result; - } - - static jsi::Object toJs( - jsi::Runtime& rt, - const GraphNode value, - const std::shared_ptr& jsInvoker) { - auto result = facebook::jsi::Object(rt); - result.setProperty(rt, "label", bridging::toJs(rt, value.label, jsInvoker)); - if (value.neighbors) { - result.setProperty( - rt, - "neighbors", - bridging::toJs(rt, value.neighbors.value(), jsInvoker)); - } - return result; - } -}; +struct Bridging + : NativeCxxModuleExampleCxxGraphNodeBridging {}; #pragma mark - implementation class NativeCxxModuleExample