From 5122e33f4eb8507e1a4cd3325d8ba8197f0b93c8 Mon Sep 17 00:00:00 2001 From: Ramanpreet Nara Date: Thu, 1 Oct 2020 19:26:14 -0700 Subject: [PATCH] Make ObjCPP Generator handle NullableTypeAnnotation Summary: See title. Changelog: [Internal] Reviewed By: hramos Differential Revision: D24027243 fbshipit-source-id: 4e03a14fd23c1f5f3d282fc14a760d7549cdd6b9 --- .../GenerateModuleObjCpp/StructCollector.js | 58 ++++++++++------- .../header/serializeConstantsStruct.js | 13 ++-- .../header/serializeRegularStruct.js | 13 ++-- .../GenerateModuleObjCpp/serializeMethod.js | 65 ++++++++++++------- 4 files changed, 92 insertions(+), 57 deletions(-) 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 71855f15f4f..13c3c1031ab 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/StructCollector.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/StructCollector.js @@ -11,7 +11,7 @@ 'use strict'; import type { - Required, + Nullable, NativeModuleObjectTypeAnnotation, NativeModuleStringTypeAnnotation, NativeModuleNumberTypeAnnotation, @@ -29,6 +29,10 @@ import type { import type {AliasResolver} from '../Utils'; const {capitalize} = require('./Utils'); +const { + unwrapNullable, + wrapNullable, +} = require('../../../parsers/flow/modules/utils'); type StructContext = 'CONSTANTS' | 'REGULAR'; @@ -49,7 +53,7 @@ export type Struct = RegularStruct | ConstantsStruct; export type StructProperty = $ReadOnly<{| name: string, optional: boolean, - typeAnnotation: StructTypeAnnotation, + typeAnnotation: Nullable, |}>; export type StructTypeAnnotation = @@ -62,7 +66,7 @@ export type StructTypeAnnotation = | NativeModuleGenericObjectTypeAnnotation | NativeModuleReservedFunctionValueTypeAnnotation | NativeModuleTypeAliasTypeAnnotation - | NativeModuleArrayTypeAnnotation; + | NativeModuleArrayTypeAnnotation>; class StructCollector { _structs: Map = new Map(); @@ -71,46 +75,45 @@ class StructCollector { structName: string, structContext: StructContext, resolveAlias: AliasResolver, - typeAnnotation: NativeModuleBaseTypeAnnotation, - ): StructTypeAnnotation { + nullableTypeAnnotation: Nullable, + ): Nullable { + const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation); switch (typeAnnotation.type) { case 'ObjectTypeAnnotation': { - this._insertStruct(structName, structContext, resolveAlias, { - ...typeAnnotation, - // The nullability status of this struct is recorded in the type-alias we create for it below. - nullable: false, - }); - return { + this._insertStruct( + structName, + structContext, + resolveAlias, + typeAnnotation, + ); + return wrapNullable(nullable, { type: 'TypeAliasTypeAnnotation', name: structName, - nullable: typeAnnotation.nullable, - }; + }); } case 'ArrayTypeAnnotation': { if (typeAnnotation.elementType == null) { - return { + return wrapNullable(nullable, { type: 'ArrayTypeAnnotation', - nullable: typeAnnotation.nullable, - }; + }); } - return { + return wrapNullable(nullable, { type: 'ArrayTypeAnnotation', - nullable: typeAnnotation.nullable, elementType: this.process( structName + 'Element', structContext, resolveAlias, typeAnnotation.elementType, ), - }; + }); } case 'TypeAliasTypeAnnotation': { this._insertAlias(typeAnnotation.name, structContext, resolveAlias); - return typeAnnotation; + return wrapNullable(nullable, typeAnnotation); } default: { - return typeAnnotation; + return wrapNullable(nullable, typeAnnotation); } } } @@ -139,10 +142,15 @@ class StructCollector { structName: string, structContext: StructContext, resolveAlias: AliasResolver, - objectTypeAnnotation: Required, + objectTypeAnnotation: NativeModuleObjectTypeAnnotation, ): void { - const properties = objectTypeAnnotation.properties.map(property => { - const {typeAnnotation: propertyTypeAnnotation} = property; + const properties = objectTypeAnnotation.properties.map< + $ReadOnly<{ + name: string, + optional: boolean, + typeAnnotation: Nullable, + }>, + >(property => { const propertyStructName = structName + capitalize(property.name); return { @@ -151,7 +159,7 @@ class StructCollector { propertyStructName, structContext, resolveAlias, - propertyTypeAnnotation, + property.typeAnnotation, ), }; }); 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 27a71f43a32..1d3ddb9755f 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 @@ -16,9 +16,12 @@ const { getNamespacedStructName, } = require('../Utils'); +import type {Nullable} from '../../../../CodegenSchema'; import type {StructTypeAnnotation, ConstantsStruct} from '../StructCollector'; import type {StructSerilizationOutput} from './serializeStruct'; +const {unwrapNullable} = require('../../../../parsers/flow/modules/utils'); + const StructTemplate = ({ moduleName, structName, @@ -76,10 +79,11 @@ inline JS::Native${moduleName}::${structName}::Builder::Builder(${structName} i) function toObjCType( moduleName: string, - typeAnnotation: StructTypeAnnotation, + nullableTypeAnnotation: Nullable, isOptional: boolean = false, ): string { - const isRequired = !typeAnnotation.nullable && !isOptional; + const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation); + const isRequired = !nullable && !isOptional; const wrapFollyOptional = (type: string) => { return isRequired ? type : `folly::Optional<${type}>`; }; @@ -132,12 +136,13 @@ function toObjCType( function toObjCValue( moduleName: string, - typeAnnotation: StructTypeAnnotation, + nullableTypeAnnotation: Nullable, value: string, depth: number, isOptional: boolean = false, ): string { - const isRequired = !isOptional && !typeAnnotation.nullable; + const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation); + const isRequired = !nullable && !isOptional; function wrapPrimitive(type: string) { return !isRequired 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 ce7b5f12d4f..6815dd05c54 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 @@ -16,9 +16,12 @@ const { getNamespacedStructName, } = require('../Utils'); +import type {Nullable} from '../../../../CodegenSchema'; import type {StructTypeAnnotation, RegularStruct} from '../StructCollector'; import type {StructSerilizationOutput} from './serializeStruct'; +const {unwrapNullable} = require('../../../../parsers/flow/modules/utils'); + const StructTemplate = ({ moduleName, structName, @@ -67,10 +70,11 @@ inline ${returnType}JS::Native${moduleName}::${structName}::${propertyName}() co function toObjCType( moduleName: string, - typeAnnotation: StructTypeAnnotation, + nullableTypeAnnotation: Nullable, isOptional: boolean = false, ): string { - const isRequired = !typeAnnotation.nullable && !isOptional; + const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation); + const isRequired = !nullable && !isOptional; const wrapFollyOptional = (type: string) => { return isRequired ? type : `folly::Optional<${type}>`; }; @@ -125,12 +129,13 @@ function toObjCType( function toObjCValue( moduleName: string, - typeAnnotation: StructTypeAnnotation, + nullableTypeAnnotation: Nullable, value: string, depth: number, isOptional: boolean = false, ): string { - const isRequired = !typeAnnotation.nullable && !isOptional; + const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation); + const isRequired = !nullable && !isOptional; const RCTBridgingTo = (type: string, arg?: string) => { const args = [value, arg].filter(Boolean).join(', '); return isRequired diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/serializeMethod.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/serializeMethod.js index bec84f59d6d..2825696ca44 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/serializeMethod.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleObjCpp/serializeMethod.js @@ -14,6 +14,7 @@ import type { NativeModuleMethodParamSchema, NativeModuleReturnTypeAnnotation, NativeModulePropertySchema, + Nullable, } from '../../../CodegenSchema'; import type {AliasResolver} from '../Utils'; @@ -21,6 +22,10 @@ import type {AliasResolver} from '../Utils'; const invariant = require('invariant'); const {StructCollector} = require('./StructCollector'); const {capitalize, getNamespacedStructName} = require('./Utils'); +const { + wrapNullable, + unwrapNullable, +} = require('../../../parsers/flow/modules/utils'); const ProtocolMethodTemplate = ({ returnObjCType, @@ -60,10 +65,9 @@ function serializeMethod( structCollector: StructCollector, resolveAlias: AliasResolver, ): $ReadOnlyArray { - const { - name: methodName, - typeAnnotation: {params, returnTypeAnnotation}, - } = property; + const {name: methodName, typeAnnotation: nullableTypeAnnotation} = property; + const [propertyTypeAnnotation] = unwrapNullable(nullableTypeAnnotation); + const {params} = propertyTypeAnnotation; if (methodName === 'getConstants') { return serializeConstantsProtocolMethods( @@ -95,6 +99,12 @@ function serializeMethod( } }); + // Unwrap returnTypeAnnotation, so we check if the return type is Promise + // TODO(T76719514): Disallow nullable PromiseTypeAnnotations + const [returnTypeAnnotation] = unwrapNullable( + propertyTypeAnnotation.returnTypeAnnotation, + ); + if (returnTypeAnnotation.type === 'PromiseTypeAnnotation') { methodParams.push( {paramName: 'resolve', objCType: 'RCTPromiseResolveBlock'}, @@ -105,7 +115,10 @@ function serializeMethod( /** * Build Protocol Method **/ - const returnObjCType = getReturnObjCType(methodName, returnTypeAnnotation); + const returnObjCType = getReturnObjCType( + methodName, + propertyTypeAnnotation.returnTypeAnnotation, + ); const paddingMax = `- (${returnObjCType})${methodName}`.length; const objCParams = methodParams.reduce( @@ -155,8 +168,9 @@ function getParamStructName( methodName: string, param: NativeModuleMethodParamSchema, ): string { - if (param.typeAnnotation.type === 'TypeAliasTypeAnnotation') { - return param.typeAnnotation.name; + const [typeAnnotation] = unwrapNullable(param.typeAnnotation); + if (typeAnnotation.type === 'TypeAliasTypeAnnotation') { + return typeAnnotation.name; } return `Spec${capitalize(methodName)}${capitalize(param.name)}`; @@ -170,13 +184,12 @@ function getParamObjCType( structCollector: StructCollector, resolveAlias: AliasResolver, ): $ReadOnly<{|objCType: string, isStruct: boolean|}> { - const {name: paramName, typeAnnotation} = param; - const notRequired = param.optional || typeAnnotation.nullable; + const {name: paramName, typeAnnotation: nullableTypeAnnotation} = param; + const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation); + const notRequired = param.optional || nullable; function wrapIntoNullableIfNeeded(generatedType: string) { - return typeAnnotation.nullable - ? `${generatedType} _Nullable` - : generatedType; + return nullable ? `${generatedType} _Nullable` : generatedType; } const isStruct = (objCType: string) => ({ @@ -209,11 +222,13 @@ function getParamObjCType( } } - const structTypeAnnotation = structCollector.process( - structName, - 'REGULAR', - resolveAlias, - typeAnnotation, + const [structTypeAnnotation] = unwrapNullable( + structCollector.process( + structName, + 'REGULAR', + resolveAlias, + wrapNullable(nullable, typeAnnotation), + ), ); invariant( @@ -264,12 +279,12 @@ function getParamObjCType( function getReturnObjCType( methodName: string, - typeAnnotation: NativeModuleReturnTypeAnnotation, + nullableTypeAnnotation: Nullable, ) { + const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation); + function wrapIntoNullableIfNeeded(generatedType: string) { - return typeAnnotation.nullable - ? `${generatedType} _Nullable` - : generatedType; + return nullable ? `${generatedType} _Nullable` : generatedType; } switch (typeAnnotation.type) { @@ -328,8 +343,9 @@ function getReturnObjCType( function getReturnJSType( methodName: string, - typeAnnotation: NativeModuleReturnTypeAnnotation, + nullableTypeAnnotation: Nullable, ): ReturnJSType { + const [typeAnnotation] = unwrapNullable(nullableTypeAnnotation); switch (typeAnnotation.type) { case 'VoidTypeAnnotation': return 'VoidKind'; @@ -371,13 +387,14 @@ function serializeConstantsProtocolMethods( structCollector: StructCollector, resolveAlias: AliasResolver, ): $ReadOnlyArray { - if (property.typeAnnotation.params.length !== 0) { + const [propertyTypeAnnotation] = unwrapNullable(property.typeAnnotation); + if (propertyTypeAnnotation.params.length !== 0) { throw new Error( `${moduleName}.getConstants() may only accept 0 arguments.`, ); } - const {returnTypeAnnotation} = property.typeAnnotation; + const {returnTypeAnnotation} = propertyTypeAnnotation; if (returnTypeAnnotation.type !== 'ObjectTypeAnnotation') { throw new Error( `${moduleName}.getConstants() may only return an object literal: {|...|}.`,