Skip to content

Commit c23be31

Browse files
Add support for Unions in react-native-codegen & Compat Checker (facebook#54594)
Summary: Pull Request resolved: facebook#54594 Refactoring `NativeModuleUnionTypeAnnotation` to use the newly introduced `NativeModuleUnionTypeAnnotationMemberType` Changelog: [Internal] Differential Revision: D86501597
1 parent 92676d3 commit c23be31

39 files changed

+2681
-1646
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: 4 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',

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: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,23 @@
1212
import type {
1313
EventTypeAnnotation,
1414
NamedShape,
15+
NativeModuleUnionTypeAnnotation,
1516
PropTypeAnnotation,
1617
} from '../../CodegenSchema';
1718

1819
const {getEnumName, toSafeCppString} = require('../Utils');
1920

21+
const NumberTypes = ['NumberTypeAnnotation', 'NumberLiteralTypeAnnotation'];
22+
const StringTypes = ['StringTypeAnnotation', 'StringLiteralTypeAnnotation'];
23+
const ObjectTypes = ['ObjectTypeAnnotation'];
24+
const BooleanTypes = ['BooleanTypeAnnotation', 'BooleanLiteralTypeAnnotation'];
25+
const ValidUnionTypes = [
26+
...NumberTypes,
27+
...ObjectTypes,
28+
...StringTypes,
29+
...BooleanTypes,
30+
];
31+
2032
function toIntEnumValueName(propName: string, value: number): string {
2133
return `${toSafeCppString(propName)}${value}`;
2234
}
@@ -61,7 +73,54 @@ function getCppArrayTypeForAnnotation(
6173
case 'Int32TypeAnnotation':
6274
case 'MixedTypeAnnotation':
6375
return `std::vector<${getCppTypeForAnnotation(typeElement.type)}>`;
64-
case 'StringLiteralUnionTypeAnnotation':
76+
case 'UnionTypeAnnotation':
77+
const union: NativeModuleUnionTypeAnnotation = typeElement;
78+
const isUnionOfType = (types: $ReadOnlyArray<string>): boolean => {
79+
return union.types.every(memberTypeAnnotation =>
80+
types.includes(memberTypeAnnotation.type),
81+
);
82+
};
83+
84+
if (isUnionOfType(NumberTypes)) {
85+
return `std::vector<${getCppTypeForAnnotation('DoubleTypeAnnotation')}>`;
86+
}
87+
88+
if (isUnionOfType(ObjectTypes)) {
89+
if (!structParts) {
90+
throw new Error(
91+
`Trying to generate the event emitter for an Array of ${typeElement.type} without informations to generate the generic type`,
92+
);
93+
}
94+
return `std::vector<${generateEventStructName(structParts)}>`;
95+
}
96+
97+
if (isUnionOfType(['StringTypeAnnotation'])) {
98+
return `std::vector<${getCppTypeForAnnotation('StringTypeAnnotation')}>`;
99+
}
100+
if (isUnionOfType(['StringLiteralTypeAnnotation'])) {
101+
if (!structParts) {
102+
throw new Error(
103+
`Trying to generate the event emitter for an Array of ${typeElement.type} without informations to generate the generic type`,
104+
);
105+
}
106+
return `std::vector<${generateEventStructName(structParts)}>`;
107+
}
108+
109+
if (isUnionOfType(BooleanTypes)) {
110+
return `std::vector<${getCppTypeForAnnotation('BooleanTypeAnnotation')}>`;
111+
}
112+
113+
const invalidTypes = union.types.filter(member => {
114+
return !ValidUnionTypes.includes(member.type);
115+
});
116+
117+
if (invalidTypes.length === 0) {
118+
throw new Error(`Non-homogenous union member types`);
119+
} else {
120+
throw new Error(
121+
`Unsupported union member types: ${invalidTypes.join(', ')}`,
122+
);
123+
}
65124
case 'ObjectTypeAnnotation':
66125
if (!structParts) {
67126
throw new Error(

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ function handleArrayElementType(
207207
loopLocalVariable,
208208
val => `jsi::valueFromDynamic(runtime, ${val})`,
209209
);
210-
case 'StringLiteralUnionTypeAnnotation':
210+
case 'UnionTypeAnnotation':
211211
return setValueAtIndex(
212212
propertyName,
213213
indexVariable,
@@ -320,7 +320,7 @@ function generateSetters(
320320
usingEvent,
321321
prop => `jsi::valueFromDynamic(runtime, ${prop})`,
322322
);
323-
case 'StringLiteralUnionTypeAnnotation':
323+
case 'UnionTypeAnnotation':
324324
return generateSetter(
325325
parentPropertyName,
326326
eventProperty.name,

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ function getNativeTypeFromAnnotation(
128128
case 'FloatTypeAnnotation':
129129
case 'MixedTypeAnnotation':
130130
return getCppTypeForAnnotation(type);
131-
case 'StringLiteralUnionTypeAnnotation':
131+
case 'UnionTypeAnnotation':
132132
case 'ObjectTypeAnnotation':
133133
return generateEventStructName([...nameParts, eventProperty.name]);
134134
case 'ArrayTypeAnnotation':
@@ -188,7 +188,7 @@ function handleGenerateStructForArray(
188188
nameParts.concat([name]),
189189
nullthrows(elementType.properties),
190190
);
191-
} else if (elementType.type === 'StringLiteralUnionTypeAnnotation') {
191+
} else if (elementType.type === 'UnionTypeAnnotation') {
192192
generateEnum(
193193
structs,
194194
elementType.types.map(literal => literal.value),
@@ -251,7 +251,7 @@ function generateStruct(
251251
nullthrows(typeAnnotation.properties),
252252
);
253253
return;
254-
case 'StringLiteralUnionTypeAnnotation':
254+
case 'UnionTypeAnnotation':
255255
generateEnum(
256256
structs,
257257
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',

packages/react-native-codegen/src/generators/modules/GenerateModuleH.js

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,12 @@ import type {AliasResolver} from './Utils';
2929

3030
const {unwrapNullable} = require('../../parsers/parsers-commons');
3131
const {wrapOptional} = require('../TypeUtils/Cxx');
32-
const {getEnumName, toPascalCase, toSafeCppString} = require('../Utils');
32+
const {
33+
getEnumName,
34+
parseValidUnionType,
35+
toPascalCase,
36+
toSafeCppString,
37+
} = require('../Utils');
3338
const {
3439
createAliasResolver,
3540
getModules,
@@ -92,8 +97,6 @@ function serializeArg(
9297
return wrap(val => `${val}.asString(rt)`);
9398
case 'StringLiteralTypeAnnotation':
9499
return wrap(val => `${val}.asString(rt)`);
95-
case 'StringLiteralUnionTypeAnnotation':
96-
return wrap(val => `${val}.asString(rt)`);
97100
case 'BooleanTypeAnnotation':
98101
return wrap(val => `${val}.asBool()`);
99102
case 'BooleanLiteralTypeAnnotation':
@@ -126,17 +129,19 @@ function serializeArg(
126129
case 'GenericObjectTypeAnnotation':
127130
return wrap(val => `${val}.asObject(rt)`);
128131
case 'UnionTypeAnnotation':
129-
switch (typeAnnotation.memberType) {
130-
case 'NumberTypeAnnotation':
132+
const validUnionType = parseValidUnionType(realTypeAnnotation);
133+
switch (validUnionType) {
134+
case 'boolean':
135+
return wrap(val => `${val}.asBool()`);
136+
case 'number':
131137
return wrap(val => `${val}.asNumber()`);
132-
case 'ObjectTypeAnnotation':
138+
case 'object':
133139
return wrap(val => `${val}.asObject(rt)`);
134-
case 'StringTypeAnnotation':
140+
case 'string':
135141
return wrap(val => `${val}.asString(rt)`);
136142
default:
137-
throw new Error(
138-
`Unsupported union member type for param "${arg.name}, found: ${realTypeAnnotation.memberType}"`,
139-
);
143+
(validUnionType: empty);
144+
throw new Error(`Unsupported union member type`);
140145
}
141146
case 'ObjectTypeAnnotation':
142147
return wrap(val => `${val}.asObject(rt)`);
@@ -253,8 +258,6 @@ function translatePrimitiveJSTypeToCpp(
253258
return wrapOptional('jsi::String', isRequired);
254259
case 'StringLiteralTypeAnnotation':
255260
return wrapOptional('jsi::String', isRequired);
256-
case 'StringLiteralUnionTypeAnnotation':
257-
return wrapOptional('jsi::String', isRequired);
258261
case 'NumberTypeAnnotation':
259262
return wrapOptional('double', isRequired);
260263
case 'NumberLiteralTypeAnnotation':
@@ -281,15 +284,19 @@ function translatePrimitiveJSTypeToCpp(
281284
case 'GenericObjectTypeAnnotation':
282285
return wrapOptional('jsi::Object', isRequired);
283286
case 'UnionTypeAnnotation':
284-
switch (typeAnnotation.memberType) {
285-
case 'NumberTypeAnnotation':
287+
const validUnionType = parseValidUnionType(realTypeAnnotation);
288+
switch (validUnionType) {
289+
case 'boolean':
290+
return wrapOptional('bool', isRequired);
291+
case 'number':
286292
return wrapOptional('double', isRequired);
287-
case 'ObjectTypeAnnotation':
293+
case 'object':
288294
return wrapOptional('jsi::Object', isRequired);
289-
case 'StringTypeAnnotation':
295+
case 'string':
290296
return wrapOptional('jsi::String', isRequired);
291297
default:
292-
throw new Error(createErrorMessage(realTypeAnnotation.type));
298+
(validUnionType: empty);
299+
throw new Error(`Unsupported union member type`);
293300
}
294301
case 'ObjectTypeAnnotation':
295302
return wrapOptional('jsi::Object', isRequired);

0 commit comments

Comments
 (0)