From b422375782441c5836390579d0030be94a395f0c Mon Sep 17 00:00:00 2001 From: Riccardo Cipolleschi Date: Thu, 4 May 2023 04:11:49 -0700 Subject: [PATCH] Add support for Arrays in generator (#37145) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/37145 This diff adds the generation of Array types in events. It supports the generation of Array of: - Boolean - Int32 - Float - Double - String - Objects - Array **Note:** This is a first iteration. We could improve the generation further by leveraging the `Bridging` module within React Native. I'll take a stab at it in a next diff. ## Changelog: [General][Added] - Generate events with arrays Reviewed By: cortinico Differential Revision: D45321685 fbshipit-source-id: 34b05f5501b06714d1fabf1a51cb057af7027f00 --- .../src/generators/components/CppHelpers.js | 37 ++++ .../components/GenerateEventEmitterCpp.js | 175 +++++++++++++++++- .../components/GenerateEventEmitterH.js | 54 +++++- .../components/__test_fixtures__/fixtures.js | 93 ++++++++++ .../GenerateEventEmitterCpp-test.js.snap | 64 +++++++ .../GenerateEventEmitterH-test.js.snap | 28 +++ .../GenerateViewConfigJs-test.js.snap | 8 + 7 files changed, 450 insertions(+), 9 deletions(-) diff --git a/packages/react-native-codegen/src/generators/components/CppHelpers.js b/packages/react-native-codegen/src/generators/components/CppHelpers.js index e1fbfb79dc1d7b..ccecb1bcd08073 100644 --- a/packages/react-native-codegen/src/generators/components/CppHelpers.js +++ b/packages/react-native-codegen/src/generators/components/CppHelpers.js @@ -49,6 +49,42 @@ function getCppTypeForAnnotation( } } +function getCppArrayTypeForAnnotation( + typeElement: EventTypeAnnotation, + structParts?: string[], +): string { + switch (typeElement.type) { + case 'BooleanTypeAnnotation': + case 'StringTypeAnnotation': + case 'DoubleTypeAnnotation': + case 'FloatTypeAnnotation': + case 'Int32TypeAnnotation': + case 'MixedTypeAnnotation': + return `std::vector<${getCppTypeForAnnotation(typeElement.type)}>`; + case 'StringEnumTypeAnnotation': + case 'ObjectTypeAnnotation': + if (!structParts) { + throw new Error( + `Trying to generate the event emitter for an Array of ${typeElement.type} without informations to generate the generic type`, + ); + } + return `std::vector<${generateEventStructName(structParts)}>`; + case 'ArrayTypeAnnotation': + return `std::vector<${getCppArrayTypeForAnnotation( + typeElement.elementType, + structParts, + )}>`; + default: + throw new Error( + `Can't determine array type with typeElement: ${JSON.stringify( + typeElement, + null, + 2, + )}`, + ); + } +} + function getImports( properties: | $ReadOnlyArray> @@ -222,6 +258,7 @@ function convertDefaultTypeToString( module.exports = { convertDefaultTypeToString, + getCppArrayTypeForAnnotation, getCppTypeForAnnotation, getEnumMaskName, getImports, diff --git a/packages/react-native-codegen/src/generators/components/GenerateEventEmitterCpp.js b/packages/react-native-codegen/src/generators/components/GenerateEventEmitterCpp.js index f7c76237926599..7a43aea0a2b54e 100644 --- a/packages/react-native-codegen/src/generators/components/GenerateEventEmitterCpp.js +++ b/packages/react-native-codegen/src/generators/components/GenerateEventEmitterCpp.js @@ -102,9 +102,12 @@ function generateSetter( variableName: string, propertyName: string, propertyParts: $ReadOnlyArray, + usingEvent: boolean, valueMapper: string => string = value => value, ) { - const eventChain = `$event.${[...propertyParts, propertyName].join('.')}`; + const eventChain = usingEvent + ? `$event.${[...propertyParts, propertyName].join('.')}` + : [propertyParts, propertyName].join('.'); return `${variableName}.setProperty(runtime, "${propertyName}", ${valueMapper( eventChain, )});`; @@ -116,6 +119,7 @@ function generateObjectSetter( propertyParts: $ReadOnlyArray, typeAnnotation: ObjectTypeAnnotation, extraIncludes: Set, + usingEvent: boolean, ) { return ` { @@ -126,6 +130,7 @@ function generateObjectSetter( typeAnnotation.properties, propertyParts.concat([propertyName]), extraIncludes, + usingEvent, ), 2, )} @@ -134,11 +139,165 @@ function generateObjectSetter( `.trim(); } +function setValueAtIndex( + propertyName: string, + indexVariable: string, + loopLocalVariable: string, + mappingFunction: string => string = value => value, +) { + return `${propertyName}.setValueAtIndex(runtime, ${indexVariable}++, ${mappingFunction( + loopLocalVariable, + )});`; +} + +function generateArraySetter( + variableName: string, + propertyName: string, + propertyParts: $ReadOnlyArray, + elementType: EventTypeAnnotation, + extraIncludes: Set, + usingEvent: boolean, +): string { + const eventChain = usingEvent + ? `$event.${[...propertyParts, propertyName].join('.')}` + : [propertyParts, propertyName].join('.'); + const indexVar = `${propertyName}Index`; + const innerLoopVar = `${propertyName}Value`; + return ` + auto ${propertyName} = jsi::Array(runtime, ${eventChain}.size()); + size_t ${indexVar} = 0; + for (auto ${innerLoopVar} : ${eventChain}) { + ${handleArrayElementType( + elementType, + propertyName, + indexVar, + innerLoopVar, + propertyParts, + extraIncludes, + usingEvent, + )} + } + ${variableName}.setProperty(runtime, "${propertyName}", ${propertyName}); + `; +} + +function handleArrayElementType( + elementType: EventTypeAnnotation, + propertyName: string, + indexVariable: string, + loopLocalVariable: string, + propertyParts: $ReadOnlyArray, + extraIncludes: Set, + usingEvent: boolean, +): string { + switch (elementType.type) { + case 'BooleanTypeAnnotation': + return setValueAtIndex( + propertyName, + indexVariable, + loopLocalVariable, + val => `(bool)${val}`, + ); + case 'StringTypeAnnotation': + case 'Int32TypeAnnotation': + case 'DoubleTypeAnnotation': + case 'FloatTypeAnnotation': + return setValueAtIndex(propertyName, indexVariable, loopLocalVariable); + case 'MixedTypeAnnotation': + return setValueAtIndex( + propertyName, + indexVariable, + loopLocalVariable, + val => `jsi::valueFromDynamic(runtime, ${val})`, + ); + case 'StringEnumTypeAnnotation': + return setValueAtIndex( + propertyName, + indexVariable, + loopLocalVariable, + val => `toString(${val})`, + ); + case 'ObjectTypeAnnotation': + return convertObjectTypeArray( + propertyName, + indexVariable, + loopLocalVariable, + propertyParts, + elementType, + extraIncludes, + ); + case 'ArrayTypeAnnotation': + return convertArrayTypeArray( + propertyName, + indexVariable, + loopLocalVariable, + propertyParts, + elementType, + extraIncludes, + usingEvent, + ); + default: + throw new Error( + `Received invalid elementType for array ${elementType.type}`, + ); + } +} + +function convertObjectTypeArray( + propertyName: string, + indexVariable: string, + loopLocalVariable: string, + propertyParts: $ReadOnlyArray, + objectTypeAnnotation: ObjectTypeAnnotation, + extraIncludes: Set, +): string { + return `auto ${propertyName}Object = jsi::Object(runtime); + ${generateSetters( + `${propertyName}Object`, + objectTypeAnnotation.properties, + [].concat([loopLocalVariable]), + extraIncludes, + false, + )} + ${setValueAtIndex(propertyName, indexVariable, `${propertyName}Object`)}`; +} + +function convertArrayTypeArray( + propertyName: string, + indexVariable: string, + loopLocalVariable: string, + propertyParts: $ReadOnlyArray, + eventTypeAnnotation: EventTypeAnnotation, + extraIncludes: Set, + usingEvent: boolean, +): string { + if (eventTypeAnnotation.type !== 'ArrayTypeAnnotation') { + throw new Error( + `Inconsistent eventTypeAnnotation received. Expected type = 'ArrayTypeAnnotation'; received = ${eventTypeAnnotation.type}`, + ); + } + return `auto ${propertyName}Array = jsi::Array(runtime, ${loopLocalVariable}.size()); + size_t ${indexVariable}Internal = 0; + for (auto ${loopLocalVariable}Internal : ${loopLocalVariable}) { + ${handleArrayElementType( + eventTypeAnnotation.elementType, + `${propertyName}Array`, + `${indexVariable}Internal`, + `${loopLocalVariable}Internal`, + propertyParts, + extraIncludes, + usingEvent, + )} + } + ${setValueAtIndex(propertyName, indexVariable, `${propertyName}Array`)}`; +} + function generateSetters( parentPropertyName: string, properties: $ReadOnlyArray>, propertyParts: $ReadOnlyArray, extraIncludes: Set, + usingEvent: boolean = true, ): string { const propSetters = properties .map(eventProperty => { @@ -153,6 +312,7 @@ function generateSetters( parentPropertyName, eventProperty.name, propertyParts, + usingEvent, ); case 'MixedTypeAnnotation': extraIncludes.add('#include '); @@ -160,6 +320,7 @@ function generateSetters( parentPropertyName, eventProperty.name, propertyParts, + usingEvent, prop => `jsi::valueFromDynamic(runtime, ${prop})`, ); case 'StringEnumTypeAnnotation': @@ -167,6 +328,7 @@ function generateSetters( parentPropertyName, eventProperty.name, propertyParts, + usingEvent, prop => `toString(${prop})`, ); case 'ObjectTypeAnnotation': @@ -176,10 +338,17 @@ function generateSetters( propertyParts, typeAnnotation, extraIncludes, + usingEvent, ); case 'ArrayTypeAnnotation': - // TODO: implement this in the next diff - break; + return generateArraySetter( + parentPropertyName, + eventProperty.name, + propertyParts, + typeAnnotation.elementType, + extraIncludes, + usingEvent, + ); default: (typeAnnotation.type: empty); throw new Error( diff --git a/packages/react-native-codegen/src/generators/components/GenerateEventEmitterH.js b/packages/react-native-codegen/src/generators/components/GenerateEventEmitterH.js index 0acd50d7db460b..b5d55033f063b4 100644 --- a/packages/react-native-codegen/src/generators/components/GenerateEventEmitterH.js +++ b/packages/react-native-codegen/src/generators/components/GenerateEventEmitterH.js @@ -14,6 +14,7 @@ const nullthrows = require('nullthrows'); const { getImports, + getCppArrayTypeForAnnotation, getCppTypeForAnnotation, generateEventStructName, } = require('./CppHelpers'); @@ -134,8 +135,16 @@ function getNativeTypeFromAnnotation( case 'ObjectTypeAnnotation': return generateEventStructName([...nameParts, eventProperty.name]); case 'ArrayTypeAnnotation': - // TODO: implement this in the next diff - return ''; + const eventTypeAnnotation = eventProperty.typeAnnotation; + if (eventTypeAnnotation.type !== 'ArrayTypeAnnotation') { + throw new Error( + "Inconsistent Codegen state: type was ArrayTypeAnnotation at the beginning of the body and now it isn't", + ); + } + return getCppArrayTypeForAnnotation(eventTypeAnnotation.elementType, [ + ...nameParts, + eventProperty.name, + ]); default: (type: empty); throw new Error(`Received invalid event property type ${type}`); @@ -168,6 +177,33 @@ function generateEnum( ); } +function handleGenerateStructForArray( + structs: StructsMap, + name: string, + componentName: string, + elementType: EventTypeAnnotation, + nameParts: $ReadOnlyArray, +): void { + if (elementType.type === 'ObjectTypeAnnotation') { + generateStruct( + structs, + componentName, + nameParts.concat([name]), + nullthrows(elementType.properties), + ); + } else if (elementType.type === 'StringEnumTypeAnnotation') { + generateEnum(structs, elementType.options, nameParts.concat([name])); + } else if (elementType.type === 'ArrayTypeAnnotation') { + handleGenerateStructForArray( + structs, + name, + componentName, + elementType.elementType, + nameParts, + ); + } +} + function generateStruct( structs: StructsMap, componentName: string, @@ -197,6 +233,15 @@ function generateStruct( case 'FloatTypeAnnotation': case 'MixedTypeAnnotation': return; + case 'ArrayTypeAnnotation': + handleGenerateStructForArray( + structs, + name, + componentName, + typeAnnotation.elementType, + nameParts, + ); + return; case 'ObjectTypeAnnotation': generateStruct( structs, @@ -208,9 +253,6 @@ function generateStruct( case 'StringEnumTypeAnnotation': generateEnum(structs, typeAnnotation.options, nameParts.concat([name])); return; - case 'ArrayTypeAnnotation': - //TODO: implement this in the next diff - break; default: (typeAnnotation.type: empty); throw new Error( @@ -277,7 +319,7 @@ module.exports = { .map(moduleName => { const module = schema.modules[moduleName]; if (module.type !== 'Component') { - return; + return null; } const {components} = module; diff --git a/packages/react-native-codegen/src/generators/components/__test_fixtures__/fixtures.js b/packages/react-native-codegen/src/generators/components/__test_fixtures__/fixtures.js index e5401bb4e7146c..65f7a46d25ba2c 100644 --- a/packages/react-native-codegen/src/generators/components/__test_fixtures__/fixtures.js +++ b/packages/react-native-codegen/src/generators/components/__test_fixtures__/fixtures.js @@ -1206,6 +1206,99 @@ const EVENT_PROPS: SchemaType = { }, }, }, + { + name: 'onArrayEventType', + optional: true, + bubblingType: 'bubble', + typeAnnotation: { + type: 'EventTypeAnnotation', + argument: { + type: 'ObjectTypeAnnotation', + properties: [ + { + name: 'bool_array_event_prop', + optional: false, + typeAnnotation: { + type: 'ArrayTypeAnnotation', + elementType: { + type: 'BooleanTypeAnnotation', + }, + }, + }, + { + name: 'string_enum_event_prop', + optional: false, + typeAnnotation: { + type: 'ArrayTypeAnnotation', + elementType: { + type: 'StringEnumTypeAnnotation', + options: ['YES', 'NO'], + }, + }, + }, + { + name: 'array_array_event_prop', + optional: false, + typeAnnotation: { + type: 'ArrayTypeAnnotation', + elementType: { + type: 'ArrayTypeAnnotation', + elementType: { + type: 'StringTypeAnnotation', + }, + }, + }, + }, + { + name: 'array_object_event_prop', + optional: false, + typeAnnotation: { + type: 'ArrayTypeAnnotation', + elementType: { + type: 'ObjectTypeAnnotation', + properties: [ + { + name: 'lat', + optional: false, + typeAnnotation: { + type: 'DoubleTypeAnnotation', + }, + }, + { + name: 'lon', + optional: false, + typeAnnotation: { + type: 'DoubleTypeAnnotation', + }, + }, + { + name: 'names', + optional: false, + typeAnnotation: { + type: 'ArrayTypeAnnotation', + elementType: { + type: 'StringTypeAnnotation', + }, + }, + }, + ], + }, + }, + }, + { + name: 'array_mixed_event_prop', + optional: false, + typeAnnotation: { + type: 'ArrayTypeAnnotation', + elementType: { + type: 'MixedTypeAnnotation', + }, + }, + }, + ], + }, + }, + }, { name: 'onEventDirect', optional: true, diff --git a/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateEventEmitterCpp-test.js.snap b/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateEventEmitterCpp-test.js.snap index 68fc88ff7a1818..2ec6051506eab0 100644 --- a/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateEventEmitterCpp-test.js.snap +++ b/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateEventEmitterCpp-test.js.snap @@ -264,6 +264,70 @@ $payload.setProperty(runtime, \\"scale\\", $event.scale); } +void EventsNativeComponentEventEmitter::onArrayEventType(OnArrayEventType $event) const { + dispatchEvent(\\"arrayEventType\\", [$event=std::move($event)](jsi::Runtime &runtime) { + auto $payload = jsi::Object(runtime); + + auto bool_array_event_prop = jsi::Array(runtime, $event.bool_array_event_prop.size()); + size_t bool_array_event_propIndex = 0; + for (auto bool_array_event_propValue : $event.bool_array_event_prop) { + bool_array_event_prop.setValueAtIndex(runtime, bool_array_event_propIndex++, (bool)bool_array_event_propValue); + } + $payload.setProperty(runtime, \\"bool_array_event_prop\\", bool_array_event_prop); + + + auto string_enum_event_prop = jsi::Array(runtime, $event.string_enum_event_prop.size()); + size_t string_enum_event_propIndex = 0; + for (auto string_enum_event_propValue : $event.string_enum_event_prop) { + string_enum_event_prop.setValueAtIndex(runtime, string_enum_event_propIndex++, toString(string_enum_event_propValue)); + } + $payload.setProperty(runtime, \\"string_enum_event_prop\\", string_enum_event_prop); + + + auto array_array_event_prop = jsi::Array(runtime, $event.array_array_event_prop.size()); + size_t array_array_event_propIndex = 0; + for (auto array_array_event_propValue : $event.array_array_event_prop) { + auto array_array_event_propArray = jsi::Array(runtime, array_array_event_propValue.size()); + size_t array_array_event_propIndexInternal = 0; + for (auto array_array_event_propValueInternal : array_array_event_propValue) { + array_array_event_propArray.setValueAtIndex(runtime, array_array_event_propIndexInternal++, array_array_event_propValueInternal); + } + array_array_event_prop.setValueAtIndex(runtime, array_array_event_propIndex++, array_array_event_propArray); + } + $payload.setProperty(runtime, \\"array_array_event_prop\\", array_array_event_prop); + + + auto array_object_event_prop = jsi::Array(runtime, $event.array_object_event_prop.size()); + size_t array_object_event_propIndex = 0; + for (auto array_object_event_propValue : $event.array_object_event_prop) { + auto array_object_event_propObject = jsi::Object(runtime); + array_object_event_propObject.setProperty(runtime, \\"lat\\", array_object_event_propValue.lat); +array_object_event_propObject.setProperty(runtime, \\"lon\\", array_object_event_propValue.lon); + + auto names = jsi::Array(runtime, array_object_event_propValue.names.size()); + size_t namesIndex = 0; + for (auto namesValue : array_object_event_propValue.names) { + names.setValueAtIndex(runtime, namesIndex++, namesValue); + } + array_object_event_propObject.setProperty(runtime, \\"names\\", names); + + array_object_event_prop.setValueAtIndex(runtime, array_object_event_propIndex++, array_object_event_propObject); + } + $payload.setProperty(runtime, \\"array_object_event_prop\\", array_object_event_prop); + + + auto array_mixed_event_prop = jsi::Array(runtime, $event.array_mixed_event_prop.size()); + size_t array_mixed_event_propIndex = 0; + for (auto array_mixed_event_propValue : $event.array_mixed_event_prop) { + array_mixed_event_prop.setValueAtIndex(runtime, array_mixed_event_propIndex++, jsi::valueFromDynamic(runtime, array_mixed_event_propValue)); + } + $payload.setProperty(runtime, \\"array_mixed_event_prop\\", array_mixed_event_prop); + + return $payload; + }); +} + + void EventsNativeComponentEventEmitter::onEventDirect(OnEventDirect $event) const { dispatchEvent(\\"eventDirect\\", [$event=std::move($event)](jsi::Runtime &runtime) { auto $payload = jsi::Object(runtime); diff --git a/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateEventEmitterH-test.js.snap b/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateEventEmitterH-test.js.snap index 31ee002d234a0c..ca4237ab4d7474 100644 --- a/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateEventEmitterH-test.js.snap +++ b/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateEventEmitterH-test.js.snap @@ -320,6 +320,32 @@ class EventsNativeComponentEventEmitter : public ViewEventEmitter { Float scale; }; + enum class OnArrayEventTypeString_enum_event_prop { + YES, + NO + }; + + static char const *toString(const OnArrayEventTypeString_enum_event_prop value) { + switch (value) { + case OnArrayEventTypeString_enum_event_prop::YES: return \\"YES\\"; + case OnArrayEventTypeString_enum_event_prop::NO: return \\"NO\\"; + } + } + + struct OnArrayEventTypeArray_object_event_prop { + double lat; + double lon; + std::vector names; + }; + + struct OnArrayEventType { + std::vector bool_array_event_prop; + std::vector string_enum_event_prop; + std::vector> array_array_event_prop; + std::vector array_object_event_prop; + std::vector array_mixed_event_prop; + }; + struct OnEventDirect { bool value; }; @@ -345,6 +371,8 @@ class EventsNativeComponentEventEmitter : public ViewEventEmitter { }; void onChange(OnChange value) const; + void onArrayEventType(OnArrayEventType value) const; + void onEventDirect(OnEventDirect value) const; void onOrientationChange(OnOrientationChange value) const; diff --git a/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateViewConfigJs-test.js.snap b/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateViewConfigJs-test.js.snap index bc616a220e0b6a..e88cd1abe901cc 100644 --- a/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateViewConfigJs-test.js.snap +++ b/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateViewConfigJs-test.js.snap @@ -394,6 +394,13 @@ export const __INTERNAL_VIEW_CONFIG = { }, }, + topArrayEventType: { + phasedRegistrationNames: { + captured: 'onArrayEventTypeCapture', + bubbled: 'onArrayEventType', + }, + }, + topEnd: { phasedRegistrationNames: { captured: 'onEndCapture', @@ -421,6 +428,7 @@ export const __INTERNAL_VIEW_CONFIG = { ...ConditionallyIgnoredEventHandlers({ onChange: true, + onArrayEventType: true, onEventDirect: true, onOrientationChange: true, onEnd: true,