Skip to content

Commit d241fed

Browse files
Add support for Unions in react-native-codegen & Compat Checker (facebook#54489)
Summary: Pull Request resolved: facebook#54489 Adding support for Unions in react-native-codegen & Compat Checker so that they can allow non-homogenous types and un-special casing StringLiteralUnionTypeAnnotation. Changelog: [Internal] Differential Revision: D86501597
1 parent b0e754b commit d241fed

File tree

52 files changed

+3450
-1747
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+3450
-1747
lines changed

packages/react-native-codegen/src/CodegenSchema.d.ts

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -318,10 +318,8 @@ export interface StringLiteralTypeAnnotation {
318318
readonly value: string;
319319
}
320320

321-
export interface StringLiteralUnionTypeAnnotation {
322-
readonly type: 'StringLiteralUnionTypeAnnotation';
323-
readonly types: StringLiteralTypeAnnotation[];
324-
}
321+
export type StringLiteralUnionTypeAnnotation =
322+
UnionTypeAnnotation<StringLiteralTypeAnnotation>;
325323

326324
export interface NativeModuleNumberTypeAnnotation {
327325
readonly type: 'NumberTypeAnnotation';
@@ -383,11 +381,6 @@ export interface NativeModulePromiseTypeAnnotation {
383381
readonly elementType: Nullable<NativeModuleBaseTypeAnnotation> | VoidTypeAnnotation;
384382
}
385383

386-
export type UnionTypeAnnotationMemberType =
387-
| 'NumberTypeAnnotation'
388-
| 'ObjectTypeAnnotation'
389-
| 'StringTypeAnnotation';
390-
391384
export type NativeModuleUnionTypeAnnotationMemberType =
392385
| NativeModuleObjectTypeAnnotation
393386
| StringLiteralTypeAnnotation
@@ -397,10 +390,8 @@ export type NativeModuleUnionTypeAnnotationMemberType =
397390
| StringTypeAnnotation
398391
| NumberTypeAnnotation;
399392

400-
export interface NativeModuleUnionTypeAnnotation {
401-
readonly type: 'UnionTypeAnnotation';
402-
readonly memberType: UnionTypeAnnotationMemberType;
403-
}
393+
export type NativeModuleUnionTypeAnnotation =
394+
UnionTypeAnnotation<NativeModuleUnionTypeAnnotationMemberType>;
404395

405396
export interface NativeModuleMixedTypeAnnotation {
406397
readonly type: 'MixedTypeAnnotation';

packages/react-native-codegen/src/CodegenSchema.js

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,8 @@ export type BooleanLiteralTypeAnnotation = $ReadOnly<{
6161
value: boolean,
6262
}>;
6363

64-
export type StringLiteralUnionTypeAnnotation = $ReadOnly<{
65-
type: 'StringLiteralUnionTypeAnnotation',
66-
types: $ReadOnlyArray<StringLiteralTypeAnnotation>,
67-
}>;
64+
export type StringLiteralUnionTypeAnnotation =
65+
UnionTypeAnnotation<StringLiteralTypeAnnotation>;
6866

6967
export type VoidTypeAnnotation = $ReadOnly<{
7068
type: 'VoidTypeAnnotation',
@@ -372,11 +370,6 @@ export type NativeModulePromiseTypeAnnotation = $ReadOnly<{
372370
elementType: VoidTypeAnnotation | Nullable<NativeModuleBaseTypeAnnotation>,
373371
}>;
374372

375-
export type UnionTypeAnnotationMemberType =
376-
| 'NumberTypeAnnotation'
377-
| 'ObjectTypeAnnotation'
378-
| 'StringTypeAnnotation';
379-
380373
export type NativeModuleUnionTypeAnnotationMemberType =
381374
| NativeModuleObjectTypeAnnotation
382375
| StringLiteralTypeAnnotation
@@ -386,10 +379,8 @@ export type NativeModuleUnionTypeAnnotationMemberType =
386379
| StringTypeAnnotation
387380
| NumberTypeAnnotation;
388381

389-
export type NativeModuleUnionTypeAnnotation = $ReadOnly<{
390-
type: 'UnionTypeAnnotation',
391-
memberType: UnionTypeAnnotationMemberType,
392-
}>;
382+
export type NativeModuleUnionTypeAnnotation =
383+
UnionTypeAnnotation<NativeModuleUnionTypeAnnotationMemberType>;
393384

394385
export type NativeModuleMixedTypeAnnotation = $ReadOnly<{
395386
type: 'MixedTypeAnnotation',
@@ -473,6 +464,7 @@ export type CompleteTypeAnnotation =
473464
| UnsafeAnyTypeAnnotation
474465
| ArrayTypeAnnotation<CompleteTypeAnnotation>
475466
| ObjectTypeAnnotation<CompleteTypeAnnotation>
467+
| NativeModuleUnionTypeAnnotationMemberType
476468
// Components
477469
| CommandTypeAnnotation
478470
| CompleteReservedTypeAnnotation;

packages/react-native-codegen/src/generators/Utils.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
'use strict';
1212

13+
import type {NativeModuleUnionTypeAnnotation} from '../CodegenSchema';
14+
1315
function capitalize(string: string): string {
1416
return string.charAt(0).toUpperCase() + string.slice(1);
1517
}
@@ -44,9 +46,58 @@ function getEnumName(moduleName: string, origEnumName: string): string {
4446
return `${moduleName}${uppercasedPropName}`;
4547
}
4648

49+
type ValidUnionType = 'boolean' | 'number' | 'object' | 'string';
50+
const NumberTypes = ['NumberTypeAnnotation', 'NumberLiteralTypeAnnotation'];
51+
const StringTypes = ['StringTypeAnnotation', 'StringLiteralTypeAnnotation'];
52+
const ObjectTypes = ['ObjectTypeAnnotation'];
53+
const BooleanTypes = ['BooleanTypeAnnotation', 'BooleanLiteralTypeAnnotation'];
54+
const ValidUnionTypes = [
55+
...NumberTypes,
56+
...ObjectTypes,
57+
...StringTypes,
58+
...BooleanTypes,
59+
];
60+
61+
function parseValidUnionType(
62+
annotation: NativeModuleUnionTypeAnnotation,
63+
): ValidUnionType {
64+
const isUnionOfType = (types: $ReadOnlyArray<string>): boolean => {
65+
return annotation.types.every(memberTypeAnnotation =>
66+
types.includes(memberTypeAnnotation.type),
67+
);
68+
};
69+
if (isUnionOfType(BooleanTypes)) {
70+
return 'boolean';
71+
}
72+
if (isUnionOfType(NumberTypes)) {
73+
return 'number';
74+
}
75+
if (isUnionOfType(ObjectTypes)) {
76+
return 'object';
77+
}
78+
if (isUnionOfType(StringTypes)) {
79+
return 'string';
80+
}
81+
82+
const invalidTypes = annotation.types.filter(member => {
83+
return !ValidUnionTypes.includes(member.type);
84+
});
85+
86+
// Check if union members are all supported but not homogeneous
87+
// (e.g., mix of number and boolean)
88+
if (invalidTypes.length === 0) {
89+
throw new Error(`Non-homogenous union member types`);
90+
} else {
91+
throw new Error(
92+
`Unsupported union member types: ${invalidTypes.join(', ')}"`,
93+
);
94+
}
95+
}
96+
4797
module.exports = {
4898
capitalize,
4999
indent,
100+
parseValidUnionType,
50101
toPascalCase,
51102
toSafeCppString,
52103
getEnumName,

packages/react-native-codegen/src/generators/components/CppHelpers.js

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import type {
1515
PropTypeAnnotation,
1616
} from '../../CodegenSchema';
1717

18-
const {getEnumName, toSafeCppString} = require('../Utils');
18+
const {getEnumName, parseValidUnionType, toSafeCppString} = require('../Utils');
1919

2020
function toIntEnumValueName(propName: string, value: number): string {
2121
return `${toSafeCppString(propName)}${value}`;
@@ -61,7 +61,40 @@ function getCppArrayTypeForAnnotation(
6161
case 'Int32TypeAnnotation':
6262
case 'MixedTypeAnnotation':
6363
return `std::vector<${getCppTypeForAnnotation(typeElement.type)}>`;
64-
case 'StringLiteralUnionTypeAnnotation':
64+
case 'UnionTypeAnnotation':
65+
const validUnionType = parseValidUnionType(typeElement);
66+
switch (validUnionType) {
67+
case 'boolean':
68+
return `std::vector<${getCppTypeForAnnotation('BooleanTypeAnnotation')}>`;
69+
case 'number':
70+
// Have type-upgraded Number to Double in this case to match the Int32TypeAnnotation, FloatTypeAnnotation & DoubleTypeAnnotation
71+
return `std::vector<${getCppTypeForAnnotation('DoubleTypeAnnotation')}>`;
72+
case 'object':
73+
if (!structParts) {
74+
throw new Error(
75+
`Trying to generate the event emitter for an Array of ${typeElement.type} without informations to generate the generic type`,
76+
);
77+
}
78+
return `std::vector<${generateEventStructName(structParts)}>`;
79+
case 'string':
80+
if (
81+
typeElement.types.every(
82+
({type}) => type === 'StringLiteralTypeAnnotation',
83+
)
84+
) {
85+
if (!structParts) {
86+
throw new Error(
87+
`Trying to generate the event emitter for an Array of ${typeElement.type} without informations to generate the generic type`,
88+
);
89+
}
90+
return `std::vector<${generateEventStructName(structParts)}>`;
91+
}
92+
// Unions of strings and string literals are treated as just strings
93+
return `std::vector<${getCppTypeForAnnotation('StringTypeAnnotation')}>`;
94+
default:
95+
(validUnionType: empty);
96+
throw new Error(`Unsupported union member type`);
97+
}
6598
case 'ObjectTypeAnnotation':
6699
if (!structParts) {
67100
throw new Error(

packages/react-native-codegen/src/generators/components/GenerateEventEmitterCpp.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import type {
1919
} from '../../CodegenSchema';
2020

2121
const {indent} = require('../Utils');
22+
const {parseValidUnionType} = require('../Utils');
2223
const {IncludeTemplate, generateEventStructName} = require('./CppHelpers');
2324

2425
// File path -> contents
@@ -207,7 +208,11 @@ function handleArrayElementType(
207208
loopLocalVariable,
208209
val => `jsi::valueFromDynamic(runtime, ${val})`,
209210
);
210-
case 'StringLiteralUnionTypeAnnotation':
211+
case 'UnionTypeAnnotation':
212+
const validUnionType = parseValidUnionType(elementType);
213+
if (validUnionType !== 'string') {
214+
throw new Error('Invalid since it is a union of non strings');
215+
}
211216
return setValueAtIndex(
212217
propertyName,
213218
indexVariable,
@@ -320,7 +325,11 @@ function generateSetters(
320325
usingEvent,
321326
prop => `jsi::valueFromDynamic(runtime, ${prop})`,
322327
);
323-
case 'StringLiteralUnionTypeAnnotation':
328+
case 'UnionTypeAnnotation':
329+
const validUnionType = parseValidUnionType(typeAnnotation);
330+
if (validUnionType !== 'string') {
331+
throw new Error('Invalid since it is a union of non strings');
332+
}
324333
return generateSetter(
325334
parentPropertyName,
326335
eventProperty.name,

packages/react-native-codegen/src/generators/components/GenerateEventEmitterH.js

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import type {
1818
SchemaType,
1919
} from '../../CodegenSchema';
2020

21-
const {indent, toSafeCppString} = require('../Utils');
21+
const {indent, parseValidUnionType, toSafeCppString} = require('../Utils');
2222
const {
2323
generateEventStructName,
2424
getCppArrayTypeForAnnotation,
@@ -118,17 +118,21 @@ function getNativeTypeFromAnnotation(
118118
eventProperty: NamedShape<EventTypeAnnotation>,
119119
nameParts: $ReadOnlyArray<string>,
120120
): string {
121-
const {type} = eventProperty.typeAnnotation;
122-
123-
switch (type) {
121+
const {typeAnnotation} = eventProperty;
122+
switch (typeAnnotation.type) {
124123
case 'BooleanTypeAnnotation':
125124
case 'StringTypeAnnotation':
126125
case 'Int32TypeAnnotation':
127126
case 'DoubleTypeAnnotation':
128127
case 'FloatTypeAnnotation':
129128
case 'MixedTypeAnnotation':
130-
return getCppTypeForAnnotation(type);
131-
case 'StringLiteralUnionTypeAnnotation':
129+
return getCppTypeForAnnotation(typeAnnotation.type);
130+
case 'UnionTypeAnnotation':
131+
const validUnionType = parseValidUnionType(typeAnnotation);
132+
if (validUnionType !== 'string') {
133+
throw new Error('Invalid since it is a union of non strings');
134+
}
135+
return generateEventStructName([...nameParts, eventProperty.name]);
132136
case 'ObjectTypeAnnotation':
133137
return generateEventStructName([...nameParts, eventProperty.name]);
134138
case 'ArrayTypeAnnotation':
@@ -143,8 +147,10 @@ function getNativeTypeFromAnnotation(
143147
eventProperty.name,
144148
]);
145149
default:
146-
(type: empty);
147-
throw new Error(`Received invalid event property type ${type}`);
150+
(typeAnnotation.type: empty);
151+
throw new Error(
152+
`Received invalid event property type ${typeAnnotation.type}`,
153+
);
148154
}
149155
}
150156
function generateEnum(
@@ -188,7 +194,11 @@ function handleGenerateStructForArray(
188194
nameParts.concat([name]),
189195
nullthrows(elementType.properties),
190196
);
191-
} else if (elementType.type === 'StringLiteralUnionTypeAnnotation') {
197+
} else if (elementType.type === 'UnionTypeAnnotation') {
198+
const validUnionType = parseValidUnionType(elementType);
199+
if (validUnionType !== 'string') {
200+
throw new Error('Invalid since it is a union of non strings');
201+
}
192202
generateEnum(
193203
structs,
194204
elementType.types.map(literal => literal.value),
@@ -251,7 +261,11 @@ function generateStruct(
251261
nullthrows(typeAnnotation.properties),
252262
);
253263
return;
254-
case 'StringLiteralUnionTypeAnnotation':
264+
case 'UnionTypeAnnotation':
265+
const validUnionType = parseValidUnionType(typeAnnotation);
266+
if (validUnionType !== 'string') {
267+
throw new Error('Invalid since it is a union of non strings');
268+
}
255269
generateEnum(
256270
structs,
257271
typeAnnotation.types.map(literal => literal.value),

packages/react-native-codegen/src/generators/components/__test_fixtures__/fixtures.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1273,7 +1273,7 @@ const EVENT_PROPS: SchemaType = {
12731273
typeAnnotation: {
12741274
type: 'ArrayTypeAnnotation',
12751275
elementType: {
1276-
type: 'StringLiteralUnionTypeAnnotation',
1276+
type: 'UnionTypeAnnotation',
12771277
types: [
12781278
{
12791279
type: 'StringLiteralTypeAnnotation',
@@ -1383,7 +1383,7 @@ const EVENT_PROPS: SchemaType = {
13831383
name: 'orientation',
13841384
optional: false,
13851385
typeAnnotation: {
1386-
type: 'StringLiteralUnionTypeAnnotation',
1386+
type: 'UnionTypeAnnotation',
13871387
types: [
13881388
{
13891389
type: 'StringLiteralTypeAnnotation',

0 commit comments

Comments
 (0)