Skip to content

Commit

Permalink
Add support for Arrays in generator (facebook#37145)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: facebook#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

Differential Revision: D45321685

fbshipit-source-id: 2544ad151738fd4ba185b20fe17daa09b8d98d48
  • Loading branch information
cipolleschi authored and facebook-github-bot committed May 2, 2023
1 parent dd5ec6b commit 6ffb63e
Show file tree
Hide file tree
Showing 7 changed files with 450 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<NamedShape<PropTypeAnnotation>>
Expand Down Expand Up @@ -222,6 +258,7 @@ function convertDefaultTypeToString(

module.exports = {
convertDefaultTypeToString,
getCppArrayTypeForAnnotation,
getCppTypeForAnnotation,
getEnumMaskName,
getImports,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,12 @@ function generateSetter(
variableName: string,
propertyName: string,
propertyParts: $ReadOnlyArray<string>,
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,
)});`;
Expand All @@ -116,6 +119,7 @@ function generateObjectSetter(
propertyParts: $ReadOnlyArray<string>,
typeAnnotation: ObjectTypeAnnotation<EventTypeAnnotation>,
extraIncludes: Set<string>,
usingEvent: boolean,
) {
return `
{
Expand All @@ -126,6 +130,7 @@ function generateObjectSetter(
typeAnnotation.properties,
propertyParts.concat([propertyName]),
extraIncludes,
usingEvent,
),
2,
)}
Expand All @@ -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<string>,
elementType: EventTypeAnnotation,
extraIncludes: Set<string>,
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<string>,
extraIncludes: Set<string>,
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<string>,
objectTypeAnnotation: ObjectTypeAnnotation<EventTypeAnnotation>,
extraIncludes: Set<string>,
): 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<string>,
eventTypeAnnotation: EventTypeAnnotation,
extraIncludes: Set<string>,
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<NamedShape<EventTypeAnnotation>>,
propertyParts: $ReadOnlyArray<string>,
extraIncludes: Set<string>,
usingEvent: boolean = true,
): string {
const propSetters = properties
.map(eventProperty => {
Expand All @@ -153,20 +312,23 @@ function generateSetters(
parentPropertyName,
eventProperty.name,
propertyParts,
usingEvent,
);
case 'MixedTypeAnnotation':
extraIncludes.add('#include <jsi/JSIDynamic.h>');
return generateSetter(
parentPropertyName,
eventProperty.name,
propertyParts,
usingEvent,
prop => `jsi::valueFromDynamic(runtime, ${prop})`,
);
case 'StringEnumTypeAnnotation':
return generateSetter(
parentPropertyName,
eventProperty.name,
propertyParts,
usingEvent,
prop => `toString(${prop})`,
);
case 'ObjectTypeAnnotation':
Expand All @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const nullthrows = require('nullthrows');

const {
getImports,
getCppArrayTypeForAnnotation,
getCppTypeForAnnotation,
generateEventStructName,
} = require('./CppHelpers');
Expand Down Expand Up @@ -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}`);
Expand Down Expand Up @@ -168,6 +177,33 @@ function generateEnum(
);
}

function handleGenerateStructForArray(
structs: StructsMap,
name: string,
componentName: string,
elementType: EventTypeAnnotation,
nameParts: $ReadOnlyArray<string>,
): 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,
Expand Down Expand Up @@ -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,
Expand All @@ -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(
Expand Down Expand Up @@ -277,7 +319,7 @@ module.exports = {
.map(moduleName => {
const module = schema.modules[moduleName];
if (module.type !== 'Component') {
return;
return null;
}

const {components} = module;
Expand Down
Loading

0 comments on commit 6ffb63e

Please sign in to comment.