From ead73de464c03afa8e61d830c6ba10d51dee0a10 Mon Sep 17 00:00:00 2001 From: Christoph Purrer Date: Mon, 4 Dec 2023 05:54:50 -0800 Subject: [PATCH] Add a BinaryTreeNode example for Cxx TMs (#41767) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/41767 Changelog: [Internal] Adds a simple example showing a direct recursive node in a Cxx TM. Currently we can't auto-generate [the necessary C++ Types](https://reactnative.dev/docs/next/the-new-architecture/cxx-custom-types#struct-generator) - but we can add it later if this scenarios becomes really common. Direct recursive nodes, can't be value types - it would require infinite memory. Hence they are nullable and managed by a smart pointer. Reviewed By: rshest Differential Revision: D51784136 fbshipit-source-id: f6f0710d03583bdf1e6e72ba42d8df7f8ff8d915 --- .../parsers/__tests__/parsers-commons-test.js | 8 +- .../modules/__test_fixtures__/fixtures.js | 13 +++ .../module-parser-snapshot-test.js.snap | 96 ++++++++++++++++++- .../src/parsers/flow/modules/index.js | 1 + .../src/parsers/parsers-commons.js | 29 ++++++ .../modules/__test_fixtures__/fixtures.js | 13 +++ ...script-module-parser-snapshot-test.js.snap | 96 ++++++++++++++++++- .../src/parsers/typescript/modules/index.js | 5 + .../NativeCxxModuleExample.cpp | 6 ++ .../NativeCxxModuleExample.h | 46 +++++++++ .../NativeCxxModuleExample.js | 7 ++ .../NativeCxxModuleExampleExample.js | 7 ++ 12 files changed, 322 insertions(+), 5 deletions(-) diff --git a/packages/react-native-codegen/src/parsers/__tests__/parsers-commons-test.js b/packages/react-native-codegen/src/parsers/__tests__/parsers-commons-test.js index 66ab3a1369025b..44e25e7687648e 100644 --- a/packages/react-native-codegen/src/parsers/__tests__/parsers-commons-test.js +++ b/packages/react-native-codegen/src/parsers/__tests__/parsers-commons-test.js @@ -322,6 +322,7 @@ describe('parseObjectProperty', () => { ); expect(() => parseObjectProperty( + null, // parentObject property, moduleName, types, @@ -356,6 +357,7 @@ describe('parseObjectProperty', () => { ); expect(() => parseObjectProperty( + null, // parentObject property, moduleName, types, @@ -1139,7 +1141,7 @@ describe('buildModuleSchema', () => { const contents = ` import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; - + export interface Spec extends TurboModule { +getBool: (arg: boolean) => boolean; } export interface SpecOther extends TurboModule { @@ -1189,11 +1191,11 @@ describe('buildModuleSchema', () => { const contents = ` import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport'; import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry'; - + export interface MisnamedSpec extends TurboModule { +getArray: (a: Array) => Array; } - + export default (TurboModuleRegistry.getEnforcing( 'SampleTurboModule', ): Spec); diff --git a/packages/react-native-codegen/src/parsers/flow/modules/__test_fixtures__/fixtures.js b/packages/react-native-codegen/src/parsers/flow/modules/__test_fixtures__/fixtures.js index a7a81e017831bc..c661f988517c60 100644 --- a/packages/react-native-codegen/src/parsers/flow/modules/__test_fixtures__/fixtures.js +++ b/packages/react-native-codegen/src/parsers/flow/modules/__test_fixtures__/fixtures.js @@ -731,10 +731,23 @@ export enum StringOptions { Three = 'three', } +export type BinaryTreeNode = { + left?: BinaryTreeNode, + value: number, + right?: BinaryTreeNode, +}; + +export type GraphNode = { + label: string, + neighbors?: Array, +}; + export interface Spec extends TurboModule { +getCallback: () => () => void; +getMixed: (arg: mixed) => mixed; +getEnums: (quality: Quality, resolution?: Resolution, floppy: Floppy, stringOptions: StringOptions) => string; + +getBinaryTreeNode: (arg: BinaryTreeNode) => BinaryTreeNode; + +getGraphNode: (arg: GraphNode) => GraphNode; +getMap: (arg: {[a: string]: ?number}) => {[b: string]: ?number}; +getAnotherMap: (arg: {[string]: string}) => {[string]: string}; +getUnion: (chooseInt: ChooseInt, chooseFloat: ChooseFloat, chooseObject: ChooseObject, chooseString: ChooseString) => ChooseObject; diff --git a/packages/react-native-codegen/src/parsers/flow/modules/__tests__/__snapshots__/module-parser-snapshot-test.js.snap b/packages/react-native-codegen/src/parsers/flow/modules/__tests__/__snapshots__/module-parser-snapshot-test.js.snap index 43210001dc7759..64e6ed07e66b71 100644 --- a/packages/react-native-codegen/src/parsers/flow/modules/__tests__/__snapshots__/module-parser-snapshot-test.js.snap +++ b/packages/react-native-codegen/src/parsers/flow/modules/__tests__/__snapshots__/module-parser-snapshot-test.js.snap @@ -51,7 +51,59 @@ exports[`RN Codegen Flow Parser can generate fixture CXX_ONLY_NATIVE_MODULE 1`] 'modules': { 'NativeSampleTurboModule': { 'type': 'NativeModule', - 'aliasMap': {}, + 'aliasMap': { + 'BinaryTreeNode': { + 'type': 'ObjectTypeAnnotation', + 'properties': [ + { + 'name': 'left', + 'optional': true, + 'typeAnnotation': { + 'type': 'TypeAliasTypeAnnotation', + 'name': 'BinaryTreeNode' + } + }, + { + 'name': 'value', + 'optional': false, + 'typeAnnotation': { + 'type': 'NumberTypeAnnotation' + } + }, + { + 'name': 'right', + 'optional': true, + 'typeAnnotation': { + 'type': 'TypeAliasTypeAnnotation', + 'name': 'BinaryTreeNode' + } + } + ] + }, + 'GraphNode': { + 'type': 'ObjectTypeAnnotation', + 'properties': [ + { + 'name': 'label', + 'optional': false, + 'typeAnnotation': { + 'type': 'StringTypeAnnotation' + } + }, + { + 'name': 'neighbors', + 'optional': true, + 'typeAnnotation': { + 'type': 'ArrayTypeAnnotation', + 'elementType': { + 'type': 'TypeAliasTypeAnnotation', + 'name': 'GraphNode' + } + } + } + ] + } + }, 'enumMap': { 'Quality': { 'name': 'Quality', @@ -202,6 +254,48 @@ exports[`RN Codegen Flow Parser can generate fixture CXX_ONLY_NATIVE_MODULE 1`] ] } }, + { + 'name': 'getBinaryTreeNode', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'TypeAliasTypeAnnotation', + 'name': 'BinaryTreeNode' + }, + 'params': [ + { + 'name': 'arg', + 'optional': false, + 'typeAnnotation': { + 'type': 'TypeAliasTypeAnnotation', + 'name': 'BinaryTreeNode' + } + } + ] + } + }, + { + 'name': 'getGraphNode', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'TypeAliasTypeAnnotation', + 'name': 'GraphNode' + }, + 'params': [ + { + 'name': 'arg', + 'optional': false, + 'typeAnnotation': { + 'type': 'TypeAliasTypeAnnotation', + 'name': 'GraphNode' + } + } + ] + } + }, { 'name': 'getMap', 'optional': false, diff --git a/packages/react-native-codegen/src/parsers/flow/modules/index.js b/packages/react-native-codegen/src/parsers/flow/modules/index.js index 99550b2da02bd6..93e68955323960 100644 --- a/packages/react-native-codegen/src/parsers/flow/modules/index.js +++ b/packages/react-native-codegen/src/parsers/flow/modules/index.js @@ -176,6 +176,7 @@ function translateTypeAnnotation( property => { return tryParse(() => { return parseObjectProperty( + flowTypeAnnotation, property, hasteModuleName, types, diff --git a/packages/react-native-codegen/src/parsers/parsers-commons.js b/packages/react-native-codegen/src/parsers/parsers-commons.js index 65832cf66a0890..82c78afb87530a 100644 --- a/packages/react-native-codegen/src/parsers/parsers-commons.js +++ b/packages/react-native-codegen/src/parsers/parsers-commons.js @@ -171,6 +171,7 @@ function isObjectProperty(property: $FlowFixMe, language: ParserType): boolean { } function parseObjectProperty( + parentObject?: $FlowFixMe, property: $FlowFixMe, hasteModuleName: string, types: TypeDeclarationMap, @@ -191,6 +192,34 @@ function parseObjectProperty( ? property.typeAnnotation.typeAnnotation : property.value; + // Handle recursive types + if (parentObject) { + const propertyType = parser.getResolveTypeAnnotationFN()( + languageTypeAnnotation, + types, + parser, + ); + if ( + propertyType.typeResolutionStatus.successful === true && + propertyType.typeResolutionStatus.type === 'alias' && + (language === 'TypeScript' + ? parentObject.typeName && + parentObject.typeName.name === languageTypeAnnotation.typeName?.name + : parentObject.id && + parentObject.id.name === languageTypeAnnotation.id?.name) + ) { + return { + name, + optional, + typeAnnotation: { + type: 'TypeAliasTypeAnnotation', + name: propertyType.typeResolutionStatus.name, + }, + }; + } + } + + // Handle non-recursive types const [propertyTypeAnnotation, isPropertyNullable] = unwrapNullable<$FlowFixMe>( translateTypeAnnotation( diff --git a/packages/react-native-codegen/src/parsers/typescript/modules/__test_fixtures__/fixtures.js b/packages/react-native-codegen/src/parsers/typescript/modules/__test_fixtures__/fixtures.js index 5f6377869afb1d..09a26837f09339 100644 --- a/packages/react-native-codegen/src/parsers/typescript/modules/__test_fixtures__/fixtures.js +++ b/packages/react-native-codegen/src/parsers/typescript/modules/__test_fixtures__/fixtures.js @@ -825,10 +825,23 @@ export type ChooseFloat = 1.44 | 2.88 | 5.76; export type ChooseObject = {} | {low: string}; export type ChooseString = 'One' | 'Two' | 'Three'; +export type BinaryTreeNode = { + left?: BinaryTreeNode, + value: number, + right?: BinaryTreeNode, +}; + +export type GraphNode = { + label: string, + neighbors?: Array, +}; + export interface Spec extends TurboModule { readonly getCallback: () => () => void; readonly getMixed: (arg: unknown) => unknown; readonly getEnums: (quality: Quality, resolution?: Resolution, floppy: Floppy, stringOptions: StringOptions) => string; + readonly getBinaryTreeNode: (arg: BinaryTreeNode) => BinaryTreeNode; + readonly getGraphNode: (arg: GraphNode) => GraphNode; readonly getMap: (arg: {[a: string]: number | null;}) => {[b: string]: number | null;}; readonly getAnotherMap: (arg: {[key: string]: string}) => {[key: string]: string}; readonly getUnion: (chooseInt: ChooseInt, chooseFloat: ChooseFloat, chooseObject: ChooseObject, chooseString: ChooseString) => ChooseObject; diff --git a/packages/react-native-codegen/src/parsers/typescript/modules/__tests__/__snapshots__/typescript-module-parser-snapshot-test.js.snap b/packages/react-native-codegen/src/parsers/typescript/modules/__tests__/__snapshots__/typescript-module-parser-snapshot-test.js.snap index 870a93494166d8..e18ec2843dd1fb 100644 --- a/packages/react-native-codegen/src/parsers/typescript/modules/__tests__/__snapshots__/typescript-module-parser-snapshot-test.js.snap +++ b/packages/react-native-codegen/src/parsers/typescript/modules/__tests__/__snapshots__/typescript-module-parser-snapshot-test.js.snap @@ -42,7 +42,59 @@ exports[`RN Codegen TypeScript Parser can generate fixture CXX_ONLY_NATIVE_MODUL 'modules': { 'NativeSampleTurboModule': { 'type': 'NativeModule', - 'aliasMap': {}, + 'aliasMap': { + 'BinaryTreeNode': { + 'type': 'ObjectTypeAnnotation', + 'properties': [ + { + 'name': 'left', + 'optional': true, + 'typeAnnotation': { + 'type': 'TypeAliasTypeAnnotation', + 'name': 'BinaryTreeNode' + } + }, + { + 'name': 'value', + 'optional': false, + 'typeAnnotation': { + 'type': 'NumberTypeAnnotation' + } + }, + { + 'name': 'right', + 'optional': true, + 'typeAnnotation': { + 'type': 'TypeAliasTypeAnnotation', + 'name': 'BinaryTreeNode' + } + } + ] + }, + 'GraphNode': { + 'type': 'ObjectTypeAnnotation', + 'properties': [ + { + 'name': 'label', + 'optional': false, + 'typeAnnotation': { + 'type': 'StringTypeAnnotation' + } + }, + { + 'name': 'neighbors', + 'optional': true, + 'typeAnnotation': { + 'type': 'ArrayTypeAnnotation', + 'elementType': { + 'type': 'TypeAliasTypeAnnotation', + 'name': 'GraphNode' + } + } + } + ] + } + }, 'enumMap': { 'Quality': { 'name': 'Quality', @@ -193,6 +245,48 @@ exports[`RN Codegen TypeScript Parser can generate fixture CXX_ONLY_NATIVE_MODUL ] } }, + { + 'name': 'getBinaryTreeNode', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'TypeAliasTypeAnnotation', + 'name': 'BinaryTreeNode' + }, + 'params': [ + { + 'name': 'arg', + 'optional': false, + 'typeAnnotation': { + 'type': 'TypeAliasTypeAnnotation', + 'name': 'BinaryTreeNode' + } + } + ] + } + }, + { + 'name': 'getGraphNode', + 'optional': false, + 'typeAnnotation': { + 'type': 'FunctionTypeAnnotation', + 'returnTypeAnnotation': { + 'type': 'TypeAliasTypeAnnotation', + 'name': 'GraphNode' + }, + 'params': [ + { + 'name': 'arg', + 'optional': false, + 'typeAnnotation': { + 'type': 'TypeAliasTypeAnnotation', + 'name': 'GraphNode' + } + } + ] + } + }, { 'name': 'getMap', 'optional': false, diff --git a/packages/react-native-codegen/src/parsers/typescript/modules/index.js b/packages/react-native-codegen/src/parsers/typescript/modules/index.js index 060ee2f5c23e53..0b92061702db1b 100644 --- a/packages/react-native-codegen/src/parsers/typescript/modules/index.js +++ b/packages/react-native-codegen/src/parsers/typescript/modules/index.js @@ -50,6 +50,7 @@ function translateObjectTypeAnnotation( /** * TODO(T108222691): Use flow-types for @babel/parser */ + typeScriptTypeAnnotation: $FlowFixMe, nullable: boolean, objectMembers: $ReadOnlyArray<$FlowFixMe>, typeResolutionStatus: TypeResolutionStatus, @@ -66,6 +67,7 @@ function translateObjectTypeAnnotation( .map>>(property => { return tryParse(() => { return parseObjectProperty( + typeScriptTypeAnnotation, property, hasteModuleName, types, @@ -266,6 +268,7 @@ function translateTypeAnnotation( return translateObjectTypeAnnotation( hasteModuleName, + typeScriptTypeAnnotation, nullable, flattenProperties([typeAnnotation], types, parser), typeResolutionStatus, @@ -281,6 +284,7 @@ function translateTypeAnnotation( case 'TSIntersectionType': { return translateObjectTypeAnnotation( hasteModuleName, + typeScriptTypeAnnotation, nullable, flattenProperties( flattenIntersectionType(typeAnnotation, types), @@ -324,6 +328,7 @@ function translateTypeAnnotation( return translateObjectTypeAnnotation( hasteModuleName, + typeScriptTypeAnnotation, nullable, typeAnnotation.members, typeResolutionStatus, diff --git a/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.cpp b/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.cpp index 29a547955154ed..2fbebf8eae088f 100644 --- a/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.cpp +++ b/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.cpp @@ -65,6 +65,12 @@ std::string NativeCxxModuleExample::consumeCustomHostObject( return value->a_ + std::to_string(value->b_); } +BinaryTreeNode NativeCxxModuleExample::getBinaryTreeNode( + jsi::Runtime& rt, + BinaryTreeNode arg) { + return arg; +} + GraphNode NativeCxxModuleExample::getGraphNode( jsi::Runtime& rt, GraphNode arg) { diff --git a/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.h b/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.h index 10fe5d50f7cd16..15c7e7209133eb 100644 --- a/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.h +++ b/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.h @@ -91,6 +91,50 @@ struct CustomHostObjectRef { using CustomHostObject = HostObjectWrapper; #pragma mark - recursive objects +struct BinaryTreeNode { + std::unique_ptr left; + int32_t value; + std::unique_ptr right; +}; + +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; + } + + 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; @@ -156,6 +200,8 @@ class NativeCxxModuleExample jsi::Runtime& rt, std::shared_ptr arg); + BinaryTreeNode getBinaryTreeNode(jsi::Runtime& rt, BinaryTreeNode arg); + GraphNode getGraphNode(jsi::Runtime& rt, GraphNode arg); NativeCxxModuleExampleCxxEnumFloat getNumEnum( diff --git a/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.js b/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.js index 0ce33d72b4f16d..e4a6d6c100f7d6 100644 --- a/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.js +++ b/packages/rn-tester/NativeCxxModuleExample/NativeCxxModuleExample.js @@ -56,6 +56,12 @@ export type ValueStruct = {| export type CustomHostObject = {}; +export type BinaryTreeNode = { + left?: BinaryTreeNode, + value: number, + right?: BinaryTreeNode, +}; + export type GraphNode = { label: string, neighbors?: Array, @@ -68,6 +74,7 @@ export interface Spec extends TurboModule { +getCustomEnum: (arg: EnumInt) => EnumInt; +getCustomHostObject: () => CustomHostObject; +consumeCustomHostObject: (customHostObject: CustomHostObject) => string; + +getBinaryTreeNode: (arg: BinaryTreeNode) => BinaryTreeNode; +getGraphNode: (arg: GraphNode) => GraphNode; +getNumEnum: (arg: EnumInt) => EnumFloat; +getStrEnum: (arg: EnumNone) => EnumStr; diff --git a/packages/rn-tester/js/examples/TurboModule/NativeCxxModuleExampleExample.js b/packages/rn-tester/js/examples/TurboModule/NativeCxxModuleExampleExample.js index fe1567344c0425..2564e0e07f0c3a 100644 --- a/packages/rn-tester/js/examples/TurboModule/NativeCxxModuleExampleExample.js +++ b/packages/rn-tester/js/examples/TurboModule/NativeCxxModuleExampleExample.js @@ -43,6 +43,7 @@ type Examples = | 'getConstants' | 'getCustomEnum' | 'getCustomHostObject' + | 'getBinaryTreeNode' | 'getGraphNode' | 'getNumEnum' | 'getStrEnum' @@ -104,6 +105,12 @@ class NativeCxxModuleExampleExample extends React.Component<{||}, State> { NativeCxxModuleExample?.consumeCustomHostObject( NativeCxxModuleExample?.getCustomHostObject(), ), + getBinaryTreeNode: () => + NativeCxxModuleExample?.getBinaryTreeNode({ + left: {value: 1}, + value: 0, + right: {value: 2}, + }), getGraphNode: () => NativeCxxModuleExample?.getGraphNode({ label: 'root',