From 494af009b8e7996c0e4fadd0fa1be236ad271a4b Mon Sep 17 00:00:00 2001 From: Christoph Purrer Date: Sat, 14 Mar 2026 21:36:52 -0700 Subject: [PATCH] Deduplicate toObjCType function in struct serializers (#56050) Summary: Removes ~75 lines of duplicated `toObjCType` logic between `serializeConstantsStruct.js` and `serializeRegularStruct.js` by extracting it into a new shared module `serializeStructUtils.js`. The two implementations were identical except for array wrapper types (`std::vector` vs `facebook::react::LazyVector`) and type alias suffixes (`::Builder` vs none), which are now handled via a `structContext` parameter that distinguishes between `CONSTANTS` and `REGULAR` contexts. Changelog: [internal] Differential Revision: D95908134 --- .../GenerateModuleObjCpp/StructCollector.js | 2 +- .../header/serializeConstantsStruct.js | 99 ++------------- .../header/serializeRegularStruct.js | 96 ++------------ .../header/serializeStructUtils.js | 117 ++++++++++++++++++ 4 files changed, 144 insertions(+), 170 deletions(-) create mode 100644 packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/header/serializeStructUtils.js diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/StructCollector.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/StructCollector.js index 31df79ac2020..e8775b28aadf 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/StructCollector.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/StructCollector.js @@ -42,7 +42,7 @@ const { parseValidUnionType, } = require('../../Utils'); -type StructContext = 'CONSTANTS' | 'REGULAR'; +export type StructContext = 'CONSTANTS' | 'REGULAR'; export type RegularStruct = Readonly<{ context: 'REGULAR', diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/header/serializeConstantsStruct.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/header/serializeConstantsStruct.js index dcf0cf50f013..8ab9b2ba5d71 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/header/serializeConstantsStruct.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/header/serializeConstantsStruct.js @@ -15,12 +15,8 @@ import type {ConstantsStruct, StructTypeAnnotation} from '../StructCollector'; import type {StructSerilizationOutput} from './serializeStruct'; const {unwrapNullable} = require('../../../../parsers/parsers-commons'); -const {wrapOptional: wrapCxxOptional} = require('../../../TypeUtils/Cxx'); -const { - wrapOptional: wrapObjCOptional, -} = require('../../../TypeUtils/Objective-C'); -const {capitalize} = require('../../../Utils'); -const {getNamespacedStructName, getSafePropertyName} = require('../Utils'); +const {getSafePropertyName} = require('../Utils'); +const {toObjCType} = require('./serializeStructUtils'); const StructTemplate = ({ hasteModuleName, @@ -78,84 +74,6 @@ inline JS::${hasteModuleName}::${structName}::Builder::Builder(${structName} i) return i.unsafeRawValue(); }) {}`; -function toObjCType( - hasteModuleName: string, - nullableTypeAnnotation: Nullable, - isOptional: boolean = false, -): string { - const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation); - const isRequired = !nullable && !isOptional; - - switch (typeAnnotation.type) { - case 'ReservedTypeAnnotation': - switch (typeAnnotation.name) { - case 'RootTag': - return wrapCxxOptional('double', isRequired); - default: - (typeAnnotation.name: empty); - throw new Error(`Unknown prop type, found: ${typeAnnotation.name}"`); - } - case 'StringTypeAnnotation': - return 'NSString *'; - case 'StringLiteralTypeAnnotation': - return 'NSString *'; - case 'UnionTypeAnnotation': - // TODO(T247151345): Implement proper heterogeneous union support. This is unsafe. - return 'NSObject *'; - case 'NumberTypeAnnotation': - return wrapCxxOptional('double', isRequired); - case 'NumberLiteralTypeAnnotation': - return wrapCxxOptional('double', isRequired); - case 'FloatTypeAnnotation': - return wrapCxxOptional('double', isRequired); - case 'Int32TypeAnnotation': - return wrapCxxOptional('double', isRequired); - case 'DoubleTypeAnnotation': - return wrapCxxOptional('double', isRequired); - case 'BooleanTypeAnnotation': - return wrapCxxOptional('bool', isRequired); - case 'BooleanLiteralTypeAnnotation': - return wrapCxxOptional('bool', isRequired); - case 'EnumDeclaration': - switch (typeAnnotation.memberType) { - case 'NumberTypeAnnotation': - return wrapCxxOptional('double', isRequired); - case 'StringTypeAnnotation': - return 'NSString *'; - default: - throw new Error( - `Couldn't convert enum into ObjC type: ${typeAnnotation.type}"`, - ); - } - case 'GenericObjectTypeAnnotation': - return wrapObjCOptional('id', isRequired); - case 'ArrayTypeAnnotation': - if (typeAnnotation.elementType.type === 'AnyTypeAnnotation') { - return wrapObjCOptional('id', isRequired); - } - - return wrapCxxOptional( - `std::vector<${toObjCType( - hasteModuleName, - typeAnnotation.elementType, - )}>`, - isRequired, - ); - case 'TypeAliasTypeAnnotation': - const structName = capitalize(typeAnnotation.name); - const namespacedStructName = getNamespacedStructName( - hasteModuleName, - structName, - ); - return wrapCxxOptional(`${namespacedStructName}::Builder`, isRequired); - default: - (typeAnnotation.type: empty); - throw new Error( - `Couldn't convert into ObjC type: ${typeAnnotation.type}"`, - ); - } -} - function toObjCValue( hasteModuleName: string, nullableTypeAnnotation: Nullable, @@ -223,7 +141,11 @@ function toObjCValue( } const localVarName = `el${'_'.repeat(depth + 1)}`; - const elementObjCType = toObjCType(hasteModuleName, elementType); + const elementObjCType = toObjCType( + hasteModuleName, + elementType, + 'CONSTANTS', + ); const elementObjCValue = toObjCValue( hasteModuleName, elementType, @@ -263,7 +185,12 @@ function serializeConstantsStruct( .map(property => { const {typeAnnotation, optional} = property; const safePropName = getSafePropertyName(property); - const objCType = toObjCType(hasteModuleName, typeAnnotation, optional); + const objCType = toObjCType( + hasteModuleName, + typeAnnotation, + 'CONSTANTS', + optional, + ); if (!optional) { return `RCTRequired<${objCType}> ${safePropName};`; diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/header/serializeRegularStruct.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/header/serializeRegularStruct.js index 37b7ae4a5156..820ffbd23933 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/header/serializeRegularStruct.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/header/serializeRegularStruct.js @@ -15,12 +15,9 @@ import type {RegularStruct, StructTypeAnnotation} from '../StructCollector'; import type {StructSerilizationOutput} from './serializeStruct'; const {unwrapNullable} = require('../../../../parsers/parsers-commons'); -const {wrapOptional: wrapCxxOptional} = require('../../../TypeUtils/Cxx'); -const { - wrapOptional: wrapObjCOptional, -} = require('../../../TypeUtils/Objective-C'); const {capitalize} = require('../../../Utils'); const {getNamespacedStructName, getSafePropertyName} = require('../Utils'); +const {toObjCType} = require('./serializeStructUtils'); const StructTemplate = ({ hasteModuleName, @@ -66,83 +63,6 @@ const MethodTemplate = ({ return ${returnValue}; }`; -function toObjCType( - hasteModuleName: string, - nullableTypeAnnotation: Nullable, - isOptional: boolean = false, -): string { - const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation); - const isRequired = !nullable && !isOptional; - - switch (typeAnnotation.type) { - case 'ReservedTypeAnnotation': - switch (typeAnnotation.name) { - case 'RootTag': - return wrapCxxOptional('double', isRequired); - default: - (typeAnnotation.name: empty); - throw new Error(`Unknown prop type, found: ${typeAnnotation.name}"`); - } - case 'StringTypeAnnotation': - return 'NSString *'; - case 'StringLiteralTypeAnnotation': - return 'NSString *'; - case 'UnionTypeAnnotation': - // TODO(T247151345): Implement proper heterogeneous union support. This is unsafe. - return 'NSObject *'; - case 'NumberTypeAnnotation': - return wrapCxxOptional('double', isRequired); - case 'NumberLiteralTypeAnnotation': - return wrapCxxOptional('double', isRequired); - case 'FloatTypeAnnotation': - return wrapCxxOptional('double', isRequired); - case 'Int32TypeAnnotation': - return wrapCxxOptional('double', isRequired); - case 'DoubleTypeAnnotation': - return wrapCxxOptional('double', isRequired); - case 'BooleanTypeAnnotation': - return wrapCxxOptional('bool', isRequired); - case 'BooleanLiteralTypeAnnotation': - return wrapCxxOptional('bool', isRequired); - case 'EnumDeclaration': - switch (typeAnnotation.memberType) { - case 'NumberTypeAnnotation': - return wrapCxxOptional('double', isRequired); - case 'StringTypeAnnotation': - return 'NSString *'; - default: - throw new Error( - `Couldn't convert enum into ObjC type: ${typeAnnotation.type}"`, - ); - } - case 'GenericObjectTypeAnnotation': - return wrapObjCOptional('id', isRequired); - case 'ArrayTypeAnnotation': - if (typeAnnotation.elementType.type === 'AnyTypeAnnotation') { - return wrapObjCOptional('id', isRequired); - } - return wrapCxxOptional( - `facebook::react::LazyVector<${toObjCType( - hasteModuleName, - typeAnnotation.elementType, - )}>`, - isRequired, - ); - case 'TypeAliasTypeAnnotation': - const structName = capitalize(typeAnnotation.name); - const namespacedStructName = getNamespacedStructName( - hasteModuleName, - structName, - ); - return wrapCxxOptional(namespacedStructName, isRequired); - default: - (typeAnnotation.type: empty); - throw new Error( - `Couldn't convert into ObjC type: ${typeAnnotation.type}"`, - ); - } -} - function toObjCValue( hasteModuleName: string, nullableTypeAnnotation: Nullable, @@ -212,7 +132,11 @@ function toObjCValue( } const localVarName = `itemValue_${depth}`; - const elementObjCType = toObjCType(hasteModuleName, elementType); + const elementObjCType = toObjCType( + hasteModuleName, + elementType, + 'REGULAR', + ); const elementObjCValue = toObjCValue( hasteModuleName, elementType, @@ -256,6 +180,7 @@ function serializeRegularStruct( const returnType = toObjCType( hasteModuleName, typeAnnotation, + 'REGULAR', optional, ); @@ -270,7 +195,12 @@ function serializeRegularStruct( .map(property => { const {typeAnnotation, optional, name: propName} = property; const safePropertyName = getSafePropertyName(property); - const returnType = toObjCType(hasteModuleName, typeAnnotation, optional); + const returnType = toObjCType( + hasteModuleName, + typeAnnotation, + 'REGULAR', + optional, + ); const returnValue = toObjCValue( hasteModuleName, typeAnnotation, diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/header/serializeStructUtils.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/header/serializeStructUtils.js new file mode 100644 index 000000000000..c2e0bf1fcf95 --- /dev/null +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/header/serializeStructUtils.js @@ -0,0 +1,117 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + * @format + */ + +'use strict'; + +import type {Nullable} from '../../../../CodegenSchema'; +import type {StructContext, StructTypeAnnotation} from '../StructCollector'; + +const {unwrapNullable} = require('../../../../parsers/parsers-commons'); +const {wrapOptional: wrapCxxOptional} = require('../../../TypeUtils/Cxx'); +const { + wrapOptional: wrapObjCOptional, +} = require('../../../TypeUtils/Objective-C'); +const {capitalize} = require('../../../Utils'); +const {getNamespacedStructName} = require('../Utils'); + +function toObjCType( + hasteModuleName: string, + nullableTypeAnnotation: Nullable, + structContext: StructContext, + isOptional: boolean = false, +): string { + const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation); + const isRequired = !nullable && !isOptional; + + switch (typeAnnotation.type) { + case 'ReservedTypeAnnotation': + switch (typeAnnotation.name) { + case 'RootTag': + return wrapCxxOptional('double', isRequired); + default: + (typeAnnotation.name: empty); + throw new Error(`Unknown prop type, found: ${typeAnnotation.name}"`); + } + case 'StringTypeAnnotation': + return 'NSString *'; + case 'StringLiteralTypeAnnotation': + return 'NSString *'; + case 'UnionTypeAnnotation': + // TODO(T247151345): Implement proper heterogeneous union support. This is unsafe. + return 'NSObject *'; + case 'NumberTypeAnnotation': + return wrapCxxOptional('double', isRequired); + case 'NumberLiteralTypeAnnotation': + return wrapCxxOptional('double', isRequired); + case 'FloatTypeAnnotation': + return wrapCxxOptional('double', isRequired); + case 'Int32TypeAnnotation': + return wrapCxxOptional('double', isRequired); + case 'DoubleTypeAnnotation': + return wrapCxxOptional('double', isRequired); + case 'BooleanTypeAnnotation': + return wrapCxxOptional('bool', isRequired); + case 'BooleanLiteralTypeAnnotation': + return wrapCxxOptional('bool', isRequired); + case 'EnumDeclaration': + switch (typeAnnotation.memberType) { + case 'NumberTypeAnnotation': + return wrapCxxOptional('double', isRequired); + case 'StringTypeAnnotation': + return 'NSString *'; + default: + throw new Error( + `Couldn't convert enum into ObjC type: ${typeAnnotation.type}"`, + ); + } + case 'GenericObjectTypeAnnotation': + return wrapObjCOptional('id', isRequired); + case 'ArrayTypeAnnotation': + if (typeAnnotation.elementType.type === 'AnyTypeAnnotation') { + return wrapObjCOptional('id', isRequired); + } + + return wrapCxxOptional( + structContext === 'CONSTANTS' + ? `std::vector<${toObjCType( + hasteModuleName, + typeAnnotation.elementType, + structContext, + )}>` + : `facebook::react::LazyVector<${toObjCType( + hasteModuleName, + typeAnnotation.elementType, + structContext, + )}>`, + isRequired, + ); + case 'TypeAliasTypeAnnotation': + const structName = capitalize(typeAnnotation.name); + const namespacedStructName = getNamespacedStructName( + hasteModuleName, + structName, + ); + return wrapCxxOptional( + structContext === 'CONSTANTS' + ? `${namespacedStructName}::Builder` + : namespacedStructName, + isRequired, + ); + default: + (typeAnnotation.type: empty); + throw new Error( + `Couldn't convert into ObjC type: ${typeAnnotation.type}"`, + ); + } +} + +module.exports = { + toObjCType, +};