From 8bea2c19f317c9c39645a49617a53ce11e4aefc2 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Mon, 20 May 2024 14:17:42 -0400 Subject: [PATCH 01/23] deprecate isNullable property on type --- .../src/http.ts | 8 +++--- .../src/interfaces.ts | 26 ++++++++++++++++--- .../src/internal-utils.ts | 3 ++- .../src/package.ts | 6 ++--- .../src/types.ts | 22 ++++++++-------- 5 files changed, 43 insertions(+), 22 deletions(-) diff --git a/packages/typespec-client-generator-core/src/http.ts b/packages/typespec-client-generator-core/src/http.ts index 157895e26d..9a25bf60f3 100644 --- a/packages/typespec-client-generator-core/src/http.ts +++ b/packages/typespec-client-generator-core/src/http.ts @@ -45,7 +45,7 @@ import { isAcceptHeader, isContentTypeHeader, isNeverOrVoidType, - isNullable, + isNullableDeprecated, isSubscriptionId, } from "./internal-utils.js"; import { createDiagnostic } from "./lib.js"; @@ -165,7 +165,7 @@ function getSdkHttpParameters( apiVersions: getAvailableApiVersions(context, tspBody.type, httpOperation.operation), type, optional: false, - nullable: isNullable(tspBody.type), + nullable: isNullableDeprecated(tspBody.type), // eslint-disable-line deprecation/deprecation correspondingMethodParams, }; } @@ -397,7 +397,7 @@ function getSdkHttpResponseAndExceptions( details: getDocHelper(context, header).details, serializedName: getHeaderFieldName(context.program, header), type: clientType, - nullable: isNullable(header.type), + nullable: isNullableDeprecated(header.type), // eslint-disable-line deprecation/deprecation }); } if (innerResponse.body && !isNeverOrVoidType(innerResponse.body.type)) { @@ -437,7 +437,7 @@ function getSdkHttpResponseAndExceptions( httpOperation.operation, httpOperation.operation ), - nullable: body ? isNullable(body) : true, + nullable: body ? isNullableDeprecated(body) : true, // eslint-disable-line deprecation/deprecation }; if (response.statusCodes === "*" || (body && isErrorModel(context.program, body))) { exceptions.set(response.statusCodes, sdkResponse); diff --git a/packages/typespec-client-generator-core/src/interfaces.ts b/packages/typespec-client-generator-core/src/interfaces.ts index d6f2550405..d401609f37 100644 --- a/packages/typespec-client-generator-core/src/interfaces.ts +++ b/packages/typespec-client-generator-core/src/interfaces.ts @@ -81,8 +81,7 @@ interface SdkTypeBase { __raw?: Type; kind: string; /** - * @deprecated Moving `.nullable` onto the parameter itself for fidelity. - * https://github.com/Azure/typespec-azure/issues/448 + * @deprecated Use helper function `isNullable` instead */ nullable: boolean; deprecation?: string; @@ -230,6 +229,9 @@ export interface SdkDurationType extends SdkTypeBase { export interface SdkArrayType extends SdkTypeBase { kind: "array"; valueType: SdkType; + /** + * @deprecated Pass `valueType`` to `isNullable` instead + */ nullableValues: boolean; } @@ -242,6 +244,9 @@ export interface SdkDictionaryType extends SdkTypeBase { kind: "dict"; keyType: SdkType; valueType: SdkType; + /** + * @deprecated Pass `valueType`` to `isNullable` instead + */ nullableValues: boolean; } @@ -289,7 +294,7 @@ export interface SdkModelType extends SdkTypeBase { properties: SdkModelPropertyType[]; name: string; /** - * @deprecated This property is deprecated. Check the bitwise and value of UsageFlags.MultipartFormData nad the `.usage` property on this model + * @deprecated This property is deprecated. Check the bitwise and value of UsageFlags.MultipartFormData and the `.usage` property on this model */ isFormDataType: boolean; /** @@ -300,6 +305,9 @@ export interface SdkModelType extends SdkTypeBase { access?: AccessFlags; usage: UsageFlags; additionalProperties?: SdkType; + /** + * @deprecated This property is deprecated. Pass the type of the additionalProperties to `isNullable` instead + */ additionalPropertiesNullable?: boolean; discriminatorValue?: string; discriminatedSubtypes?: Record; @@ -337,6 +345,9 @@ export interface SdkModelPropertyTypeBase { clientDefaultValue?: any; isApiVersionParam: boolean; optional: boolean; + /** + * @deprecated se exported helper function `isNullable` instead, and pass the type of the property to the function. + */ nullable: boolean; } @@ -421,12 +432,18 @@ export interface SdkServiceResponseHeader { type: SdkType; description?: string; details?: string; + /** + * @deprecated This property is deprecated. Pass the type of response header to `isNullable` instead + */ nullable: boolean; } export interface SdkMethodResponse { kind: "method"; type?: SdkType; + /** + * @deprecated This property is deprecated. Pass the type of response to `isNullable` instead + */ nullable: boolean; resultPath?: string; // if exists, tells you how to get from the service response to the method response } @@ -435,6 +452,9 @@ export interface SdkServiceResponse { type?: SdkType; headers: SdkServiceResponseHeader[]; apiVersions: string[]; + /** + * @deprecated This property is deprecated. Pass the type of response to `isNullable` instead + */ nullable: boolean; } diff --git a/packages/typespec-client-generator-core/src/internal-utils.ts b/packages/typespec-client-generator-core/src/internal-utils.ts index 2dcad81323..8fb3f67c5a 100644 --- a/packages/typespec-client-generator-core/src/internal-utils.ts +++ b/packages/typespec-client-generator-core/src/internal-utils.ts @@ -376,10 +376,11 @@ export function getAllResponseBodies( /** * Determines if a type is nullable. + * @deprecated We want to move away from .nullable on SdkPropertyTypes and have people pass the type to `isNullable` instead. * @param type * @returns */ -export function isNullable(type: Type | SdkServiceOperation): boolean { +export function isNullableDeprecated(type: Type | SdkServiceOperation): boolean { if (type.kind === "Union") { if (getNonNullOptions(type).length < type.variants.size) return true; return Boolean(ignoreDiagnostics(getUnionAsEnum(type))?.nullable); diff --git a/packages/typespec-client-generator-core/src/package.ts b/packages/typespec-client-generator-core/src/package.ts index 514e40a5cd..30208815e1 100644 --- a/packages/typespec-client-generator-core/src/package.ts +++ b/packages/typespec-client-generator-core/src/package.ts @@ -57,7 +57,7 @@ import { getLocationOfOperation, getSdkTypeBaseHelper, isNeverOrVoidType, - isNullable, + isNullableDeprecated, updateWithApiVersionInformation, } from "./internal-utils.js"; import { createDiagnostic } from "./lib.js"; @@ -214,7 +214,7 @@ function getSdkMethodResponse( __raw: operation, kind: "union", values: allResponseBodies, - nullable: isNullable(sdkOperation), + nullable: isNullableDeprecated(sdkOperation), name: createGeneratedName(operation, "UnionResponse"), isGeneratedName: true, }; @@ -224,7 +224,7 @@ function getSdkMethodResponse( return { kind: "method", type, - nullable: isNullable(sdkOperation), + nullable: isNullableDeprecated(sdkOperation), }; } diff --git a/packages/typespec-client-generator-core/src/types.ts b/packages/typespec-client-generator-core/src/types.ts index ce1cdc9874..f30c328b74 100644 --- a/packages/typespec-client-generator-core/src/types.ts +++ b/packages/typespec-client-generator-core/src/types.ts @@ -86,7 +86,7 @@ import { isMultipartFormData, isMultipartOperation, isNeverOrVoidType, - isNullable, + isNullableDeprecated, updateWithApiVersionInformation, } from "./internal-utils.js"; import { createDiagnostic } from "./lib.js"; @@ -157,7 +157,7 @@ export function addEncodeInfo( propertyType.wireType = diagnostics.pipe( getClientTypeWithDiagnostics(context, encodeData.type) ) as SdkBuiltInType; - if (type.kind === "ModelProperty" && isNullable(type.type)) { + if (type.kind === "ModelProperty" && isNullableDeprecated(type.type)) { // eslint-disable-next-line deprecation/deprecation propertyType.wireType.nullable = true; } @@ -171,7 +171,7 @@ export function addEncodeInfo( } else if (type.kind === "ModelProperty" && isHeader(context.program, type)) { propertyType.encode = "rfc7231"; } - if (type.kind === "ModelProperty" && isNullable(type.type)) { + if (type.kind === "ModelProperty" && isNullableDeprecated(type.type)) { // eslint-disable-next-line deprecation/deprecation propertyType.wireType.nullable = true; } @@ -297,14 +297,14 @@ export function getSdkArrayOrDictWithDiagnostics( getClientTypeWithDiagnostics(context, type.indexer.key, operation) ), valueType, - nullableValues: isNullable(type.indexer.value!), + nullableValues: isNullableDeprecated(type.indexer.value!), }); } else if (name === "integer") { // only array's index key name is integer return diagnostics.wrap({ ...getSdkTypeBaseHelper(context, type, "array"), valueType, - nullableValues: isNullable(type.indexer.value!), + nullableValues: isNullableDeprecated(type.indexer.value!), }); } } @@ -356,7 +356,7 @@ export function getSdkUnionWithDiagnostics( getClientTypeWithDiagnostics(context, nonNullOptions[0], operation) ); // eslint-disable-next-line deprecation/deprecation - clientType.nullable = isNullable(type); + clientType.nullable = isNullableDeprecated(type); clientType.__raw = type; return diagnostics.wrap(clientType); } @@ -383,7 +383,7 @@ export function getSdkUnionWithDiagnostics( values: nonNullOptions.map((x) => diagnostics.pipe(getClientTypeWithDiagnostics(context, x, operation)) ), - nullable: isNullable(type), + nullable: isNullableDeprecated(type), }); } @@ -570,14 +570,14 @@ export function getSdkModelWithDiagnostics( sdkType.additionalProperties = diagnostics.pipe( getClientTypeWithDiagnostics(context, type.sourceModel!.indexer!.value!, operation) ); - sdkType.additionalPropertiesNullable = isNullable(type.sourceModel!.indexer!.value!); + sdkType.additionalPropertiesNullable = isNullableDeprecated(type.sourceModel!.indexer!.value!); } // model MyModel { ...Record<>} should be model with additional properties if (type.indexer) { sdkType.additionalProperties = diagnostics.pipe( getClientTypeWithDiagnostics(context, type.indexer.value, operation) ); - sdkType.additionalPropertiesNullable = isNullable(type.indexer.value); + sdkType.additionalPropertiesNullable = isNullableDeprecated(type.indexer.value); } // propreties should be generated first since base model'sdiscriminator handling is depend on derived model's properties diagnostics.pipe(addPropertiesToModelType(context, type, sdkType, operation)); @@ -590,7 +590,7 @@ export function getSdkModelWithDiagnostics( if (baseModel.kind === "dict") { // model MyModel extends Record<> {} should be model with additional properties sdkType.additionalProperties = baseModel.valueType; - sdkType.additionalPropertiesNullable = isNullable(baseModel.valueType.__raw!); + sdkType.additionalPropertiesNullable = isNullableDeprecated(baseModel.valueType.__raw!); } else { sdkType.baseModel = baseModel; } @@ -1003,7 +1003,7 @@ export function getSdkModelPropertyTypeBase( name, isGeneratedName: false, optional: type.optional, - nullable: isNullable(type.type), + nullable: isNullableDeprecated(type.type), ...updateWithApiVersionInformation( context, type, From b23a674ab23317efa2e9db01122e1e85b202b028 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Mon, 20 May 2024 15:30:07 -0400 Subject: [PATCH 02/23] don't generate a single type with nullable information --- .../src/http.ts | 8 +-- .../src/interfaces.ts | 1 + .../src/internal-utils.ts | 4 +- .../src/package.ts | 6 +- .../src/public-utils.ts | 20 +++++++ .../src/types.ts | 57 +++++++------------ 6 files changed, 50 insertions(+), 46 deletions(-) diff --git a/packages/typespec-client-generator-core/src/http.ts b/packages/typespec-client-generator-core/src/http.ts index 9a25bf60f3..73fdae83b4 100644 --- a/packages/typespec-client-generator-core/src/http.ts +++ b/packages/typespec-client-generator-core/src/http.ts @@ -45,7 +45,7 @@ import { isAcceptHeader, isContentTypeHeader, isNeverOrVoidType, - isNullableDeprecated, + isNullableInternal, isSubscriptionId, } from "./internal-utils.js"; import { createDiagnostic } from "./lib.js"; @@ -165,7 +165,7 @@ function getSdkHttpParameters( apiVersions: getAvailableApiVersions(context, tspBody.type, httpOperation.operation), type, optional: false, - nullable: isNullableDeprecated(tspBody.type), // eslint-disable-line deprecation/deprecation + nullable: isNullableInternal(tspBody.type), // eslint-disable-line deprecation/deprecation correspondingMethodParams, }; } @@ -397,7 +397,7 @@ function getSdkHttpResponseAndExceptions( details: getDocHelper(context, header).details, serializedName: getHeaderFieldName(context.program, header), type: clientType, - nullable: isNullableDeprecated(header.type), // eslint-disable-line deprecation/deprecation + nullable: isNullableInternal(header.type), // eslint-disable-line deprecation/deprecation }); } if (innerResponse.body && !isNeverOrVoidType(innerResponse.body.type)) { @@ -437,7 +437,7 @@ function getSdkHttpResponseAndExceptions( httpOperation.operation, httpOperation.operation ), - nullable: body ? isNullableDeprecated(body) : true, // eslint-disable-line deprecation/deprecation + nullable: body ? isNullableInternal(body) : true, // eslint-disable-line deprecation/deprecation }; if (response.statusCodes === "*" || (body && isErrorModel(context.program, body))) { exceptions.set(response.statusCodes, sdkResponse); diff --git a/packages/typespec-client-generator-core/src/interfaces.ts b/packages/typespec-client-generator-core/src/interfaces.ts index d401609f37..eb049175a7 100644 --- a/packages/typespec-client-generator-core/src/interfaces.ts +++ b/packages/typespec-client-generator-core/src/interfaces.ts @@ -155,6 +155,7 @@ enum SdkBuiltInKindsMiscellaneousEnum { plainDate = "plainDate", plainTime = "plainTime", any = "any", + null = "null", } export type SdkBuiltInKinds = diff --git a/packages/typespec-client-generator-core/src/internal-utils.ts b/packages/typespec-client-generator-core/src/internal-utils.ts index 8fb3f67c5a..8f648576d7 100644 --- a/packages/typespec-client-generator-core/src/internal-utils.ts +++ b/packages/typespec-client-generator-core/src/internal-utils.ts @@ -343,7 +343,7 @@ export function createTCGCContext(program: Program): TCGCContext { }; } -export function getNonNullOptions(type: Union): Type[] { +function getNonNullOptions(type: Union): Type[] { return [...type.variants.values()].map((x) => x.type).filter((t) => !isNullType(t)); } @@ -380,7 +380,7 @@ export function getAllResponseBodies( * @param type * @returns */ -export function isNullableDeprecated(type: Type | SdkServiceOperation): boolean { +export function isNullableInternal(type: Type | SdkServiceOperation): boolean { if (type.kind === "Union") { if (getNonNullOptions(type).length < type.variants.size) return true; return Boolean(ignoreDiagnostics(getUnionAsEnum(type))?.nullable); diff --git a/packages/typespec-client-generator-core/src/package.ts b/packages/typespec-client-generator-core/src/package.ts index 30208815e1..3c8e1be390 100644 --- a/packages/typespec-client-generator-core/src/package.ts +++ b/packages/typespec-client-generator-core/src/package.ts @@ -57,7 +57,7 @@ import { getLocationOfOperation, getSdkTypeBaseHelper, isNeverOrVoidType, - isNullableDeprecated, + isNullableInternal, updateWithApiVersionInformation, } from "./internal-utils.js"; import { createDiagnostic } from "./lib.js"; @@ -214,7 +214,7 @@ function getSdkMethodResponse( __raw: operation, kind: "union", values: allResponseBodies, - nullable: isNullableDeprecated(sdkOperation), + nullable: isNullableInternal(sdkOperation), name: createGeneratedName(operation, "UnionResponse"), isGeneratedName: true, }; @@ -224,7 +224,7 @@ function getSdkMethodResponse( return { kind: "method", type, - nullable: isNullableDeprecated(sdkOperation), + nullable: isNullableInternal(sdkOperation), }; } diff --git a/packages/typespec-client-generator-core/src/public-utils.ts b/packages/typespec-client-generator-core/src/public-utils.ts index d9a45ebfa7..26c9564ecf 100644 --- a/packages/typespec-client-generator-core/src/public-utils.ts +++ b/packages/typespec-client-generator-core/src/public-utils.ts @@ -35,6 +35,7 @@ import { listOperationGroups, listOperationsInOperationGroup, } from "./decorators.js"; +import { SdkType } from "./interfaces.js"; import { TCGCContext, TspLiteralType, @@ -604,3 +605,22 @@ export function getHttpOperationWithCache( context.httpOperationCache.set(operation, httpOperation); return httpOperation; } + +/** + * Determines if a type is nullable. + * @param type + * @returns + */ +export function isNullable(type: SdkType): boolean { + if (type.kind !== "union") return false; + let nullCount = 0; + let nonNullCount = 0; + for (const value of type.values) { + if (value.kind === "null") { + nullCount++; + } else { + nonNullCount++; + } + } + return nullCount > 0 && nonNullCount > 0; +} diff --git a/packages/typespec-client-generator-core/src/types.ts b/packages/typespec-client-generator-core/src/types.ts index f30c328b74..f6b638fa0e 100644 --- a/packages/typespec-client-generator-core/src/types.ts +++ b/packages/typespec-client-generator-core/src/types.ts @@ -79,14 +79,13 @@ import { getAvailableApiVersions, getDocHelper, getLocationOfOperation, - getNonNullOptions, getSdkTypeBaseHelper, intOrFloat, isAzureCoreModel, isMultipartFormData, isMultipartOperation, isNeverOrVoidType, - isNullableDeprecated, + isNullableInternal, updateWithApiVersionInformation, } from "./internal-utils.js"; import { createDiagnostic } from "./lib.js"; @@ -157,7 +156,7 @@ export function addEncodeInfo( propertyType.wireType = diagnostics.pipe( getClientTypeWithDiagnostics(context, encodeData.type) ) as SdkBuiltInType; - if (type.kind === "ModelProperty" && isNullableDeprecated(type.type)) { + if (type.kind === "ModelProperty" && isNullableInternal(type.type)) { // eslint-disable-next-line deprecation/deprecation propertyType.wireType.nullable = true; } @@ -171,7 +170,7 @@ export function addEncodeInfo( } else if (type.kind === "ModelProperty" && isHeader(context.program, type)) { propertyType.encode = "rfc7231"; } - if (type.kind === "ModelProperty" && isNullableDeprecated(type.type)) { + if (type.kind === "ModelProperty" && isNullableInternal(type.type)) { // eslint-disable-next-line deprecation/deprecation propertyType.wireType.nullable = true; } @@ -297,14 +296,14 @@ export function getSdkArrayOrDictWithDiagnostics( getClientTypeWithDiagnostics(context, type.indexer.key, operation) ), valueType, - nullableValues: isNullableDeprecated(type.indexer.value!), + nullableValues: isNullableInternal(type.indexer.value!), // eslint-disable-line deprecation/deprecation }); } else if (name === "integer") { // only array's index key name is integer return diagnostics.wrap({ ...getSdkTypeBaseHelper(context, type, "array"), valueType, - nullableValues: isNullableDeprecated(type.indexer.value!), + nullableValues: isNullableInternal(type.indexer.value!), // eslint-disable-line deprecation/deprecation }); } } @@ -344,23 +343,6 @@ export function getSdkUnionWithDiagnostics( operation?: Operation ): [SdkType, readonly Diagnostic[]] { const diagnostics = createDiagnosticCollector(); - const nonNullOptions = getNonNullOptions(type); - if (nonNullOptions.length === 0) { - diagnostics.add(createDiagnostic({ code: "union-null", target: type })); - return diagnostics.wrap(getAnyType(context, type)); - } - - // convert to normal type if the union is type | null - if (nonNullOptions.length === 1) { - const clientType = diagnostics.pipe( - getClientTypeWithDiagnostics(context, nonNullOptions[0], operation) - ); - // eslint-disable-next-line deprecation/deprecation - clientType.nullable = isNullableDeprecated(type); - clientType.__raw = type; - return diagnostics.wrap(clientType); - } - // judge if the union can be converted to enum // if language does not need flatten union as enum // need to filter the case that union is composed of union or enum @@ -376,14 +358,15 @@ export function getSdkUnionWithDiagnostics( } } + const values = [...type.variants.values()].map((x) => + diagnostics.pipe(getClientTypeWithDiagnostics(context, x.type)) + ); return diagnostics.wrap({ ...getSdkTypeBaseHelper(context, type, "union"), name: getLibraryName(context, type) || getGeneratedName(context, type), isGeneratedName: !type.name, - values: nonNullOptions.map((x) => - diagnostics.pipe(getClientTypeWithDiagnostics(context, x, operation)) - ), - nullable: isNullableDeprecated(type), + values, + nullable: isNullableInternal(type), // eslint-disable-line deprecation/deprecation }); } @@ -570,14 +553,14 @@ export function getSdkModelWithDiagnostics( sdkType.additionalProperties = diagnostics.pipe( getClientTypeWithDiagnostics(context, type.sourceModel!.indexer!.value!, operation) ); - sdkType.additionalPropertiesNullable = isNullableDeprecated(type.sourceModel!.indexer!.value!); + sdkType.additionalPropertiesNullable = isNullableInternal(type.sourceModel!.indexer!.value!); } // model MyModel { ...Record<>} should be model with additional properties if (type.indexer) { sdkType.additionalProperties = diagnostics.pipe( getClientTypeWithDiagnostics(context, type.indexer.value, operation) ); - sdkType.additionalPropertiesNullable = isNullableDeprecated(type.indexer.value); + sdkType.additionalPropertiesNullable = isNullableInternal(type.indexer.value); } // propreties should be generated first since base model'sdiscriminator handling is depend on derived model's properties diagnostics.pipe(addPropertiesToModelType(context, type, sdkType, operation)); @@ -590,7 +573,7 @@ export function getSdkModelWithDiagnostics( if (baseModel.kind === "dict") { // model MyModel extends Record<> {} should be model with additional properties sdkType.additionalProperties = baseModel.valueType; - sdkType.additionalPropertiesNullable = isNullableDeprecated(baseModel.valueType.__raw!); + sdkType.additionalPropertiesNullable = isNullableInternal(baseModel.valueType.__raw!); } else { sdkType.baseModel = baseModel; } @@ -637,13 +620,13 @@ function getSdkEnumValueType( } function getUnionAsEnumValueType(context: TCGCContext, union: Union): SdkBuiltInType | undefined { - const nonNullOptions = getNonNullOptions(union); - for (const option of nonNullOptions) { - if (option.kind === "Union") { - const ret = getUnionAsEnumValueType(context, option); + for (const variant of union.variants.values()) { + const variantType = variant.type; + if (variantType.kind === "Union") { + const ret = getUnionAsEnumValueType(context, variantType); if (ret) return ret; - } else if (option.kind === "Scalar") { - return getClientType(context, option) as SdkBuiltInType; + } else if (variantType.kind === "Scalar") { + return getClientType(context, variantType) as SdkBuiltInType; } } @@ -1003,7 +986,7 @@ export function getSdkModelPropertyTypeBase( name, isGeneratedName: false, optional: type.optional, - nullable: isNullableDeprecated(type.type), + nullable: isNullableInternal(type.type), ...updateWithApiVersionInformation( context, type, From 234344af21db773d548eb20d4c3a6e417f91a03c Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Mon, 20 May 2024 17:58:23 -0400 Subject: [PATCH 03/23] working on getting tests passing --- .../src/internal-utils.ts | 11 +- .../src/public-utils.ts | 32 +++- .../src/types.ts | 26 ++- .../test/types.test.ts | 150 +++++++++++++++--- 4 files changed, 177 insertions(+), 42 deletions(-) diff --git a/packages/typespec-client-generator-core/src/internal-utils.ts b/packages/typespec-client-generator-core/src/internal-utils.ts index 8f648576d7..be8ae8ec71 100644 --- a/packages/typespec-client-generator-core/src/internal-utils.ts +++ b/packages/typespec-client-generator-core/src/internal-utils.ts @@ -26,6 +26,7 @@ import { HttpOperation, HttpStatusCodeRange } from "@typespec/http"; import { getAddedOnVersions, getRemovedOnVersions, getVersions } from "@typespec/versioning"; import { SdkBuiltInKinds, + SdkBuiltInType, SdkClient, SdkEnumType, SdkHttpResponse, @@ -343,7 +344,7 @@ export function createTCGCContext(program: Program): TCGCContext { }; } -function getNonNullOptions(type: Union): Type[] { +export function getNonNullOptions(type: Union): Type[] { return [...type.variants.values()].map((x) => x.type).filter((t) => !isNullType(t)); } @@ -437,3 +438,11 @@ export function getLocationOfOperation(operation: Operation): Namespace | Interf export function isNeverOrVoidType(type: Type): boolean { return isNeverType(type) || isVoidType(type); } + +export function getAnyType(): SdkBuiltInType { + return { + kind: "any", + encode: "string", + nullable: true, // any is nullable + }; +} diff --git a/packages/typespec-client-generator-core/src/public-utils.ts b/packages/typespec-client-generator-core/src/public-utils.ts index 26c9564ecf..ac70abedbb 100644 --- a/packages/typespec-client-generator-core/src/public-utils.ts +++ b/packages/typespec-client-generator-core/src/public-utils.ts @@ -35,14 +35,17 @@ import { listOperationGroups, listOperationsInOperationGroup, } from "./decorators.js"; -import { SdkType } from "./interfaces.js"; +import { SdkType, SdkUnionType } from "./interfaces.js"; import { TCGCContext, TspLiteralType, + getAnyType, getClientNamespaceStringHelper, parseEmitterName, } from "./internal-utils.js"; import { createDiagnostic } from "./lib.js"; +import { getUnionAsEnum } from "@azure-tools/typespec-azure-core"; +import { getSdkUnionEnum } from "./types.js"; /** * Return the default api version for a versioned service. Will return undefined if one does not exist @@ -624,3 +627,30 @@ export function isNullable(type: SdkType): boolean { } return nullCount > 0 && nonNullCount > 0; } + +/** + * Since we don't remove null types from the values of a union type, this helper function helps return the type without null unioned in. + * @param type + */ +export function removeNullFromUnionType(context: TCGCContext, type: SdkUnionType): [SdkType, readonly Diagnostic[]] { + const diagnostics = createDiagnosticCollector(); + const nonNullValues = type.values.filter((value) => value.kind !== "null"); + if (nonNullValues.length === 0) { + diagnostics.add( + createDiagnostic({ + code: "union-null", + target: type.__raw!, + }) + ); + return diagnostics.wrap(getAnyType()); + } + if (nonNullValues.length === 1) { + return diagnostics.wrap(nonNullValues[0]); + } + // const raw = type.__raw; + // if (raw?.kind === "Union" && getUnionAsEnum(raw)) { + // const unionEnum = ignoreDiagnostics(getUnionAsEnum(raw)); + // if (unionEnum) return diagnostics.wrap(getSdkUnionEnum(context, unionEnum)) + // } + return diagnostics.wrap({ ...type, values: type.values.filter((value) => value.kind !== "null") }); +} diff --git a/packages/typespec-client-generator-core/src/types.ts b/packages/typespec-client-generator-core/src/types.ts index f6b638fa0e..d692c0acbd 100644 --- a/packages/typespec-client-generator-core/src/types.ts +++ b/packages/typespec-client-generator-core/src/types.ts @@ -76,6 +76,7 @@ import { } from "./interfaces.js"; import { createGeneratedName, + getAnyType, getAvailableApiVersions, getDocHelper, getLocationOfOperation, @@ -103,12 +104,7 @@ import { UnionEnumVariant } from "../../typespec-azure-core/dist/src/helpers/uni import { getSdkHttpParameter, isSdkHttpParameter } from "./http.js"; import { TCGCContext } from "./internal-utils.js"; -function getAnyType(context: TCGCContext, type: Type): SdkBuiltInType { - return { - ...getSdkTypeBaseHelper(context, type, "any"), - encode: getEncodeHelper(context, type, "any"), - }; -} + function getEncodeHelper(context: TCGCContext, type: Type, kind: string): string { if (type.kind === "ModelProperty" || type.kind === "Scalar") { @@ -192,7 +188,7 @@ export function addEncodeInfo( * @param scalar the original typespec scalar * @returns the corresponding sdk built in kind */ -function getScalarKind(scalar: Scalar): SdkBuiltInKinds { +function getScalarKind(scalar: Scalar | IntrinsicType): SdkBuiltInKinds { if (isSdkBuiltInKind(scalar.name)) { return scalar.name; } @@ -212,7 +208,7 @@ function getSdkBuiltInTypeWithDiagnostics( const diagnostics = createDiagnosticCollector(); if (context.program.checker.isStdType(type) || type.kind === "Intrinsic") { let kind: SdkBuiltInKinds = "any"; - if (type.kind === "Scalar") { + if (type.kind === "Scalar" || type.kind === "Intrinsic") { if (isSdkBuiltInKind(type.name)) { kind = getScalarKind(type); } @@ -242,7 +238,7 @@ function getSdkBuiltInTypeWithDiagnostics( diagnostics.add( createDiagnostic({ code: "unsupported-kind", target: type, format: { kind: type.kind } }) ); - return diagnostics.wrap(getAnyType(context, type)); + return diagnostics.wrap(getAnyType()); } export function getSdkBuiltInType( @@ -353,19 +349,15 @@ export function getSdkUnionWithDiagnostics( }) ) { const unionAsEnum = diagnostics.pipe(getUnionAsEnum(type)); - if (unionAsEnum) { + if (unionAsEnum && !isNullableInternal(type)) { return diagnostics.wrap(getSdkUnionEnum(context, unionAsEnum, operation)); } } - - const values = [...type.variants.values()].map((x) => - diagnostics.pipe(getClientTypeWithDiagnostics(context, x.type)) - ); return diagnostics.wrap({ ...getSdkTypeBaseHelper(context, type, "union"), name: getLibraryName(context, type) || getGeneratedName(context, type), isGeneratedName: !type.name, - values, + values: [...type.variants.values()].map((x) => diagnostics.pipe(getClientTypeWithDiagnostics(context, x.type, operation))), nullable: isNullableInternal(type), // eslint-disable-line deprecation/deprecation }); } @@ -701,7 +693,7 @@ function getSdkUnionEnumValues( return values; } -function getSdkUnionEnum(context: TCGCContext, type: UnionEnum, operation?: Operation) { +export function getSdkUnionEnum(context: TCGCContext, type: UnionEnum, operation?: Operation) { let sdkType = context.modelsMap?.get(type.union) as SdkEnumType | undefined; if (!sdkType) { const union = type.union; @@ -862,7 +854,7 @@ export function getClientTypeWithDiagnostics( retval = getSdkEnumValue(context, enumType, type); break; default: - retval = getAnyType(context, type); + retval = getAnyType(); diagnostics.add( createDiagnostic({ code: "unsupported-kind", target: type, format: { kind: type.kind } }) ); diff --git a/packages/typespec-client-generator-core/test/types.test.ts b/packages/typespec-client-generator-core/test/types.test.ts index 62983aee26..19b93771b2 100644 --- a/packages/typespec-client-generator-core/test/types.test.ts +++ b/packages/typespec-client-generator-core/test/types.test.ts @@ -1,18 +1,19 @@ import { AzureCoreTestLibrary } from "@azure-tools/typespec-azure-core/testing"; -import { Enum, Model, Union } from "@typespec/compiler"; +import { Enum, Model, Union, ignoreDiagnostics } from "@typespec/compiler"; import { expectDiagnostics } from "@typespec/compiler/testing"; import { deepEqual, deepStrictEqual, ok, strictEqual } from "assert"; import { beforeEach, describe, it } from "vitest"; import { SdkArrayType, SdkBodyModelPropertyType, + SdkBuiltInType, SdkEnumType, SdkModelType, SdkType, SdkUnionType, UsageFlags, } from "../src/interfaces.js"; -import { isErrorOrChildOfError } from "../src/public-utils.js"; +import { isErrorOrChildOfError, isNullable, removeNullFromUnionType } from "../src/public-utils.js"; import { getAllModels, getAllModelsWithDiagnostics, @@ -477,16 +478,24 @@ describe("typespec-client-generator-core: types", () => { } ` ); - const sdkType = getSdkTypeHelper(runner); + const sdkTypeWithNull = getSdkTypeHelper(runner); + strictEqual(sdkTypeWithNull.kind, "union"); + strictEqual(sdkTypeWithNull.values.length, 2); + strictEqual(sdkTypeWithNull.values[0].kind, "utcDateTime"); + strictEqual(sdkTypeWithNull.values[1].kind, "null"); + const sdkType = ignoreDiagnostics(removeNullFromUnionType(runner.context, sdkTypeWithNull)); strictEqual(sdkType.kind, "utcDateTime"); strictEqual(sdkType.wireType.kind, "int64"); strictEqual(sdkType.encode, "unixTimestamp"); + strictEqual(isNullable(sdkType), true); // eslint-disable-next-line deprecation/deprecation strictEqual(sdkType.nullable, true); + strictEqual(isNullable(sdkType.wireType), true); // eslint-disable-next-line deprecation/deprecation strictEqual(sdkType.wireType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; strictEqual(nameProp.nullable, true); + strictEqual(isNullable(nameProp.type), true); }); it("unixTimestamp array", async function () { @@ -543,10 +552,16 @@ describe("typespec-client-generator-core: types", () => { `); const sdkType = getSdkTypeHelper(runner); - strictEqual(sdkType.kind, "float32"); + strictEqual(sdkType.kind, "union"); + strictEqual(sdkType.values.length, 2); + strictEqual(sdkType.values[0].kind, "float32"); + strictEqual(sdkType.values[1].kind, "null"); + strictEqual(isNullable(sdkType), true); + strictEqual(ignoreDiagnostics(removeNullFromUnionType(runner.context, sdkType)).kind, "float32"); // eslint-disable-next-line deprecation/deprecation strictEqual(sdkType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; + strictEqual(isNullable(nameProp.type), true); strictEqual(nameProp.nullable, true); }); @@ -561,12 +576,21 @@ describe("typespec-client-generator-core: types", () => { const sdkType = getSdkTypeHelper(runner); strictEqual(sdkType.kind, "union"); - strictEqual(sdkType.values.length, 2); + strictEqual(sdkType.values.length, 3); strictEqual(sdkType.values[0].kind, "string"); strictEqual(sdkType.values[1].kind, "float32"); + strictEqual(sdkType.values[2].kind, "null"); + strictEqual(isNullable(sdkType), true); + + const sdkTypeWithoutNull = ignoreDiagnostics(removeNullFromUnionType(runner.context, sdkType)); + strictEqual(sdkTypeWithoutNull.kind, "union"); + strictEqual(sdkTypeWithoutNull.values.length, 2); + strictEqual(sdkTypeWithoutNull.values[0].kind, "string"); + strictEqual(sdkTypeWithoutNull.values[1].kind, "float32"); // eslint-disable-next-line deprecation/deprecation strictEqual(sdkType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; + strictEqual(isNullable(nameProp.type), true); strictEqual(nameProp.nullable, true); }); @@ -582,10 +606,16 @@ describe("typespec-client-generator-core: types", () => { const sdkType = getSdkTypeHelper(runner); strictEqual(sdkType.kind, "dict"); const elementType = sdkType.valueType; - strictEqual(elementType.kind, "float32"); + strictEqual(elementType.kind, "union"); + strictEqual(elementType.values.length, 2); + strictEqual(elementType.values[0].kind, "float32"); + strictEqual(elementType.values[1].kind, "null"); + strictEqual(ignoreDiagnostics(removeNullFromUnionType(runner.context, elementType)).kind, "float32"); + strictEqual(isNullable(elementType), true); // eslint-disable-next-line deprecation/deprecation strictEqual(elementType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; + strictEqual(isNullable(nameProp.type), false); strictEqual(nameProp.nullable, false); strictEqual(sdkType.nullableValues, true); }); @@ -603,13 +633,17 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkType.kind, "dict"); const elementType = sdkType.valueType; strictEqual(elementType.kind, "union"); - strictEqual(elementType.values.length, 2); + strictEqual(elementType.values.length, 3); strictEqual(elementType.values[0].kind, "string"); strictEqual(elementType.values[1].kind, "float32"); + strictEqual(elementType.values[2].kind, "null"); // eslint-disable-next-line deprecation/deprecation strictEqual(elementType.nullable, true); + strictEqual(isNullable(elementType), true); + const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; strictEqual(nameProp.nullable, false); + strictEqual(isNullable(nameProp.type), false); strictEqual(sdkType.nullableValues, true); }); @@ -625,12 +659,15 @@ describe("typespec-client-generator-core: types", () => { const sdkType = getSdkTypeHelper(runner); strictEqual(sdkType.kind, "array"); const elementType = sdkType.valueType; - strictEqual(elementType.kind, "float32"); + strictEqual(elementType.kind, "union"); + strictEqual(ignoreDiagnostics(removeNullFromUnionType(runner.context, elementType)).kind, "float32"); // eslint-disable-next-line deprecation/deprecation strictEqual(elementType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; strictEqual(nameProp.nullable, false); + strictEqual(isNullable(nameProp.type), false); strictEqual(sdkType.nullableValues, true); + strictEqual(isNullable(elementType), true); }); it("array with nullable with more types", async function () { @@ -646,14 +683,18 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkType.kind, "array"); const elementType = sdkType.valueType; strictEqual(elementType.kind, "union"); - strictEqual(elementType.values.length, 2); + strictEqual(elementType.values.length, 3); strictEqual(elementType.values[0].kind, "string"); strictEqual(elementType.values[1].kind, "float32"); + strictEqual(elementType.values[2].kind, "null") // eslint-disable-next-line deprecation/deprecation strictEqual(elementType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; strictEqual(nameProp.nullable, false); strictEqual(sdkType.nullableValues, true); + strictEqual(isNullable(nameProp.type), false); + strictEqual(isNullable(elementType), true); + strictEqual(ignoreDiagnostics(removeNullFromUnionType(runner.context, elementType)).kind, "union"); }); it("additional property is nullable", async function () { @@ -684,26 +725,38 @@ describe("typespec-client-generator-core: types", () => { const extendsType = models.find((x) => x.name === "TestExtends"); ok(extendsType); strictEqual(extendsType.kind, "model"); - strictEqual(extendsType.additionalProperties?.kind, "string"); + const additionalProperties = extendsType.additionalProperties; + ok(additionalProperties); + strictEqual(additionalProperties.kind, "union"); + strictEqual(isNullable(additionalProperties), true); // eslint-disable-next-line deprecation/deprecation strictEqual(extendsType.additionalProperties?.nullable, true); strictEqual(extendsType.additionalPropertiesNullable, true); + strictEqual(ignoreDiagnostics(removeNullFromUnionType(runner.context, additionalProperties)).kind, "string"); const isType = models.find((x) => x.name === "TestIs"); ok(isType); strictEqual(isType.kind, "model"); - strictEqual(isType.additionalProperties?.kind, "string"); + const isTypeAdditionalProperties = isType.additionalProperties; + ok(isTypeAdditionalProperties); + strictEqual(isTypeAdditionalProperties.kind, "union"); + strictEqual(isNullable(isTypeAdditionalProperties), true); // eslint-disable-next-line deprecation/deprecation - strictEqual(isType.additionalProperties?.nullable, true); + strictEqual(isTypeAdditionalProperties.nullable, true); strictEqual(isType.additionalPropertiesNullable, true); + strictEqual(ignoreDiagnostics(removeNullFromUnionType(runner.context, isTypeAdditionalProperties)).kind, "string"); const spreadType = models.find((x) => x.name === "TestSpread"); ok(spreadType); strictEqual(spreadType.kind, "model"); - strictEqual(spreadType.additionalProperties?.kind, "string"); + const spreadTypeAdditionalProperties = spreadType.additionalProperties; + ok(spreadTypeAdditionalProperties); + strictEqual(spreadTypeAdditionalProperties.kind, "union"); // eslint-disable-next-line deprecation/deprecation - strictEqual(spreadType.additionalProperties?.nullable, true); + strictEqual(spreadTypeAdditionalProperties.nullable, true); strictEqual(spreadType.additionalPropertiesNullable, true); + strictEqual(isNullable(spreadTypeAdditionalProperties), true); + strictEqual(ignoreDiagnostics(removeNullFromUnionType(runner.context, spreadTypeAdditionalProperties)).kind, "string"); }); it("additional property nullable with more types", async function () { @@ -861,12 +914,22 @@ describe("typespec-client-generator-core: types", () => { } `); - const sdkType = getSdkTypeHelper(runner); + const sdkTypeWithNull = getSdkTypeHelper(runner); + strictEqual(sdkTypeWithNull.kind, "union"); + strictEqual(sdkTypeWithNull.values.length, 2); + strictEqual(sdkTypeWithNull.values[0].kind, "enum"); + strictEqual(sdkTypeWithNull.values[0].name, "PetKind"); + strictEqual(sdkTypeWithNull.values[1].kind, "null"); + // eslint-disable-next-line deprecation/deprecation + strictEqual(sdkTypeWithNull.nullable, true); + strictEqual(isNullable(sdkTypeWithNull), true); + + const sdkType = ignoreDiagnostics(removeNullFromUnionType(runner.context, sdkTypeWithNull)); strictEqual(sdkType.kind, "enum"); + strictEqual(sdkTypeWithNull.values[0], sdkType); strictEqual(sdkType.isUnionAsEnum, false); strictEqual(sdkType.name, "PetKind"); - // eslint-disable-next-line deprecation/deprecation - strictEqual(sdkType.nullable, true); + const pet = runner.context.experimental_sdkPackage.models[0].properties[0]; strictEqual(pet.nullable, true); const values = sdkType.values; @@ -892,12 +955,18 @@ describe("typespec-client-generator-core: types", () => { strictEqual(models.length, 2); const model = models.find((x) => x.kind === "model" && x.name === "Test"); ok(model); - const sdkType = model.properties[0].type; + const sdkTypeWithNull = model.properties[0].type; + strictEqual(sdkTypeWithNull.kind, "union"); + strictEqual(sdkTypeWithNull.values.length, 2); + strictEqual(sdkTypeWithNull.values[0].kind, "model"); + strictEqual(sdkTypeWithNull.values[0].name, "PropertyModel"); + strictEqual(sdkTypeWithNull.values[1].kind, "null"); + + const sdkType = ignoreDiagnostics(removeNullFromUnionType(runner.context, sdkTypeWithNull)); strictEqual(sdkType.kind, "model"); strictEqual(sdkType.name, "PropertyModel"); - // eslint-disable-next-line deprecation/deprecation - strictEqual(sdkType.nullable, true); strictEqual(model.properties[0].nullable, true); + strictEqual(sdkTypeWithNull.values[0], sdkType) }); it("mix types", async function () { @@ -931,6 +1000,7 @@ describe("typespec-client-generator-core: types", () => { // eslint-disable-next-line deprecation/deprecation strictEqual(model.properties[0].type.nullable, false); strictEqual(model.properties[0].nullable, false); + strictEqual(isNullable(model.properties[0].type), false); const unionType = model.properties[0].type; strictEqual(unionType.kind, "union"); for (const v of unionType.values) { @@ -944,7 +1014,13 @@ describe("typespec-client-generator-core: types", () => { // eslint-disable-next-line deprecation/deprecation strictEqual(nullableModel.properties[0].type.nullable, true); strictEqual(nullableModel.properties[0].nullable, true); - for (const v of nullableModel.properties[0].type.values) { + strictEqual(nullableModel.properties[0].type.values.length, 4); + strictEqual(nullableModel.properties[0].type.values[3].kind, "null") + + // now check without null with help of helper function + const sdkTypeWithoutNull = ignoreDiagnostics(removeNullFromUnionType(runner.context, nullableModel.properties[0].type)); + strictEqual(sdkTypeWithoutNull.kind, "union"); + for (const v of sdkTypeWithoutNull.values) { if (v.kind === "model") { strictEqual(v.name, "ModelType"); } else { @@ -1521,7 +1597,14 @@ describe("typespec-client-generator-core: types", () => { ` )) as { Test: Union }; - const enumType = getClientType(runner.context, Test); + const enumTypeWithNull = getClientType(runner.context, Test); + strictEqual(enumTypeWithNull.kind, "union"); + strictEqual(enumTypeWithNull.name, "Test"); + strictEqual(isNullable(enumTypeWithNull), true); + strictEqual(enumTypeWithNull.values.length, 4); + strictEqual(enumTypeWithNull.values[3].kind, "null"); + + const enumType = ignoreDiagnostics(removeNullFromUnionType(runner.context, enumTypeWithNull)); strictEqual(enumType.kind, "enum"); strictEqual(enumType.name, "Test"); // eslint-disable-next-line deprecation/deprecation @@ -1573,12 +1656,21 @@ describe("typespec-client-generator-core: types", () => { )) as { Test: Union }; const unionType = getClientType(runner.context, Test); + strictEqual(unionType.kind, "union"); strictEqual(unionType.name, "Test"); // eslint-disable-next-line deprecation/deprecation strictEqual(unionType.nullable, true); + strictEqual(isNullable(unionType), true); + + const unionTypeWithoutNull = ignoreDiagnostics(removeNullFromUnionType(runner.context, unionType)); + strictEqual(unionTypeWithoutNull.kind, "union"); + strictEqual(unionTypeWithoutNull.values.length, 3); + strictEqual(unionTypeWithoutNull.name, "Test"); + strictEqual(isNullable(unionTypeWithoutNull), false); + const values = unionType.values; - strictEqual(values.length, 3); + strictEqual(values.length, 4); const a = values[0] as SdkEnumType; strictEqual(a.name, "A"); strictEqual(a.kind, "enum"); @@ -1588,12 +1680,18 @@ describe("typespec-client-generator-core: types", () => { strictEqual(a.values[1].name, "A2"); strictEqual(a.values[1].value, "A2"); + // make sure values are the same after removing null + strictEqual(values[0], unionTypeWithoutNull.values[0]); + const b = values[1] as SdkEnumType; strictEqual(b.name, "B"); strictEqual(b.kind, "enum"); strictEqual(b.isUnionAsEnum, true); strictEqual(b.values[0].name, "B"); strictEqual(b.values[0].value, "B"); + + // make sure values are the same after removing null + strictEqual(values[1], unionTypeWithoutNull.values[1]); const c = values[2] as SdkEnumType; strictEqual(c.name, "C"); @@ -1601,6 +1699,12 @@ describe("typespec-client-generator-core: types", () => { strictEqual(c.isUnionAsEnum, false); strictEqual(c.values[0].name, "C"); strictEqual(c.values[0].value, "C"); + + // make sure values are the same after removing null + strictEqual(values[2], unionTypeWithoutNull.values[2]); + + const d = values[3] as SdkBuiltInType; + strictEqual(d.kind, "null"); }); it("anonymous union as enum with hierarchy", async () => { From 0cc25092050a1521ae2ea240a25fe1ce3fbab306 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Mon, 20 May 2024 18:20:11 -0400 Subject: [PATCH 04/23] remove null from union type simplify --- .../src/public-utils.ts | 26 ++--- .../src/types.ts | 30 ++++++ .../test/types.test.ts | 102 +++++++++++------- 3 files changed, 99 insertions(+), 59 deletions(-) diff --git a/packages/typespec-client-generator-core/src/public-utils.ts b/packages/typespec-client-generator-core/src/public-utils.ts index ac70abedbb..11abda2f91 100644 --- a/packages/typespec-client-generator-core/src/public-utils.ts +++ b/packages/typespec-client-generator-core/src/public-utils.ts @@ -630,27 +630,13 @@ export function isNullable(type: SdkType): boolean { /** * Since we don't remove null types from the values of a union type, this helper function helps return the type without null unioned in. + * + * Only call after first making sure the type isNullable with our helper function * @param type */ -export function removeNullFromUnionType(context: TCGCContext, type: SdkUnionType): [SdkType, readonly Diagnostic[]] { - const diagnostics = createDiagnosticCollector(); - const nonNullValues = type.values.filter((value) => value.kind !== "null"); - if (nonNullValues.length === 0) { - diagnostics.add( - createDiagnostic({ - code: "union-null", - target: type.__raw!, - }) - ); - return diagnostics.wrap(getAnyType()); - } - if (nonNullValues.length === 1) { - return diagnostics.wrap(nonNullValues[0]); +export function removeNullFromUnionType(type: SdkUnionType): SdkType { + return { + ...type, + values: type.values.filter((value) => value.kind !== "null"), } - // const raw = type.__raw; - // if (raw?.kind === "Union" && getUnionAsEnum(raw)) { - // const unionEnum = ignoreDiagnostics(getUnionAsEnum(raw)); - // if (unionEnum) return diagnostics.wrap(getSdkUnionEnum(context, unionEnum)) - // } - return diagnostics.wrap({ ...type, values: type.values.filter((value) => value.kind !== "null") }); } diff --git a/packages/typespec-client-generator-core/src/types.ts b/packages/typespec-client-generator-core/src/types.ts index d692c0acbd..3050075b40 100644 --- a/packages/typespec-client-generator-core/src/types.ts +++ b/packages/typespec-client-generator-core/src/types.ts @@ -28,6 +28,7 @@ import { ignoreDiagnostics, isErrorModel, isNeverType, + isNullType, } from "@typespec/compiler"; import { Authentication, @@ -80,6 +81,7 @@ import { getAvailableApiVersions, getDocHelper, getLocationOfOperation, + getNonNullOptions, getSdkTypeBaseHelper, intOrFloat, isAzureCoreModel, @@ -339,6 +341,34 @@ export function getSdkUnionWithDiagnostics( operation?: Operation ): [SdkType, readonly Diagnostic[]] { const diagnostics = createDiagnosticCollector(); + const nonNullOptions = getNonNullOptions(type); + if (nonNullOptions.length === 0) { + diagnostics.add(createDiagnostic({ code: "union-null", target: type })); + return diagnostics.wrap(getAnyType()); + } + // convert to normal type if the union is type | null + if (nonNullOptions.length === 1) { + const clientType = diagnostics.pipe( + getClientTypeWithDiagnostics(context, nonNullOptions[0], operation) + ); + const nullType: SdkBuiltInType = { + ...clientType, + kind: "null", + nullable: true, + encode: "string", + }; + + const name = clientType.kind === "union" ? clientType.name : ""; + const isGeneratedName = clientType.kind === "union" ? clientType.isGeneratedName : true; + return diagnostics.wrap({ + ...clientType, + kind: "union", + values: [clientType, nullType], + name, + isGeneratedName, + nullable: true, + }) + } // judge if the union can be converted to enum // if language does not need flatten union as enum // need to filter the case that union is composed of union or enum diff --git a/packages/typespec-client-generator-core/test/types.test.ts b/packages/typespec-client-generator-core/test/types.test.ts index 19b93771b2..cf9c82208e 100644 --- a/packages/typespec-client-generator-core/test/types.test.ts +++ b/packages/typespec-client-generator-core/test/types.test.ts @@ -364,16 +364,23 @@ describe("typespec-client-generator-core: types", () => { } ` ); - const sdkType = getSdkTypeHelper(runner); + const sdkTypeWithNull = getSdkTypeHelper(runner); + strictEqual(sdkTypeWithNull.kind, "union"); + strictEqual(sdkTypeWithNull.values.length, 2); + strictEqual(sdkTypeWithNull.values[0].kind, "duration"); + strictEqual(sdkTypeWithNull.values[1].kind, "null"); + strictEqual(isNullable(sdkTypeWithNull), true); + + const sdkType = removeNullFromUnionType(sdkTypeWithNull); strictEqual(sdkType.kind, "duration"); strictEqual(sdkType.wireType.kind, "float"); strictEqual(sdkType.encode, "seconds"); - // eslint-disable-next-line deprecation/deprecation - strictEqual(sdkType.nullable, true); + strictEqual(isNullable(sdkType.wireType), true); // eslint-disable-next-line deprecation/deprecation strictEqual(sdkType.wireType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; strictEqual(nameProp.nullable, true); + strictEqual(isNullable(nameProp.type), true); }); it("float seconds decorated scalar", async function () { @@ -483,7 +490,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkTypeWithNull.values.length, 2); strictEqual(sdkTypeWithNull.values[0].kind, "utcDateTime"); strictEqual(sdkTypeWithNull.values[1].kind, "null"); - const sdkType = ignoreDiagnostics(removeNullFromUnionType(runner.context, sdkTypeWithNull)); + const sdkType = removeNullFromUnionType(sdkTypeWithNull); strictEqual(sdkType.kind, "utcDateTime"); strictEqual(sdkType.wireType.kind, "int64"); strictEqual(sdkType.encode, "unixTimestamp"); @@ -557,7 +564,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkType.values[0].kind, "float32"); strictEqual(sdkType.values[1].kind, "null"); strictEqual(isNullable(sdkType), true); - strictEqual(ignoreDiagnostics(removeNullFromUnionType(runner.context, sdkType)).kind, "float32"); + strictEqual(removeNullFromUnionType(sdkType).kind, "float32"); // eslint-disable-next-line deprecation/deprecation strictEqual(sdkType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; @@ -582,7 +589,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkType.values[2].kind, "null"); strictEqual(isNullable(sdkType), true); - const sdkTypeWithoutNull = ignoreDiagnostics(removeNullFromUnionType(runner.context, sdkType)); + const sdkTypeWithoutNull = removeNullFromUnionType(sdkType); strictEqual(sdkTypeWithoutNull.kind, "union"); strictEqual(sdkTypeWithoutNull.values.length, 2); strictEqual(sdkTypeWithoutNull.values[0].kind, "string"); @@ -610,7 +617,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(elementType.values.length, 2); strictEqual(elementType.values[0].kind, "float32"); strictEqual(elementType.values[1].kind, "null"); - strictEqual(ignoreDiagnostics(removeNullFromUnionType(runner.context, elementType)).kind, "float32"); + strictEqual(removeNullFromUnionType(elementType).kind, "float32"); strictEqual(isNullable(elementType), true); // eslint-disable-next-line deprecation/deprecation strictEqual(elementType.nullable, true); @@ -660,7 +667,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkType.kind, "array"); const elementType = sdkType.valueType; strictEqual(elementType.kind, "union"); - strictEqual(ignoreDiagnostics(removeNullFromUnionType(runner.context, elementType)).kind, "float32"); + strictEqual(removeNullFromUnionType(elementType).kind, "float32"); // eslint-disable-next-line deprecation/deprecation strictEqual(elementType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; @@ -694,7 +701,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkType.nullableValues, true); strictEqual(isNullable(nameProp.type), false); strictEqual(isNullable(elementType), true); - strictEqual(ignoreDiagnostics(removeNullFromUnionType(runner.context, elementType)).kind, "union"); + strictEqual(removeNullFromUnionType(elementType).kind, "union"); }); it("additional property is nullable", async function () { @@ -730,9 +737,9 @@ describe("typespec-client-generator-core: types", () => { strictEqual(additionalProperties.kind, "union"); strictEqual(isNullable(additionalProperties), true); // eslint-disable-next-line deprecation/deprecation - strictEqual(extendsType.additionalProperties?.nullable, true); + strictEqual(additionalProperties.nullable, true); strictEqual(extendsType.additionalPropertiesNullable, true); - strictEqual(ignoreDiagnostics(removeNullFromUnionType(runner.context, additionalProperties)).kind, "string"); + strictEqual(removeNullFromUnionType(additionalProperties).kind, "string"); const isType = models.find((x) => x.name === "TestIs"); ok(isType); @@ -744,7 +751,7 @@ describe("typespec-client-generator-core: types", () => { // eslint-disable-next-line deprecation/deprecation strictEqual(isTypeAdditionalProperties.nullable, true); strictEqual(isType.additionalPropertiesNullable, true); - strictEqual(ignoreDiagnostics(removeNullFromUnionType(runner.context, isTypeAdditionalProperties)).kind, "string"); + strictEqual(removeNullFromUnionType(isTypeAdditionalProperties).kind, "string"); const spreadType = models.find((x) => x.name === "TestSpread"); ok(spreadType); @@ -756,7 +763,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(spreadTypeAdditionalProperties.nullable, true); strictEqual(spreadType.additionalPropertiesNullable, true); strictEqual(isNullable(spreadTypeAdditionalProperties), true); - strictEqual(ignoreDiagnostics(removeNullFromUnionType(runner.context, spreadTypeAdditionalProperties)).kind, "string"); + strictEqual(removeNullFromUnionType(spreadTypeAdditionalProperties).kind, "string"); }); it("additional property nullable with more types", async function () { @@ -787,41 +794,58 @@ describe("typespec-client-generator-core: types", () => { const extendsType = models.find((x) => x.name === "TestExtends"); ok(extendsType); strictEqual(extendsType.kind, "model"); - strictEqual(extendsType.additionalProperties?.kind, "union"); - strictEqual(extendsType.additionalProperties?.name, "TestExtendsAdditionalProperty"); - strictEqual(extendsType.additionalProperties?.isGeneratedName, true); - strictEqual(extendsType.additionalProperties?.values.length, 2); - strictEqual(extendsType.additionalProperties?.values[0].kind, "string"); - strictEqual(extendsType.additionalProperties?.values[1].kind, "float32"); + + const extendsTypeAdditionalProperties = extendsType.additionalProperties; + ok(extendsTypeAdditionalProperties); + strictEqual(extendsTypeAdditionalProperties.kind, "union"); + strictEqual(extendsTypeAdditionalProperties.name, "TestExtendsAdditionalProperty"); + strictEqual(extendsTypeAdditionalProperties.isGeneratedName, true); + strictEqual(extendsTypeAdditionalProperties.values.length, 3); + strictEqual(extendsTypeAdditionalProperties.values[0].kind, "string"); + strictEqual(extendsTypeAdditionalProperties.values[1].kind, "float32"); + strictEqual(extendsTypeAdditionalProperties.values[2].kind, "null"); + strictEqual(isNullable(extendsTypeAdditionalProperties), true); // eslint-disable-next-line deprecation/deprecation - strictEqual(extendsType.additionalProperties?.nullable, true); + strictEqual(extendsTypeAdditionalProperties.nullable, true); strictEqual(extendsType.additionalPropertiesNullable, true); + strictEqual(removeNullFromUnionType(extendsTypeAdditionalProperties).kind, "union"); const isType = models.find((x) => x.name === "TestIs"); ok(isType); strictEqual(isType.kind, "model"); - strictEqual(isType.additionalProperties?.kind, "union"); - strictEqual(isType.additionalProperties?.name, "TestIsAdditionalProperty"); - strictEqual(isType.additionalProperties?.isGeneratedName, true); - strictEqual(isType.additionalProperties?.values.length, 2); - strictEqual(isType.additionalProperties?.values[0].kind, "string"); - strictEqual(isType.additionalProperties?.values[1].kind, "float32"); + const isTypeAdditionalProperties = isType.additionalProperties; + ok(isTypeAdditionalProperties); + strictEqual(isTypeAdditionalProperties.kind, "union"); + strictEqual(isTypeAdditionalProperties.name, "TestIsAdditionalProperty"); + strictEqual(isTypeAdditionalProperties.isGeneratedName, true); + strictEqual(isTypeAdditionalProperties.values.length, 3); + strictEqual(isTypeAdditionalProperties.values[0].kind, "string"); + strictEqual(isTypeAdditionalProperties.values[1].kind, "float32"); + strictEqual(isTypeAdditionalProperties.values[2].kind, "null"); + strictEqual(isNullable(isTypeAdditionalProperties), true); // eslint-disable-next-line deprecation/deprecation - strictEqual(isType.additionalProperties?.nullable, true); + strictEqual(isTypeAdditionalProperties.nullable, true); strictEqual(isType.additionalPropertiesNullable, true); + strictEqual(removeNullFromUnionType(isTypeAdditionalProperties).kind, "union"); const spreadType = models.find((x) => x.name === "TestSpread"); ok(spreadType); strictEqual(spreadType.kind, "model"); - strictEqual(spreadType.additionalProperties?.kind, "union"); - strictEqual(spreadType.additionalProperties?.name, "TestSpreadAdditionalProperty"); - strictEqual(spreadType.additionalProperties?.isGeneratedName, true); - strictEqual(spreadType.additionalProperties?.values.length, 2); - strictEqual(spreadType.additionalProperties?.values[0].kind, "string"); - strictEqual(spreadType.additionalProperties?.values[1].kind, "float32"); + + const spreadTypeAdditionalProperties = spreadType.additionalProperties; + ok(spreadTypeAdditionalProperties); + strictEqual(spreadTypeAdditionalProperties.kind, "union"); + strictEqual(spreadTypeAdditionalProperties.name, "TestSpreadAdditionalProperty"); + strictEqual(spreadTypeAdditionalProperties.isGeneratedName, true); + strictEqual(spreadTypeAdditionalProperties.values.length, 3); + strictEqual(spreadTypeAdditionalProperties.values[0].kind, "string"); + strictEqual(spreadTypeAdditionalProperties.values[1].kind, "float32"); + strictEqual(spreadTypeAdditionalProperties.values[2].kind, "null"); + strictEqual(isNullable(spreadTypeAdditionalProperties), true); // eslint-disable-next-line deprecation/deprecation - strictEqual(spreadType.additionalProperties?.nullable, true); + strictEqual(spreadTypeAdditionalProperties.nullable, true); strictEqual(spreadType.additionalPropertiesNullable, true); + strictEqual(removeNullFromUnionType(spreadTypeAdditionalProperties).kind, "union"); }); it("model with simple union property", async function () { @@ -924,7 +948,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkTypeWithNull.nullable, true); strictEqual(isNullable(sdkTypeWithNull), true); - const sdkType = ignoreDiagnostics(removeNullFromUnionType(runner.context, sdkTypeWithNull)); + const sdkType = removeNullFromUnionType(sdkTypeWithNull); strictEqual(sdkType.kind, "enum"); strictEqual(sdkTypeWithNull.values[0], sdkType); strictEqual(sdkType.isUnionAsEnum, false); @@ -962,7 +986,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkTypeWithNull.values[0].name, "PropertyModel"); strictEqual(sdkTypeWithNull.values[1].kind, "null"); - const sdkType = ignoreDiagnostics(removeNullFromUnionType(runner.context, sdkTypeWithNull)); + const sdkType = removeNullFromUnionType(sdkTypeWithNull); strictEqual(sdkType.kind, "model"); strictEqual(sdkType.name, "PropertyModel"); strictEqual(model.properties[0].nullable, true); @@ -1018,7 +1042,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(nullableModel.properties[0].type.values[3].kind, "null") // now check without null with help of helper function - const sdkTypeWithoutNull = ignoreDiagnostics(removeNullFromUnionType(runner.context, nullableModel.properties[0].type)); + const sdkTypeWithoutNull = removeNullFromUnionType(nullableModel.properties[0].type); strictEqual(sdkTypeWithoutNull.kind, "union"); for (const v of sdkTypeWithoutNull.values) { if (v.kind === "model") { @@ -1604,7 +1628,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(enumTypeWithNull.values.length, 4); strictEqual(enumTypeWithNull.values[3].kind, "null"); - const enumType = ignoreDiagnostics(removeNullFromUnionType(runner.context, enumTypeWithNull)); + const enumType = removeNullFromUnionType(enumTypeWithNull); strictEqual(enumType.kind, "enum"); strictEqual(enumType.name, "Test"); // eslint-disable-next-line deprecation/deprecation @@ -1663,7 +1687,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(unionType.nullable, true); strictEqual(isNullable(unionType), true); - const unionTypeWithoutNull = ignoreDiagnostics(removeNullFromUnionType(runner.context, unionType)); + const unionTypeWithoutNull = removeNullFromUnionType(unionType); strictEqual(unionTypeWithoutNull.kind, "union"); strictEqual(unionTypeWithoutNull.values.length, 3); strictEqual(unionTypeWithoutNull.name, "Test"); From bbac674918272ffe3e8b48c86c479090db6d9590 Mon Sep 17 00:00:00 2001 From: tadelesh Date: Tue, 21 May 2024 18:04:34 +0800 Subject: [PATCH 05/23] fix logic and test --- .../DataPlane Generation - DPG/06types.mdx | 38 +++--- .../src/interfaces.ts | 33 +++--- .../src/internal-utils.ts | 7 +- .../src/package.ts | 4 +- .../src/public-utils.ts | 22 ++-- .../src/types.ts | 109 ++++++++++-------- .../test/public-utils.test.ts | 4 +- .../test/types.test.ts | 68 ++++++++--- 8 files changed, 166 insertions(+), 119 deletions(-) diff --git a/docs/howtos/DataPlane Generation - DPG/06types.mdx b/docs/howtos/DataPlane Generation - DPG/06types.mdx index f5e9e6bcda..c497206865 100644 --- a/docs/howtos/DataPlane Generation - DPG/06types.mdx +++ b/docs/howtos/DataPlane Generation - DPG/06types.mdx @@ -39,7 +39,6 @@ model Foo { "serializedName": "prop", "flatten": true, "optional": false, - "nullable": false, "type": { "kind": "model", "name": "Properties", @@ -50,7 +49,6 @@ model Foo { "serializedName": "name", "flatten": false, "optional": false, - "nullable": false, "type": { "kind": "string", "encode": "string" @@ -224,7 +222,6 @@ model Animal is Record { "name": "name", "serializedName": "name", "optional": false, - "nullable": false, "type": { "kind": "string", "encode": "string" @@ -235,7 +232,6 @@ model Animal is Record { "name": "kind", "serializedName": "kind", "optional": false, - "nullable": false, "type": { "kind": "string", "encode": "string" @@ -244,8 +240,7 @@ model Animal is Record { ], "additionalProperties": { "kind": "any" - }, - "additionalPropertiesNullable": false + } } ``` @@ -353,7 +348,6 @@ model Animal { "name": "name", "serializedName": "name", "optional": false, - "nullable": false, "type": { "kind": "string", "encode": "string" @@ -364,7 +358,6 @@ model Animal { "name": "kind", "serializedName": "kind", "optional": false, - "nullable": false, "type": { "kind": "string", "encode": "string" @@ -380,7 +373,6 @@ model Animal { "name": "category", "serializedName": "category", "optional": false, - "nullable": false, "type": { "kind": "string", "encode": "string" @@ -391,16 +383,13 @@ model Animal { "name": "value", "serializedName": "value", "optional": false, - "nullable": false, "type": { "kind": "any" } } ], - "additionalProperties": undefined, - "additionalPropertiesNullable": undefined - }, - "additionalPropertiesNullable": false + "additionalProperties": undefined + } } ``` @@ -581,7 +570,6 @@ model Animal { "name": "name", "serializedName": "name", "optional": false, - "nullable": false, "type": { "kind": "string", "encode": "string" @@ -592,7 +580,6 @@ model Animal { "name": "kind", "serializedName": "kind", "optional": false, - "nullable": false, "type": { "kind": "string", "encode": "string" @@ -612,8 +599,7 @@ model Animal { "kind": "int32" } ] - }, - "additionalPropertiesNullable": false + } } ``` @@ -711,7 +697,6 @@ model Animal { "name": "name", "serializedName": "name", "optional": false, - "nullable": false, "type": { "kind": "string", "encode": "string" @@ -722,7 +707,6 @@ model Animal { "name": "kind", "serializedName": "kind", "optional": false, - "nullable": false, "type": { "kind": "string", "encode": "string" @@ -730,9 +714,17 @@ model Animal { } ], "additionalProperties": { - "kind": "string" - }, - "additionalPropertiesNullable": true + "kind": "union", + "values": [ + { + "kind": "string", + "encode": "string" + }, + { + "kind": "null" + } + ] + } } ``` diff --git a/packages/typespec-client-generator-core/src/interfaces.ts b/packages/typespec-client-generator-core/src/interfaces.ts index eb049175a7..a1e4d362b9 100644 --- a/packages/typespec-client-generator-core/src/interfaces.ts +++ b/packages/typespec-client-generator-core/src/interfaces.ts @@ -81,7 +81,7 @@ interface SdkTypeBase { __raw?: Type; kind: string; /** - * @deprecated Use helper function `isNullable` instead + * @deprecated Use helper function `isNullable` instead. */ nullable: boolean; deprecation?: string; @@ -231,7 +231,7 @@ export interface SdkArrayType extends SdkTypeBase { kind: "array"; valueType: SdkType; /** - * @deprecated Pass `valueType`` to `isNullable` instead + * @deprecated Pass `valueType` to `isNullable` instead. */ nullableValues: boolean; } @@ -246,7 +246,7 @@ export interface SdkDictionaryType extends SdkTypeBase { keyType: SdkType; valueType: SdkType; /** - * @deprecated Pass `valueType`` to `isNullable` instead + * @deprecated Pass `valueType` to `isNullable` instead. */ nullableValues: boolean; } @@ -295,7 +295,7 @@ export interface SdkModelType extends SdkTypeBase { properties: SdkModelPropertyType[]; name: string; /** - * @deprecated This property is deprecated. Check the bitwise and value of UsageFlags.MultipartFormData and the `.usage` property on this model + * @deprecated This property is deprecated. Check the bitwise and value of UsageFlags.MultipartFormData and the `.usage` property on this model. */ isFormDataType: boolean; /** @@ -307,7 +307,7 @@ export interface SdkModelType extends SdkTypeBase { usage: UsageFlags; additionalProperties?: SdkType; /** - * @deprecated This property is deprecated. Pass the type of the additionalProperties to `isNullable` instead + * @deprecated This property is deprecated. Pass the type of the additionalProperties to `isNullable` instead. */ additionalPropertiesNullable?: boolean; discriminatorValue?: string; @@ -334,7 +334,6 @@ export interface SdkModelPropertyTypeBase { type: SdkType; /** * @deprecated This property is deprecated. Use `.name` instead. - * https://github.com/Azure/typespec-azure/issues/446 */ nameInClient: string; name: string; @@ -347,7 +346,7 @@ export interface SdkModelPropertyTypeBase { isApiVersionParam: boolean; optional: boolean; /** - * @deprecated se exported helper function `isNullable` instead, and pass the type of the property to the function. + * @deprecated Use exported helper function `isNullable` instead, and pass the type of the property to the function. */ nullable: boolean; } @@ -434,7 +433,7 @@ export interface SdkServiceResponseHeader { description?: string; details?: string; /** - * @deprecated This property is deprecated. Pass the type of response header to `isNullable` instead + * @deprecated This property is deprecated. Pass the type of response header to `isNullable` instead. */ nullable: boolean; } @@ -443,10 +442,10 @@ export interface SdkMethodResponse { kind: "method"; type?: SdkType; /** - * @deprecated This property is deprecated. Pass the type of response to `isNullable` instead + * @deprecated This property is deprecated. Pass the type of response to `isNullable` instead. */ nullable: boolean; - resultPath?: string; // if exists, tells you how to get from the service response to the method response + resultPath?: string; // if exists, tells you how to get from the service response to the method response. } export interface SdkServiceResponse { @@ -454,7 +453,7 @@ export interface SdkServiceResponse { headers: SdkServiceResponseHeader[]; apiVersions: string[]; /** - * @deprecated This property is deprecated. Pass the type of response to `isNullable` instead + * @deprecated This property is deprecated. Pass the type of response to `isNullable` instead. */ nullable: boolean; } @@ -502,16 +501,16 @@ interface SdkMethodBase { interface SdkServiceMethodBase extends SdkMethodBase { /** - * @deprecated This property is deprecated. Access .correspondingMethodParams on the service parameters instead + * @deprecated This property is deprecated. Access .correspondingMethodParams on the service parameters instead. * @param serviceParam */ getParameterMapping(serviceParam: SdkServiceParameter): SdkModelPropertyType[]; operation: TServiceOperation; parameters: SdkMethodParameter[]; /** - * @deprecated This property is deprecated. Access .resultPath on the method response instead + * @deprecated This property is deprecated. Access .resultPath on the method response instead. */ - getResponseMapping(): string | undefined; // how to map service response -> method response (e.g. paging). If undefined, it's a 1:1 mapping + getResponseMapping(): string | undefined; response: SdkMethodResponse; exception?: SdkMethodResponse; } @@ -576,7 +575,7 @@ export interface SdkPackage { models: SdkModelType[]; enums: SdkEnumType[]; /** - * @deprecated This property is deprecated. Look at `.diagnostics` on SdkContext instead + * @deprecated This property is deprecated. Look at `.diagnostics` on SdkContext instead. */ diagnostics: readonly Diagnostic[]; crossLanguagePackageId: string; @@ -594,8 +593,8 @@ export enum UsageFlags { Input = 1 << 1, Output = 1 << 2, ApiVersionEnum = 1 << 3, - // Input will also be set when JsonMergePatch is set + // Input will also be set when JsonMergePatch is set. JsonMergePatch = 1 << 4, - // Input will also be set when MultipartFormData is set + // Input will also be set when MultipartFormData is set. MultipartFormData = 1 << 5, } diff --git a/packages/typespec-client-generator-core/src/internal-utils.ts b/packages/typespec-client-generator-core/src/internal-utils.ts index be8ae8ec71..ac3408072b 100644 --- a/packages/typespec-client-generator-core/src/internal-utils.ts +++ b/packages/typespec-client-generator-core/src/internal-utils.ts @@ -348,6 +348,10 @@ export function getNonNullOptions(type: Union): Type[] { return [...type.variants.values()].map((x) => x.type).filter((t) => !isNullType(t)); } +export function getNullOption(type: Union): Type | undefined { + return [...type.variants.values()].map((x) => x.type).filter((t) => isNullType(t))[0]; +} + function getAllResponseBodiesAndNonBodyExists( responses: Map ): { @@ -359,6 +363,7 @@ function getAllResponseBodiesAndNonBodyExists( for (const response of responses.values()) { if (response.type) { if (response.nullable) { + // eslint-disable-line deprecation/deprecation nonBodyExists = true; } allResponseBodies.push(response.type); @@ -383,7 +388,7 @@ export function getAllResponseBodies( */ export function isNullableInternal(type: Type | SdkServiceOperation): boolean { if (type.kind === "Union") { - if (getNonNullOptions(type).length < type.variants.size) return true; + if (getNullOption(type) !== undefined) return true; return Boolean(ignoreDiagnostics(getUnionAsEnum(type))?.nullable); } if (type.kind === "http") { diff --git a/packages/typespec-client-generator-core/src/package.ts b/packages/typespec-client-generator-core/src/package.ts index 3c8e1be390..3f58f09242 100644 --- a/packages/typespec-client-generator-core/src/package.ts +++ b/packages/typespec-client-generator-core/src/package.ts @@ -214,7 +214,7 @@ function getSdkMethodResponse( __raw: operation, kind: "union", values: allResponseBodies, - nullable: isNullableInternal(sdkOperation), + nullable: isNullableInternal(sdkOperation), // eslint-disable-line deprecation/deprecation name: createGeneratedName(operation, "UnionResponse"), isGeneratedName: true, }; @@ -224,7 +224,7 @@ function getSdkMethodResponse( return { kind: "method", type, - nullable: isNullableInternal(sdkOperation), + nullable: isNullableInternal(sdkOperation), // eslint-disable-line deprecation/deprecation }; } diff --git a/packages/typespec-client-generator-core/src/public-utils.ts b/packages/typespec-client-generator-core/src/public-utils.ts index 11abda2f91..77bae5a4b4 100644 --- a/packages/typespec-client-generator-core/src/public-utils.ts +++ b/packages/typespec-client-generator-core/src/public-utils.ts @@ -35,17 +35,14 @@ import { listOperationGroups, listOperationsInOperationGroup, } from "./decorators.js"; -import { SdkType, SdkUnionType } from "./interfaces.js"; +import { SdkType } from "./interfaces.js"; import { TCGCContext, TspLiteralType, - getAnyType, getClientNamespaceStringHelper, parseEmitterName, } from "./internal-utils.js"; import { createDiagnostic } from "./lib.js"; -import { getUnionAsEnum } from "@azure-tools/typespec-azure-core"; -import { getSdkUnionEnum } from "./types.js"; /** * Return the default api version for a versioned service. Will return undefined if one does not exist @@ -630,13 +627,18 @@ export function isNullable(type: SdkType): boolean { /** * Since we don't remove null types from the values of a union type, this helper function helps return the type without null unioned in. - * + * * Only call after first making sure the type isNullable with our helper function - * @param type + * @param type */ -export function removeNullFromUnionType(type: SdkUnionType): SdkType { - return { - ...type, - values: type.values.filter((value) => value.kind !== "null"), +export function removeNullFromUnionType(type: SdkType): SdkType { + if (type.kind !== "union") return type; + const values = type.values.filter((value) => value.kind !== "null"); + if (values.length === 1) return values[0]; + else { + return { + ...type, + values, + }; } } diff --git a/packages/typespec-client-generator-core/src/types.ts b/packages/typespec-client-generator-core/src/types.ts index 3050075b40..9322f15846 100644 --- a/packages/typespec-client-generator-core/src/types.ts +++ b/packages/typespec-client-generator-core/src/types.ts @@ -28,7 +28,6 @@ import { ignoreDiagnostics, isErrorModel, isNeverType, - isNullType, } from "@typespec/compiler"; import { Authentication, @@ -82,6 +81,7 @@ import { getDocHelper, getLocationOfOperation, getNonNullOptions, + getNullOption, getSdkTypeBaseHelper, intOrFloat, isAzureCoreModel, @@ -99,6 +99,7 @@ import { getHttpOperationWithCache, getLibraryName, getPropertyNames, + removeNullFromUnionType, } from "./public-utils.js"; import { getVersions } from "@typespec/versioning"; @@ -106,8 +107,6 @@ import { UnionEnumVariant } from "../../typespec-azure-core/dist/src/helpers/uni import { getSdkHttpParameter, isSdkHttpParameter } from "./http.js"; import { TCGCContext } from "./internal-utils.js"; - - function getEncodeHelper(context: TCGCContext, type: Type, kind: string): string { if (type.kind === "ModelProperty" || type.kind === "Scalar") { return getEncode(context.program, type)?.encoding || kind; @@ -129,7 +128,8 @@ export function addFormatInfo( propertyType: SdkType ): void { const format = getFormat(context.program, type) ?? ""; - if (isSdkBuiltInKind(format)) propertyType.kind = format; + const innerType = removeNullFromUnionType(propertyType); + if (isSdkBuiltInKind(format)) innerType.kind = format; } /** @@ -147,39 +147,40 @@ export function addEncodeInfo( defaultContentType?: string ): [void, readonly Diagnostic[]] { const diagnostics = createDiagnosticCollector(); + const innerType = removeNullFromUnionType(propertyType); const encodeData = getEncode(context.program, type); - if (propertyType.kind === "duration") { + if (innerType.kind === "duration") { if (!encodeData) return diagnostics.wrap(undefined); - propertyType.encode = encodeData.encoding as DurationKnownEncoding; - propertyType.wireType = diagnostics.pipe( + innerType.encode = encodeData.encoding as DurationKnownEncoding; + innerType.wireType = diagnostics.pipe( getClientTypeWithDiagnostics(context, encodeData.type) ) as SdkBuiltInType; if (type.kind === "ModelProperty" && isNullableInternal(type.type)) { - // eslint-disable-next-line deprecation/deprecation - propertyType.wireType.nullable = true; + // eslint-disable-line deprecation/deprecation + innerType.wireType.nullable = true; // eslint-disable-line deprecation/deprecation } } - if (propertyType.kind === "utcDateTime" || propertyType.kind === "offsetDateTime") { + if (innerType.kind === "utcDateTime" || innerType.kind === "offsetDateTime") { if (encodeData) { - propertyType.encode = encodeData.encoding as DateTimeKnownEncoding; - propertyType.wireType = diagnostics.pipe( + innerType.encode = encodeData.encoding as DateTimeKnownEncoding; + innerType.wireType = diagnostics.pipe( getClientTypeWithDiagnostics(context, encodeData.type) ) as SdkBuiltInType; } else if (type.kind === "ModelProperty" && isHeader(context.program, type)) { - propertyType.encode = "rfc7231"; + innerType.encode = "rfc7231"; } if (type.kind === "ModelProperty" && isNullableInternal(type.type)) { - // eslint-disable-next-line deprecation/deprecation - propertyType.wireType.nullable = true; + // eslint-disable-line deprecation/deprecation + innerType.wireType.nullable = true; // eslint-disable-line deprecation/deprecation } } - if (propertyType.kind === "bytes") { + if (innerType.kind === "bytes") { if (encodeData) { - propertyType.encode = encodeData.encoding as BytesKnownEncoding; + innerType.encode = encodeData.encoding as BytesKnownEncoding; } else if (!defaultContentType || defaultContentType === "application/json") { - propertyType.encode = "base64"; + innerType.encode = "base64"; } else { - propertyType.encode = "bytes"; + innerType.encode = "bytes"; } } return diagnostics.wrap(undefined); @@ -342,36 +343,31 @@ export function getSdkUnionWithDiagnostics( ): [SdkType, readonly Diagnostic[]] { const diagnostics = createDiagnosticCollector(); const nonNullOptions = getNonNullOptions(type); + const nullOption = getNullOption(type); + if (nonNullOptions.length === 0) { diagnostics.add(createDiagnostic({ code: "union-null", target: type })); return diagnostics.wrap(getAnyType()); } - // convert to normal type if the union is type | null - if (nonNullOptions.length === 1) { - const clientType = diagnostics.pipe( - getClientTypeWithDiagnostics(context, nonNullOptions[0], operation) - ); - const nullType: SdkBuiltInType = { - ...clientType, - kind: "null", - nullable: true, - encode: "string", - }; - const name = clientType.kind === "union" ? clientType.name : ""; - const isGeneratedName = clientType.kind === "union" ? clientType.isGeneratedName : true; + // convert type only with one non-null variant + if (nullOption && nonNullOptions.length === 1) { return diagnostics.wrap({ - ...clientType, + ...getSdkTypeBaseHelper(context, type, "union"), + name: getLibraryName(context, type) || getGeneratedName(context, type), + isGeneratedName: !type.name, kind: "union", - values: [clientType, nullType], - name, - isGeneratedName, + values: [ + diagnostics.pipe(getClientTypeWithDiagnostics(context, nonNullOptions[0], operation)), + diagnostics.pipe(getClientTypeWithDiagnostics(context, nullOption, operation)), + ], nullable: true, - }) + }); } + // judge if the union can be converted to enum // if language does not need flatten union as enum - // need to filter the case that union is composed of union or enum + // filter the case that union is composed of union or enum if ( context.flattenUnionAsEnum || ![...type.variants.values()].some((variant) => { @@ -379,15 +375,34 @@ export function getSdkUnionWithDiagnostics( }) ) { const unionAsEnum = diagnostics.pipe(getUnionAsEnum(type)); - if (unionAsEnum && !isNullableInternal(type)) { - return diagnostics.wrap(getSdkUnionEnum(context, unionAsEnum, operation)); + if (unionAsEnum) { + const enumType = getSdkUnionEnum(context, unionAsEnum, operation); + if (nullOption === undefined) { + return diagnostics.wrap(enumType); + } else { + return diagnostics.wrap({ + ...getSdkTypeBaseHelper(context, type, "union"), + name: getLibraryName(context, type) || getGeneratedName(context, type), + isGeneratedName: !type.name, + kind: "union", + values: [ + enumType, + diagnostics.pipe(getClientTypeWithDiagnostics(context, nullOption, operation)), + ], + nullable: true, + }); + } } } + + // other cases return diagnostics.wrap({ ...getSdkTypeBaseHelper(context, type, "union"), name: getLibraryName(context, type) || getGeneratedName(context, type), isGeneratedName: !type.name, - values: [...type.variants.values()].map((x) => diagnostics.pipe(getClientTypeWithDiagnostics(context, x.type, operation))), + values: [...type.variants.values()].map((x) => + diagnostics.pipe(getClientTypeWithDiagnostics(context, x.type, operation)) + ), nullable: isNullableInternal(type), // eslint-disable-line deprecation/deprecation }); } @@ -575,14 +590,14 @@ export function getSdkModelWithDiagnostics( sdkType.additionalProperties = diagnostics.pipe( getClientTypeWithDiagnostics(context, type.sourceModel!.indexer!.value!, operation) ); - sdkType.additionalPropertiesNullable = isNullableInternal(type.sourceModel!.indexer!.value!); + sdkType.additionalPropertiesNullable = isNullableInternal(type.sourceModel!.indexer!.value!); // eslint-disable-line deprecation/deprecation } // model MyModel { ...Record<>} should be model with additional properties if (type.indexer) { sdkType.additionalProperties = diagnostics.pipe( getClientTypeWithDiagnostics(context, type.indexer.value, operation) ); - sdkType.additionalPropertiesNullable = isNullableInternal(type.indexer.value); + sdkType.additionalPropertiesNullable = isNullableInternal(type.indexer.value); // eslint-disable-line deprecation/deprecation } // propreties should be generated first since base model'sdiscriminator handling is depend on derived model's properties diagnostics.pipe(addPropertiesToModelType(context, type, sdkType, operation)); @@ -595,7 +610,7 @@ export function getSdkModelWithDiagnostics( if (baseModel.kind === "dict") { // model MyModel extends Record<> {} should be model with additional properties sdkType.additionalProperties = baseModel.valueType; - sdkType.additionalPropertiesNullable = isNullableInternal(baseModel.valueType.__raw!); + sdkType.additionalPropertiesNullable = isNullableInternal(baseModel.valueType.__raw!); // eslint-disable-line deprecation/deprecation } else { sdkType.baseModel = baseModel; } @@ -724,9 +739,9 @@ function getSdkUnionEnumValues( } export function getSdkUnionEnum(context: TCGCContext, type: UnionEnum, operation?: Operation) { - let sdkType = context.modelsMap?.get(type.union) as SdkEnumType | undefined; + const union = type.union; + let sdkType = context.modelsMap?.get(union) as SdkEnumType | undefined; if (!sdkType) { - const union = type.union; const docWrapper = getDocHelper(context, union); const name = getLibraryName(context, type.union) || getGeneratedName(context, type.union); sdkType = { @@ -1008,7 +1023,7 @@ export function getSdkModelPropertyTypeBase( name, isGeneratedName: false, optional: type.optional, - nullable: isNullableInternal(type.type), + nullable: isNullableInternal(type.type), // eslint-disable-line deprecation/deprecation ...updateWithApiVersionInformation( context, type, diff --git a/packages/typespec-client-generator-core/test/public-utils.test.ts b/packages/typespec-client-generator-core/test/public-utils.test.ts index 0c65ff1afa..7562f9febe 100644 --- a/packages/typespec-client-generator-core/test/public-utils.test.ts +++ b/packages/typespec-client-generator-core/test/public-utils.test.ts @@ -1369,9 +1369,9 @@ describe("typespec-client-generator-core: public-utils", () => { ok( models.find( (x) => - x.name === "AB" && + x.name === "AB1" && x.isGeneratedName && - x.crossLanguageDefinitionId === "TestService.AB" + x.crossLanguageDefinitionId === "TestService.AB1" ) ); }); diff --git a/packages/typespec-client-generator-core/test/types.test.ts b/packages/typespec-client-generator-core/test/types.test.ts index cf9c82208e..c98a2d93a7 100644 --- a/packages/typespec-client-generator-core/test/types.test.ts +++ b/packages/typespec-client-generator-core/test/types.test.ts @@ -1,5 +1,5 @@ import { AzureCoreTestLibrary } from "@azure-tools/typespec-azure-core/testing"; -import { Enum, Model, Union, ignoreDiagnostics } from "@typespec/compiler"; +import { Enum, Model, Union } from "@typespec/compiler"; import { expectDiagnostics } from "@typespec/compiler/testing"; import { deepEqual, deepStrictEqual, ok, strictEqual } from "assert"; import { beforeEach, describe, it } from "vitest"; @@ -370,12 +370,11 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkTypeWithNull.values[0].kind, "duration"); strictEqual(sdkTypeWithNull.values[1].kind, "null"); strictEqual(isNullable(sdkTypeWithNull), true); - const sdkType = removeNullFromUnionType(sdkTypeWithNull); strictEqual(sdkType.kind, "duration"); strictEqual(sdkType.wireType.kind, "float"); strictEqual(sdkType.encode, "seconds"); - strictEqual(isNullable(sdkType.wireType), true); + strictEqual(isNullable(sdkType.wireType), false); // eslint-disable-next-line deprecation/deprecation strictEqual(sdkType.wireType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; @@ -490,14 +489,15 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkTypeWithNull.values.length, 2); strictEqual(sdkTypeWithNull.values[0].kind, "utcDateTime"); strictEqual(sdkTypeWithNull.values[1].kind, "null"); + strictEqual(isNullable(sdkTypeWithNull), true); const sdkType = removeNullFromUnionType(sdkTypeWithNull); strictEqual(sdkType.kind, "utcDateTime"); strictEqual(sdkType.wireType.kind, "int64"); strictEqual(sdkType.encode, "unixTimestamp"); - strictEqual(isNullable(sdkType), true); + strictEqual(isNullable(sdkType), false); // eslint-disable-next-line deprecation/deprecation - strictEqual(sdkType.nullable, true); - strictEqual(isNullable(sdkType.wireType), true); + strictEqual(sdkType.nullable, false); + strictEqual(isNullable(sdkType.wireType), false); // eslint-disable-next-line deprecation/deprecation strictEqual(sdkType.wireType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; @@ -588,7 +588,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkType.values[1].kind, "float32"); strictEqual(sdkType.values[2].kind, "null"); strictEqual(isNullable(sdkType), true); - + const sdkTypeWithoutNull = removeNullFromUnionType(sdkType); strictEqual(sdkTypeWithoutNull.kind, "union"); strictEqual(sdkTypeWithoutNull.values.length, 2); @@ -693,7 +693,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(elementType.values.length, 3); strictEqual(elementType.values[0].kind, "string"); strictEqual(elementType.values[1].kind, "float32"); - strictEqual(elementType.values[2].kind, "null") + strictEqual(elementType.values[2].kind, "null"); // eslint-disable-next-line deprecation/deprecation strictEqual(elementType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; @@ -940,6 +940,8 @@ describe("typespec-client-generator-core: types", () => { const sdkTypeWithNull = getSdkTypeHelper(runner); strictEqual(sdkTypeWithNull.kind, "union"); + strictEqual(sdkTypeWithNull.name, "HomePet"); + strictEqual(sdkTypeWithNull.isGeneratedName, true); strictEqual(sdkTypeWithNull.values.length, 2); strictEqual(sdkTypeWithNull.values[0].kind, "enum"); strictEqual(sdkTypeWithNull.values[0].name, "PetKind"); @@ -953,7 +955,38 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkTypeWithNull.values[0], sdkType); strictEqual(sdkType.isUnionAsEnum, false); strictEqual(sdkType.name, "PetKind"); - + + const pet = runner.context.experimental_sdkPackage.models[0].properties[0]; + strictEqual(pet.nullable, true); + const values = sdkType.values; + strictEqual(values.length, 3); + }); + + it("model with nullable union as enum", async function () { + await runner.compileWithBuiltInService(` + @usage(Usage.input | Usage.output) + @access(Access.public) + model Home { + pet: "dog" | "cat" | "bird" | string | null; + } + `); + + const sdkTypeWithNull = getSdkTypeHelper(runner); + strictEqual(sdkTypeWithNull.kind, "union"); + strictEqual(sdkTypeWithNull.values.length, 2); + strictEqual(sdkTypeWithNull.values[0].kind, "enum"); + strictEqual(sdkTypeWithNull.values[0].name, "HomePet"); + strictEqual(sdkTypeWithNull.values[1].kind, "null"); + // eslint-disable-next-line deprecation/deprecation + strictEqual(sdkTypeWithNull.nullable, true); + strictEqual(isNullable(sdkTypeWithNull), true); + + const sdkType = removeNullFromUnionType(sdkTypeWithNull); + strictEqual(sdkType.kind, "enum"); + strictEqual(sdkTypeWithNull.values[0], sdkType); + strictEqual(sdkType.isUnionAsEnum, true); + strictEqual(sdkType.name, "HomePet"); + const pet = runner.context.experimental_sdkPackage.models[0].properties[0]; strictEqual(pet.nullable, true); const values = sdkType.values; @@ -990,7 +1023,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkType.kind, "model"); strictEqual(sdkType.name, "PropertyModel"); strictEqual(model.properties[0].nullable, true); - strictEqual(sdkTypeWithNull.values[0], sdkType) + strictEqual(sdkTypeWithNull.values[0], sdkType); }); it("mix types", async function () { @@ -1039,7 +1072,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(nullableModel.properties[0].type.nullable, true); strictEqual(nullableModel.properties[0].nullable, true); strictEqual(nullableModel.properties[0].type.values.length, 4); - strictEqual(nullableModel.properties[0].type.values[3].kind, "null") + strictEqual(nullableModel.properties[0].type.values[3].kind, "null"); // now check without null with help of helper function const sdkTypeWithoutNull = removeNullFromUnionType(nullableModel.properties[0].type); @@ -1105,12 +1138,12 @@ describe("typespec-client-generator-core: types", () => { string, } + @usage(Usage.input | Usage.output) + @access(Access.public) model Foo { prop: string; } - @usage(Usage.input | Usage.output) - @access(Access.public) union NullableUnion { Foo, null @@ -1625,8 +1658,9 @@ describe("typespec-client-generator-core: types", () => { strictEqual(enumTypeWithNull.kind, "union"); strictEqual(enumTypeWithNull.name, "Test"); strictEqual(isNullable(enumTypeWithNull), true); - strictEqual(enumTypeWithNull.values.length, 4); - strictEqual(enumTypeWithNull.values[3].kind, "null"); + strictEqual(enumTypeWithNull.values.length, 2); + strictEqual(enumTypeWithNull.values[0].kind, "enum"); + strictEqual(enumTypeWithNull.values[1].kind, "null"); const enumType = removeNullFromUnionType(enumTypeWithNull); strictEqual(enumType.kind, "enum"); @@ -1680,7 +1714,7 @@ describe("typespec-client-generator-core: types", () => { )) as { Test: Union }; const unionType = getClientType(runner.context, Test); - + strictEqual(unionType.kind, "union"); strictEqual(unionType.name, "Test"); // eslint-disable-next-line deprecation/deprecation @@ -1713,7 +1747,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(b.isUnionAsEnum, true); strictEqual(b.values[0].name, "B"); strictEqual(b.values[0].value, "B"); - + // make sure values are the same after removing null strictEqual(values[1], unionTypeWithoutNull.values[1]); From 62d85c160ed73038816b6a6b0b05fd74fad99e84 Mon Sep 17 00:00:00 2001 From: tadelesh Date: Tue, 21 May 2024 18:06:59 +0800 Subject: [PATCH 06/23] changelog --- .../changes/move_nullable_to_type-2024-4-21-18-6-54.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .chronus/changes/move_nullable_to_type-2024-4-21-18-6-54.md diff --git a/.chronus/changes/move_nullable_to_type-2024-4-21-18-6-54.md b/.chronus/changes/move_nullable_to_type-2024-4-21-18-6-54.md new file mode 100644 index 0000000000..3ec1a8f9ea --- /dev/null +++ b/.chronus/changes/move_nullable_to_type-2024-4-21-18-6-54.md @@ -0,0 +1,7 @@ +--- +changeKind: breaking +packages: + - "@azure-tools/typespec-client-generator-core" +--- + +treat null as a type and nullable type as a union \ No newline at end of file From 4f57df00d94296e6e5084ec52148a3e1bfa1e76e Mon Sep 17 00:00:00 2001 From: tadelesh Date: Tue, 21 May 2024 18:18:22 +0800 Subject: [PATCH 07/23] add doc --- .../DataPlane Generation - DPG/06types.mdx | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/docs/howtos/DataPlane Generation - DPG/06types.mdx b/docs/howtos/DataPlane Generation - DPG/06types.mdx index c497206865..7dda19850d 100644 --- a/docs/howtos/DataPlane Generation - DPG/06types.mdx +++ b/docs/howtos/DataPlane Generation - DPG/06types.mdx @@ -798,6 +798,119 @@ public final class Animal implements JsonSerializable { +### Nullable + +TypeSpec use `| null` to represent nullable types. Different languages have different logic for nullable types. + + + + +```typespec +model Foo { + basicNullableProperty: string | null; + modelNullableProperty: Bar | null; +} + +model Bar { + prop: string; +} +``` + + + + +```json +{ + "kind": "model", + "name": "Foo", + "properties": [ + { + "kind": "property", + "name": "basicNullableProperty", + "serializedName": "basicNullableProperty", + "optional": false, + "type": { + "kind": "union", + "name": "", + "values": [ + { + "kind": "string", + "encode": "string" + }, + { + "kind": "null" + } + ] + } + }, + { + "kind": "property", + "name": "modelNullableProperty", + "serializedName": "modelNullableProperty", + "optional": false, + "type": { + "kind": "union", + "name": "", + "values": [ + { + "kind": "model", + "name": "Bar", + "properties": [ + { + "kind": "property", + "name": "prop", + "serializedName": "prop", + "optional": false, + "type": { + "kind": "string", + "encode": "string" + } + } + ] + }, + { + "kind": "null" + } + ] + } + } + ] +} +``` + + + + +Python treat nullable as optional. + +```python +class Bar(_model_base.Model): + prop: Optional[str] = rest_field() + +class Foo(_model_base.Model): + basicNullableProperty: Optional[str] = rest_field() + modelNullableProperty: Optional["_models.Bar"] = rest_field() + +``` + + + + +TODO + + + + +TODO + + + + +TODO + + + + ## Unions ### Union of literals with same type From 9fa534a6ea146ec82c671e607f9eb3171978f696 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Tue, 21 May 2024 11:59:37 -0400 Subject: [PATCH 08/23] lint --- .../src/internal-utils.ts | 2 +- .../src/types.ts | 4 +- .../test/package.test.ts | 79 +------------------ .../test/public-utils.test.ts | 2 +- .../test/types.test.ts | 36 +-------- 5 files changed, 8 insertions(+), 115 deletions(-) diff --git a/packages/typespec-client-generator-core/src/internal-utils.ts b/packages/typespec-client-generator-core/src/internal-utils.ts index ac3408072b..4864b1bf8e 100644 --- a/packages/typespec-client-generator-core/src/internal-utils.ts +++ b/packages/typespec-client-generator-core/src/internal-utils.ts @@ -362,8 +362,8 @@ function getAllResponseBodiesAndNonBodyExists( let nonBodyExists = false; for (const response of responses.values()) { if (response.type) { + // eslint-disable-next-line deprecation/deprecation if (response.nullable) { - // eslint-disable-line deprecation/deprecation nonBodyExists = true; } allResponseBodies.push(response.type); diff --git a/packages/typespec-client-generator-core/src/types.ts b/packages/typespec-client-generator-core/src/types.ts index 9322f15846..6496fb9c26 100644 --- a/packages/typespec-client-generator-core/src/types.ts +++ b/packages/typespec-client-generator-core/src/types.ts @@ -155,8 +155,8 @@ export function addEncodeInfo( innerType.wireType = diagnostics.pipe( getClientTypeWithDiagnostics(context, encodeData.type) ) as SdkBuiltInType; + // eslint-disable-next-line deprecation/deprecation if (type.kind === "ModelProperty" && isNullableInternal(type.type)) { - // eslint-disable-line deprecation/deprecation innerType.wireType.nullable = true; // eslint-disable-line deprecation/deprecation } } @@ -169,8 +169,8 @@ export function addEncodeInfo( } else if (type.kind === "ModelProperty" && isHeader(context.program, type)) { innerType.encode = "rfc7231"; } + // eslint-disable-next-line deprecation/deprecation if (type.kind === "ModelProperty" && isNullableInternal(type.type)) { - // eslint-disable-line deprecation/deprecation innerType.wireType.nullable = true; // eslint-disable-line deprecation/deprecation } } diff --git a/packages/typespec-client-generator-core/test/package.test.ts b/packages/typespec-client-generator-core/test/package.test.ts index c94e29ad40..d607e01939 100644 --- a/packages/typespec-client-generator-core/test/package.test.ts +++ b/packages/typespec-client-generator-core/test/package.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable deprecation/deprecation */ import { AzureCoreTestLibrary } from "@azure-tools/typespec-azure-core/testing"; import { ApiKeyAuth, OAuth2Flow, Oauth2Auth } from "@typespec/http"; import { deepStrictEqual, ok, strictEqual } from "assert"; @@ -126,7 +127,6 @@ describe("typespec-client-generator-core: package", () => { strictEqual(client.initialization.properties.length, 1); const endpointParam = client.initialization.properties[0]; strictEqual(endpointParam.kind, "endpoint"); - //eslint-disable-next-line deprecation/deprecation strictEqual(endpointParam.nameInClient, "endpoint"); strictEqual(endpointParam.name, "endpoint"); strictEqual(endpointParam.onClient, true); @@ -160,7 +160,6 @@ describe("typespec-client-generator-core: package", () => { const credentialParam = client.initialization.properties.filter( (p): p is SdkCredentialParameter => p.kind === "credential" )[0]; - //eslint-disable-next-line deprecation/deprecation strictEqual(credentialParam.nameInClient, "credential"); strictEqual(credentialParam.name, "credential"); strictEqual(credentialParam.onClient, true); @@ -201,7 +200,6 @@ describe("typespec-client-generator-core: package", () => { const credentialParam = client.initialization.properties.filter( (p): p is SdkCredentialParameter => p.kind === "credential" )[0]; - //eslint-disable-next-line deprecation/deprecation strictEqual(credentialParam.nameInClient, "credential"); strictEqual(credentialParam.name, "credential"); strictEqual(credentialParam.onClient, true); @@ -247,7 +245,6 @@ describe("typespec-client-generator-core: package", () => { const credentialParam = client.initialization.properties.filter( (p): p is SdkCredentialParameter => p.kind === "credential" )[0]; - //eslint-disable-next-line deprecation/deprecation strictEqual(credentialParam.nameInClient, "credential"); strictEqual(credentialParam.name, "credential"); strictEqual(credentialParam.onClient, true); @@ -304,7 +301,6 @@ describe("typespec-client-generator-core: package", () => { )[0]; strictEqual(endpointParam.clientDefaultValue, undefined); strictEqual(endpointParam.urlEncode, false); - //eslint-disable-next-line deprecation/deprecation strictEqual(endpointParam.nameInClient, "endpoint"); strictEqual(endpointParam.name, "endpoint"); strictEqual(endpointParam.type.kind, "endpoint"); @@ -324,7 +320,6 @@ describe("typespec-client-generator-core: package", () => { const credentialParam = client.initialization.properties.filter( (p): p is SdkCredentialParameter => p.kind === "credential" )[0]; - //eslint-disable-next-line deprecation/deprecation strictEqual(credentialParam.nameInClient, "credential"); strictEqual(credentialParam.name, "credential"); strictEqual(credentialParam.onClient, true); @@ -374,7 +369,6 @@ describe("typespec-client-generator-core: package", () => { const endpointParam = endpointParams[0]; strictEqual(endpointParam.clientDefaultValue, undefined); strictEqual(endpointParam.urlEncode, false); - //eslint-disable-next-line deprecation/deprecation strictEqual(endpointParam.nameInClient, "endpoint"); strictEqual(endpointParam.name, "endpoint"); strictEqual(endpointParam.onClient, true); @@ -395,7 +389,6 @@ describe("typespec-client-generator-core: package", () => { const apiVersionParam = endpointParamType.templateArguments[1]; strictEqual(apiVersionParam.clientDefaultValue, "v1.0"); strictEqual(apiVersionParam.urlEncode, true); - //eslint-disable-next-line deprecation/deprecation strictEqual(apiVersionParam.nameInClient, "apiVersion"); strictEqual(apiVersionParam.name, "apiVersion"); strictEqual(apiVersionParam.onClient, true); @@ -407,7 +400,6 @@ describe("typespec-client-generator-core: package", () => { (p): p is SdkCredentialParameter => p.kind === "credential" ); ok(credentialParam); - //eslint-disable-next-line deprecation/deprecation strictEqual(credentialParam.nameInClient, "credential"); strictEqual(credentialParam.name, "credential"); strictEqual(credentialParam.onClient, true); @@ -477,7 +469,6 @@ describe("typespec-client-generator-core: package", () => { const apiVersionParam = client.initialization.properties.filter( (p) => p.isApiVersionParam )[0]; - //eslint-disable-next-line deprecation/deprecation strictEqual(apiVersionParam.nameInClient, "apiVersion"); strictEqual(apiVersionParam.name, "apiVersion"); strictEqual(apiVersionParam.onClient, true); @@ -544,7 +535,6 @@ describe("typespec-client-generator-core: package", () => { const apiVersionParam = client.initialization.properties.filter( (p) => p.isApiVersionParam )[0]; - //eslint-disable-next-line deprecation/deprecation strictEqual(apiVersionParam.nameInClient, "apiVersion"); strictEqual(apiVersionParam.name, "apiVersion"); strictEqual(apiVersionParam.onClient, true); @@ -595,7 +585,6 @@ describe("typespec-client-generator-core: package", () => { strictEqual(mainClient.methods.length, 1); strictEqual(mainClient.initialization.properties.length, 1); - //eslint-disable-next-line deprecation/deprecation strictEqual(mainClient.initialization.properties[0].nameInClient, "endpoint"); strictEqual(mainClient.initialization.properties[0].name, "endpoint"); @@ -642,7 +631,6 @@ describe("typespec-client-generator-core: package", () => { strictEqual(mainClient.methods.length, 2); ok(mainClient.initialization); strictEqual(mainClient.initialization.properties.length, 1); - //eslint-disable-next-line deprecation/deprecation strictEqual(mainClient.initialization.properties[0].nameInClient, "endpoint"); strictEqual(mainClient.initialization.properties[0].name, "endpoint"); @@ -726,7 +714,6 @@ describe("typespec-client-generator-core: package", () => { const client = sdkPackage.clients[0]; strictEqual(client.initialization.properties.length, 1); - //eslint-disable-next-line deprecation/deprecation strictEqual(client.initialization.properties[0].nameInClient, "endpoint"); strictEqual(client.initialization.properties[0].name, "endpoint"); @@ -757,11 +744,9 @@ describe("typespec-client-generator-core: package", () => { const client = sdkPackage.clients[0]; strictEqual(client.initialization.properties.length, 2); - //eslint-disable-next-line deprecation/deprecation strictEqual(client.initialization.properties[0].nameInClient, "endpoint"); strictEqual(client.initialization.properties[0].name, "endpoint"); const clientApiVersionParam = client.initialization.properties[1]; - //eslint-disable-next-line deprecation/deprecation strictEqual(clientApiVersionParam.nameInClient, "apiVersion"); strictEqual(clientApiVersionParam.name, "apiVersion"); strictEqual(clientApiVersionParam.onClient, true); @@ -806,7 +791,6 @@ describe("typespec-client-generator-core: package", () => { const client = sdkPackage.clients[0]; strictEqual(client.initialization.properties.length, 1); - //eslint-disable-next-line deprecation/deprecation strictEqual(client.initialization.properties[0].nameInClient, "endpoint"); strictEqual(client.initialization.properties[0].name, "endpoint"); @@ -838,12 +822,10 @@ describe("typespec-client-generator-core: package", () => { const client = sdkPackage.clients[0]; strictEqual(client.initialization.properties.length, 2); - //eslint-disable-next-line deprecation/deprecation strictEqual(client.initialization.properties[0].nameInClient, "endpoint"); strictEqual(client.initialization.properties[0].name, "endpoint"); const clientApiVersionParam = client.initialization.properties[1]; - //eslint-disable-next-line deprecation/deprecation strictEqual(clientApiVersionParam.nameInClient, "apiVersion"); strictEqual(clientApiVersionParam.name, "apiVersion"); strictEqual(clientApiVersionParam.onClient, true); @@ -893,12 +875,10 @@ describe("typespec-client-generator-core: package", () => { const client = sdkPackage.clients[0]; strictEqual(client.initialization.properties.length, 2); - //eslint-disable-next-line deprecation/deprecation strictEqual(client.initialization.properties[0].nameInClient, "endpoint"); strictEqual(client.initialization.properties[0].name, "endpoint"); const clientApiVersionParam = client.initialization.properties[1]; - //eslint-disable-next-line deprecation/deprecation strictEqual(clientApiVersionParam.nameInClient, "apiVersion"); strictEqual(clientApiVersionParam.name, "apiVersion"); strictEqual(clientApiVersionParam.onClient, true); @@ -924,7 +904,6 @@ describe("typespec-client-generator-core: package", () => { const apiVersionParam = withApiVersion.operation.parameters[0]; strictEqual(apiVersionParam.kind, "path"); strictEqual(apiVersionParam.serializedName, "apiVersion"); - //eslint-disable-next-line deprecation/deprecation strictEqual(apiVersionParam.nameInClient, "apiVersion"); strictEqual(apiVersionParam.name, "apiVersion"); strictEqual(apiVersionParam.isApiVersionParam, true); @@ -951,7 +930,6 @@ describe("typespec-client-generator-core: package", () => { const methodParam = method.parameters[0]; strictEqual(methodParam.kind, "method"); - //eslint-disable-next-line deprecation/deprecation strictEqual(methodParam.nameInClient, "path"); strictEqual(methodParam.name, "path"); strictEqual(methodParam.optional, false); @@ -969,7 +947,6 @@ describe("typespec-client-generator-core: package", () => { strictEqual(pathParam.kind, "path"); strictEqual(pathParam.serializedName, "path"); - //eslint-disable-next-line deprecation/deprecation strictEqual(pathParam.nameInClient, "path"); strictEqual(pathParam.name, "path"); strictEqual(pathParam.optional, false); @@ -983,7 +960,6 @@ describe("typespec-client-generator-core: package", () => { const correspondingMethodParams = pathParam.correspondingMethodParams; strictEqual(correspondingMethodParams.length, 1); - //eslint-disable-next-line deprecation/deprecation strictEqual(pathParam.nameInClient, correspondingMethodParams[0].nameInClient); strictEqual(pathParam.name, correspondingMethodParams[0].name); }); @@ -1066,7 +1042,6 @@ describe("typespec-client-generator-core: package", () => { const methodParam = method.parameters[0]; strictEqual(methodParam.kind, "method"); - //eslint-disable-next-line deprecation/deprecation strictEqual(methodParam.nameInClient, "header"); strictEqual(methodParam.name, "header"); strictEqual(methodParam.optional, false); @@ -1084,7 +1059,6 @@ describe("typespec-client-generator-core: package", () => { strictEqual(headerParam.kind, "header"); strictEqual(headerParam.serializedName, "header"); - //eslint-disable-next-line deprecation/deprecation strictEqual(headerParam.nameInClient, "header"); strictEqual(headerParam.name, "header"); strictEqual(headerParam.optional, false); @@ -1096,7 +1070,6 @@ describe("typespec-client-generator-core: package", () => { const correspondingMethodParams = headerParam.correspondingMethodParams; strictEqual(correspondingMethodParams.length, 1); - //eslint-disable-next-line deprecation/deprecation strictEqual(headerParam.nameInClient, correspondingMethodParams[0].nameInClient); strictEqual(headerParam.name, correspondingMethodParams[0].name); }); @@ -1150,7 +1123,6 @@ describe("typespec-client-generator-core: package", () => { const methodParam = method.parameters[0]; strictEqual(methodParam.kind, "method"); - //eslint-disable-next-line deprecation/deprecation strictEqual(methodParam.nameInClient, "query"); strictEqual(methodParam.name, "query"); strictEqual(methodParam.optional, false); @@ -1167,7 +1139,6 @@ describe("typespec-client-generator-core: package", () => { const queryParam = serviceOperation.parameters[0]; strictEqual(queryParam.kind, "query"); strictEqual(queryParam.serializedName, "query"); - //eslint-disable-next-line deprecation/deprecation strictEqual(queryParam.nameInClient, "query"); strictEqual(queryParam.name, "query"); strictEqual(queryParam.optional, false); @@ -1179,7 +1150,6 @@ describe("typespec-client-generator-core: package", () => { const correspondingMethodParams = queryParam.correspondingMethodParams; strictEqual(correspondingMethodParams.length, 1); - //eslint-disable-next-line deprecation/deprecation strictEqual(queryParam.nameInClient, correspondingMethodParams[0].nameInClient); strictEqual(queryParam.name, correspondingMethodParams[0].name); }); @@ -1266,13 +1236,11 @@ describe("typespec-client-generator-core: package", () => { const correspondingMethodParams = bodyParameter.correspondingMethodParams; strictEqual(correspondingMethodParams.length, 1); - //eslint-disable-next-line deprecation/deprecation strictEqual(bodyParameter.nameInClient, correspondingMethodParams[0].nameInClient); strictEqual(bodyParameter.name, correspondingMethodParams[0].name); strictEqual(serviceOperation.parameters.length, 1); const contentTypeParam = serviceOperation.parameters[0]; - //eslint-disable-next-line deprecation/deprecation strictEqual(contentTypeParam.nameInClient, "contentType"); strictEqual(contentTypeParam.name, "contentType"); strictEqual(contentTypeParam.serializedName, "Content-Type"); @@ -1353,13 +1321,11 @@ describe("typespec-client-generator-core: package", () => { const correspondingMethodParams = bodyParameter.correspondingMethodParams; strictEqual(correspondingMethodParams.length, 1); - //eslint-disable-next-line deprecation/deprecation strictEqual(bodyParameter.nameInClient, correspondingMethodParams[0].nameInClient); strictEqual(bodyParameter.name, correspondingMethodParams[0].name); strictEqual(serviceOperation.parameters.length, 1); const contentTypeParam = serviceOperation.parameters[0]; - //eslint-disable-next-line deprecation/deprecation strictEqual(contentTypeParam.nameInClient, "contentType"); strictEqual(contentTypeParam.name, "contentType"); strictEqual(contentTypeParam.serializedName, "Content-Type"); @@ -1396,7 +1362,6 @@ describe("typespec-client-generator-core: package", () => { let methodParam = method.parameters[0]; strictEqual(methodParam.kind, "method"); - //eslint-disable-next-line deprecation/deprecation strictEqual(methodParam.nameInClient, "header"); strictEqual(methodParam.name, "header"); strictEqual(methodParam.optional, false); @@ -1406,7 +1371,6 @@ describe("typespec-client-generator-core: package", () => { methodParam = method.parameters[1]; strictEqual(methodParam.kind, "method"); - //eslint-disable-next-line deprecation/deprecation strictEqual(methodParam.nameInClient, "query"); strictEqual(methodParam.name, "query"); strictEqual(methodParam.optional, false); @@ -1416,7 +1380,6 @@ describe("typespec-client-generator-core: package", () => { methodParam = method.parameters[2]; strictEqual(methodParam.kind, "method"); - //eslint-disable-next-line deprecation/deprecation strictEqual(methodParam.nameInClient, "body"); strictEqual(methodParam.name, "body"); strictEqual(methodParam.optional, false); @@ -1426,7 +1389,6 @@ describe("typespec-client-generator-core: package", () => { methodParam = method.parameters[3]; strictEqual(methodParam.kind, "method"); - //eslint-disable-next-line deprecation/deprecation strictEqual(methodParam.nameInClient, "contentType"); strictEqual(methodParam.name, "contentType"); strictEqual(methodParam.optional, false); @@ -1440,7 +1402,6 @@ describe("typespec-client-generator-core: package", () => { ok(serviceOperation.bodyParam); const correspondingBodyParams = serviceOperation.bodyParam.correspondingMethodParams; strictEqual(correspondingBodyParams.length, 1); - //eslint-disable-next-line deprecation/deprecation strictEqual(correspondingBodyParams[0].nameInClient, "body"); strictEqual(correspondingBodyParams[0].name, "body"); @@ -1451,13 +1412,11 @@ describe("typespec-client-generator-core: package", () => { strictEqual(headerParams.length, 2); let correspondingHeaderParams = headerParams[0].correspondingMethodParams; strictEqual(correspondingHeaderParams.length, 1); - //eslint-disable-next-line deprecation/deprecation strictEqual(correspondingHeaderParams[0].nameInClient, "header"); strictEqual(correspondingHeaderParams[0].name, "header"); correspondingHeaderParams = headerParams[1].correspondingMethodParams; strictEqual(correspondingHeaderParams.length, 1); - //eslint-disable-next-line deprecation/deprecation strictEqual(correspondingHeaderParams[0].nameInClient, "contentType"); strictEqual(correspondingHeaderParams[0].name, "contentType"); @@ -1465,7 +1424,6 @@ describe("typespec-client-generator-core: package", () => { strictEqual(queryParams.length, 1); const correspondingQueryParams = queryParams[0].correspondingMethodParams; strictEqual(correspondingQueryParams.length, 1); - //eslint-disable-next-line deprecation/deprecation strictEqual(correspondingQueryParams[0].nameInClient, "query"); strictEqual(correspondingQueryParams[0].name, "query"); }); @@ -1483,7 +1441,6 @@ describe("typespec-client-generator-core: package", () => { let methodParam = method.parameters[0]; strictEqual(methodParam.kind, "method"); - //eslint-disable-next-line deprecation/deprecation strictEqual(methodParam.nameInClient, "body"); strictEqual(methodParam.name, "body"); strictEqual(methodParam.optional, false); @@ -1493,7 +1450,6 @@ describe("typespec-client-generator-core: package", () => { methodParam = method.parameters[1]; strictEqual(methodParam.kind, "method"); - //eslint-disable-next-line deprecation/deprecation strictEqual(methodParam.nameInClient, "contentType"); strictEqual(methodParam.name, "contentType"); strictEqual(methodParam.optional, false); @@ -1508,14 +1464,12 @@ describe("typespec-client-generator-core: package", () => { ok(serviceOperation.bodyParam); const correspondingBodyParams = serviceOperation.bodyParam.correspondingMethodParams; strictEqual(correspondingBodyParams.length, 1); - //eslint-disable-next-line deprecation/deprecation strictEqual(correspondingBodyParams[0].nameInClient, "body"); strictEqual(correspondingBodyParams[0].name, "body"); strictEqual(serviceOperation.parameters.length, 1); const correspondingHeaderParams = serviceOperation.parameters[0].correspondingMethodParams; strictEqual(correspondingHeaderParams.length, 1); - //eslint-disable-next-line deprecation/deprecation strictEqual(correspondingHeaderParams[0].nameInClient, "contentType"); strictEqual(correspondingHeaderParams[0].name, "contentType"); }); @@ -1531,13 +1485,11 @@ describe("typespec-client-generator-core: package", () => { strictEqual(method.parameters.length, 2); const methodBodyParam = method.parameters[0]; - //eslint-disable-next-line deprecation/deprecation strictEqual(methodBodyParam.nameInClient, "body"); strictEqual(methodBodyParam.name, "body"); strictEqual(methodBodyParam.type, sdkPackage.models[0]); const methodContentTypeParam = method.parameters[1]; - //eslint-disable-next-line deprecation/deprecation strictEqual(methodContentTypeParam.nameInClient, "contentType"); strictEqual(methodContentTypeParam.name, "contentType"); @@ -1552,7 +1504,6 @@ describe("typespec-client-generator-core: package", () => { strictEqual(serviceOperation.parameters.length, 1); const serviceContentTypeParam = serviceOperation.parameters[0]; - //eslint-disable-next-line deprecation/deprecation strictEqual(serviceContentTypeParam.nameInClient, "contentType"); strictEqual(serviceContentTypeParam.name, "contentType"); strictEqual(serviceContentTypeParam.serializedName, "Content-Type"); @@ -1575,14 +1526,12 @@ describe("typespec-client-generator-core: package", () => { strictEqual(method.parameters.length, 1); const methodAcceptParam = method.parameters[0]; - //eslint-disable-next-line deprecation/deprecation strictEqual(methodAcceptParam.nameInClient, "accept"); strictEqual(methodAcceptParam.name, "accept"); const serviceOperation = method.operation; strictEqual(serviceOperation.parameters.length, 1); const serviceContentTypeParam = serviceOperation.parameters[0]; - //eslint-disable-next-line deprecation/deprecation strictEqual(serviceContentTypeParam.nameInClient, "accept"); strictEqual(serviceContentTypeParam.name, "accept"); strictEqual(serviceContentTypeParam.serializedName, "Accept"); @@ -1619,14 +1568,12 @@ describe("typespec-client-generator-core: package", () => { strictEqual(method.parameters.length, 1); const methodAcceptParam = method.parameters[0]; - //eslint-disable-next-line deprecation/deprecation strictEqual(methodAcceptParam.nameInClient, "accept"); strictEqual(methodAcceptParam.name, "accept"); const serviceOperation = method.operation; strictEqual(serviceOperation.parameters.length, 1); const serviceContentTypeParam = serviceOperation.parameters[0]; - //eslint-disable-next-line deprecation/deprecation strictEqual(serviceContentTypeParam.nameInClient, "accept"); strictEqual(serviceContentTypeParam.name, "accept"); strictEqual(serviceContentTypeParam.serializedName, "Accept"); @@ -1999,7 +1946,6 @@ describe("typespec-client-generator-core: package", () => { const bodyParameter = method.operation.bodyParam; ok(bodyParameter); strictEqual(bodyParameter.kind, "body"); - //eslint-disable-next-line deprecation/deprecation strictEqual(bodyParameter.nameInClient, "widget"); strictEqual(bodyParameter.name, "widget"); strictEqual(bodyParameter.onClient, false); @@ -2072,7 +2018,6 @@ describe("typespec-client-generator-core: package", () => { ok(pathParam); strictEqual(pathParam.kind, "path"); strictEqual(pathParam.serializedName, "id"); - //eslint-disable-next-line deprecation/deprecation strictEqual(pathParam.nameInClient, "id"); strictEqual(pathParam.name, "id"); strictEqual(pathParam.optional, false); @@ -2090,7 +2035,6 @@ describe("typespec-client-generator-core: package", () => { const correspondingMethodParams = pathParam.correspondingMethodParams; strictEqual(correspondingMethodParams.length, 1); - //eslint-disable-next-line deprecation/deprecation strictEqual(pathParam.nameInClient, correspondingMethodParams[0].nameInClient); strictEqual(pathParam.name, correspondingMethodParams[0].name); }); @@ -2105,7 +2049,6 @@ describe("typespec-client-generator-core: package", () => { let methodParam = method.parameters[0]; strictEqual(methodParam.kind, "method"); - //eslint-disable-next-line deprecation/deprecation strictEqual(methodParam.nameInClient, "id"); strictEqual(methodParam.name, "id"); strictEqual(methodParam.optional, false); @@ -2115,7 +2058,6 @@ describe("typespec-client-generator-core: package", () => { methodParam = method.parameters[1]; strictEqual(methodParam.kind, "method"); - //eslint-disable-next-line deprecation/deprecation strictEqual(methodParam.nameInClient, "widget"); strictEqual(methodParam.name, "widget"); strictEqual(methodParam.optional, false); @@ -2139,7 +2081,6 @@ describe("typespec-client-generator-core: package", () => { ok(pathParam); strictEqual(pathParam.kind, "path"); strictEqual(pathParam.serializedName, "id"); - //eslint-disable-next-line deprecation/deprecation strictEqual(pathParam.nameInClient, "id"); strictEqual(pathParam.name, "id"); strictEqual(pathParam.optional, false); @@ -2195,7 +2136,6 @@ describe("typespec-client-generator-core: package", () => { const methodParam = method.parameters[0]; strictEqual(methodParam.kind, "method"); - //eslint-disable-next-line deprecation/deprecation strictEqual(methodParam.nameInClient, "id"); strictEqual(methodParam.name, "id"); strictEqual(methodParam.optional, false); @@ -2209,7 +2149,6 @@ describe("typespec-client-generator-core: package", () => { ok(pathParam); strictEqual(pathParam.kind, "path"); strictEqual(pathParam.serializedName, "id"); - //eslint-disable-next-line deprecation/deprecation strictEqual(pathParam.nameInClient, "id"); strictEqual(pathParam.name, "id"); strictEqual(pathParam.optional, false); @@ -2219,7 +2158,6 @@ describe("typespec-client-generator-core: package", () => { const correspondingMethodParams = pathParam.correspondingMethodParams; strictEqual(correspondingMethodParams.length, 1); - //eslint-disable-next-line deprecation/deprecation strictEqual(pathParam.nameInClient, correspondingMethodParams[0].nameInClient); strictEqual(pathParam.name, correspondingMethodParams[0].name); }); @@ -2458,7 +2396,6 @@ describe("typespec-client-generator-core: package", () => { const pathParam = method.operation.parameters.find((x) => x.kind === "path"); ok(pathParam); strictEqual(pathParam.kind, "path"); - //eslint-disable-next-line deprecation/deprecation strictEqual(pathParam.nameInClient, "widgetName"); strictEqual(pathParam.name, "widgetName"); strictEqual(pathParam.serializedName, "widgetName"); @@ -2469,7 +2406,6 @@ describe("typespec-client-generator-core: package", () => { const queryParam = method.operation.parameters.find((x) => x.kind === "query"); ok(queryParam); strictEqual(queryParam.isApiVersionParam, true); - //eslint-disable-next-line deprecation/deprecation strictEqual(queryParam.nameInClient, "apiVersion"); strictEqual(queryParam.name, "apiVersion"); strictEqual(queryParam.serializedName, "api-version"); @@ -2574,7 +2510,6 @@ describe("typespec-client-generator-core: package", () => { const pathParam1 = pathParams[0]; strictEqual(pathParam1.kind, "path"); - //eslint-disable-next-line deprecation/deprecation strictEqual(pathParam1.nameInClient, "widgetName"); strictEqual(pathParam1.name, "widgetName"); strictEqual(pathParam1.serializedName, "widgetName"); @@ -2584,7 +2519,6 @@ describe("typespec-client-generator-core: package", () => { const pathParam2 = pathParams[1]; strictEqual(pathParam2.kind, "path"); - //eslint-disable-next-line deprecation/deprecation strictEqual(pathParam2.nameInClient, "operationId"); strictEqual(pathParam2.name, "operationId"); strictEqual(pathParam2.serializedName, "operationId"); @@ -2595,7 +2529,6 @@ describe("typespec-client-generator-core: package", () => { const apiVersionParam = getStatus.operation.parameters.find((x) => x.kind === "query"); ok(apiVersionParam); strictEqual(apiVersionParam.isApiVersionParam, true); - //eslint-disable-next-line deprecation/deprecation strictEqual(apiVersionParam.nameInClient, "apiVersion"); strictEqual(apiVersionParam.name, "apiVersion"); strictEqual(apiVersionParam.serializedName, "api-version"); @@ -2609,7 +2542,6 @@ describe("typespec-client-generator-core: package", () => { const operationAcceptParam = getStatus.operation.parameters.find((x) => x.kind === "header"); ok(operationAcceptParam); - // eslint-disable-next-line deprecation/deprecation strictEqual(operationAcceptParam.nameInClient, "accept"); strictEqual(operationAcceptParam.name, "accept"); strictEqual(operationAcceptParam.clientDefaultValue, undefined); @@ -2673,7 +2605,6 @@ describe("typespec-client-generator-core: package", () => { ok(queryParam); strictEqual(queryParam.serializedName, "api-version"); ok(serviceOperation.bodyParam); - //eslint-disable-next-line deprecation/deprecation strictEqual(serviceOperation.bodyParam.nameInClient, "resource"); strictEqual(serviceOperation.bodyParam.name, "resource"); strictEqual(serviceOperation.bodyParam.type, widgetModel); @@ -2787,7 +2718,6 @@ describe("typespec-client-generator-core: package", () => { const apiVersion = operation.parameters.find((x) => x.isApiVersionParam); ok(apiVersion); strictEqual(apiVersion.kind, "query"); - //eslint-disable-next-line deprecation/deprecation strictEqual(apiVersion.nameInClient, "apiVersion"); strictEqual(apiVersion.name, "apiVersion"); strictEqual(apiVersion.serializedName, "api-version"); @@ -2925,7 +2855,6 @@ describe("typespec-client-generator-core: package", () => { strictEqual(bodyParameter.optional, false); strictEqual(bodyParameter.type.kind, "model"); strictEqual(bodyParameter.type.properties.length, 1); - //eslint-disable-next-line deprecation/deprecation strictEqual(bodyParameter.type.properties[0].nameInClient, "name"); strictEqual(bodyParameter.type.properties[0].name, "name"); @@ -2933,8 +2862,8 @@ describe("typespec-client-generator-core: package", () => { strictEqual(correspondingMethodParams.length, 1); strictEqual( - bodyParameter.type.properties[0].nameInClient, //eslint-disable-line deprecation/deprecation - correspondingMethodParams[0].nameInClient //eslint-disable-line deprecation/deprecation + bodyParameter.type.properties[0].nameInClient, + correspondingMethodParams[0].nameInClient ); strictEqual(bodyParameter.type.properties[0].name, correspondingMethodParams[0].name); }); @@ -2989,10 +2918,8 @@ describe("typespec-client-generator-core: package", () => { strictEqual(bodyParameter.optional, false); strictEqual(bodyParameter.type.kind, "model"); strictEqual(bodyParameter.type.properties.length, 2); - //eslint-disable-next-line deprecation/deprecation strictEqual(bodyParameter.type.properties[0].nameInClient, "kind"); strictEqual(bodyParameter.type.properties[0].name, "kind"); - //eslint-disable-next-line deprecation/deprecation strictEqual(bodyParameter.type.properties[1].nameInClient, "name"); strictEqual(bodyParameter.type.properties[1].name, "name"); diff --git a/packages/typespec-client-generator-core/test/public-utils.test.ts b/packages/typespec-client-generator-core/test/public-utils.test.ts index 7562f9febe..28b0b08cb3 100644 --- a/packages/typespec-client-generator-core/test/public-utils.test.ts +++ b/packages/typespec-client-generator-core/test/public-utils.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable deprecation/deprecation */ import { AzureCoreTestLibrary } from "@azure-tools/typespec-azure-core/testing"; import { Model, @@ -1426,7 +1427,6 @@ describe("typespec-client-generator-core: public-utils", () => { const models = runner.context.experimental_sdkPackage.models; const diagnostics = runner.context.diagnostics; ok(diagnostics); - // eslint-disable-next-line deprecation/deprecation deepStrictEqual(diagnostics, runner.context.experimental_sdkPackage.diagnostics); strictEqual(models.length, 4); const union = models[0].properties[0].type; diff --git a/packages/typespec-client-generator-core/test/types.test.ts b/packages/typespec-client-generator-core/test/types.test.ts index c98a2d93a7..0f5c9bfcf6 100644 --- a/packages/typespec-client-generator-core/test/types.test.ts +++ b/packages/typespec-client-generator-core/test/types.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable deprecation/deprecation */ import { AzureCoreTestLibrary } from "@azure-tools/typespec-azure-core/testing"; import { Enum, Model, Union } from "@typespec/compiler"; import { expectDiagnostics } from "@typespec/compiler/testing"; @@ -246,7 +247,6 @@ describe("typespec-client-generator-core: types", () => { ): void; ` ); - // eslint-disable-next-line deprecation/deprecation expectDiagnostics(runner.context.experimental_sdkPackage.diagnostics, []); expectDiagnostics(runner.context.diagnostics, []); const m = runner.context.experimental_sdkPackage.models.find((x) => x.name === "TestModel"); @@ -375,7 +375,6 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkType.wireType.kind, "float"); strictEqual(sdkType.encode, "seconds"); strictEqual(isNullable(sdkType.wireType), false); - // eslint-disable-next-line deprecation/deprecation strictEqual(sdkType.wireType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; strictEqual(nameProp.nullable, true); @@ -495,10 +494,8 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkType.wireType.kind, "int64"); strictEqual(sdkType.encode, "unixTimestamp"); strictEqual(isNullable(sdkType), false); - // eslint-disable-next-line deprecation/deprecation strictEqual(sdkType.nullable, false); strictEqual(isNullable(sdkType.wireType), false); - // eslint-disable-next-line deprecation/deprecation strictEqual(sdkType.wireType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; strictEqual(nameProp.nullable, true); @@ -565,7 +562,6 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkType.values[1].kind, "null"); strictEqual(isNullable(sdkType), true); strictEqual(removeNullFromUnionType(sdkType).kind, "float32"); - // eslint-disable-next-line deprecation/deprecation strictEqual(sdkType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; strictEqual(isNullable(nameProp.type), true); @@ -594,7 +590,6 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkTypeWithoutNull.values.length, 2); strictEqual(sdkTypeWithoutNull.values[0].kind, "string"); strictEqual(sdkTypeWithoutNull.values[1].kind, "float32"); - // eslint-disable-next-line deprecation/deprecation strictEqual(sdkType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; strictEqual(isNullable(nameProp.type), true); @@ -619,7 +614,6 @@ describe("typespec-client-generator-core: types", () => { strictEqual(elementType.values[1].kind, "null"); strictEqual(removeNullFromUnionType(elementType).kind, "float32"); strictEqual(isNullable(elementType), true); - // eslint-disable-next-line deprecation/deprecation strictEqual(elementType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; strictEqual(isNullable(nameProp.type), false); @@ -644,7 +638,6 @@ describe("typespec-client-generator-core: types", () => { strictEqual(elementType.values[0].kind, "string"); strictEqual(elementType.values[1].kind, "float32"); strictEqual(elementType.values[2].kind, "null"); - // eslint-disable-next-line deprecation/deprecation strictEqual(elementType.nullable, true); strictEqual(isNullable(elementType), true); @@ -668,7 +661,6 @@ describe("typespec-client-generator-core: types", () => { const elementType = sdkType.valueType; strictEqual(elementType.kind, "union"); strictEqual(removeNullFromUnionType(elementType).kind, "float32"); - // eslint-disable-next-line deprecation/deprecation strictEqual(elementType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; strictEqual(nameProp.nullable, false); @@ -694,7 +686,6 @@ describe("typespec-client-generator-core: types", () => { strictEqual(elementType.values[0].kind, "string"); strictEqual(elementType.values[1].kind, "float32"); strictEqual(elementType.values[2].kind, "null"); - // eslint-disable-next-line deprecation/deprecation strictEqual(elementType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; strictEqual(nameProp.nullable, false); @@ -736,7 +727,6 @@ describe("typespec-client-generator-core: types", () => { ok(additionalProperties); strictEqual(additionalProperties.kind, "union"); strictEqual(isNullable(additionalProperties), true); - // eslint-disable-next-line deprecation/deprecation strictEqual(additionalProperties.nullable, true); strictEqual(extendsType.additionalPropertiesNullable, true); strictEqual(removeNullFromUnionType(additionalProperties).kind, "string"); @@ -748,7 +738,6 @@ describe("typespec-client-generator-core: types", () => { ok(isTypeAdditionalProperties); strictEqual(isTypeAdditionalProperties.kind, "union"); strictEqual(isNullable(isTypeAdditionalProperties), true); - // eslint-disable-next-line deprecation/deprecation strictEqual(isTypeAdditionalProperties.nullable, true); strictEqual(isType.additionalPropertiesNullable, true); strictEqual(removeNullFromUnionType(isTypeAdditionalProperties).kind, "string"); @@ -759,7 +748,6 @@ describe("typespec-client-generator-core: types", () => { const spreadTypeAdditionalProperties = spreadType.additionalProperties; ok(spreadTypeAdditionalProperties); strictEqual(spreadTypeAdditionalProperties.kind, "union"); - // eslint-disable-next-line deprecation/deprecation strictEqual(spreadTypeAdditionalProperties.nullable, true); strictEqual(spreadType.additionalPropertiesNullable, true); strictEqual(isNullable(spreadTypeAdditionalProperties), true); @@ -805,7 +793,6 @@ describe("typespec-client-generator-core: types", () => { strictEqual(extendsTypeAdditionalProperties.values[1].kind, "float32"); strictEqual(extendsTypeAdditionalProperties.values[2].kind, "null"); strictEqual(isNullable(extendsTypeAdditionalProperties), true); - // eslint-disable-next-line deprecation/deprecation strictEqual(extendsTypeAdditionalProperties.nullable, true); strictEqual(extendsType.additionalPropertiesNullable, true); strictEqual(removeNullFromUnionType(extendsTypeAdditionalProperties).kind, "union"); @@ -823,7 +810,6 @@ describe("typespec-client-generator-core: types", () => { strictEqual(isTypeAdditionalProperties.values[1].kind, "float32"); strictEqual(isTypeAdditionalProperties.values[2].kind, "null"); strictEqual(isNullable(isTypeAdditionalProperties), true); - // eslint-disable-next-line deprecation/deprecation strictEqual(isTypeAdditionalProperties.nullable, true); strictEqual(isType.additionalPropertiesNullable, true); strictEqual(removeNullFromUnionType(isTypeAdditionalProperties).kind, "union"); @@ -842,7 +828,6 @@ describe("typespec-client-generator-core: types", () => { strictEqual(spreadTypeAdditionalProperties.values[1].kind, "float32"); strictEqual(spreadTypeAdditionalProperties.values[2].kind, "null"); strictEqual(isNullable(spreadTypeAdditionalProperties), true); - // eslint-disable-next-line deprecation/deprecation strictEqual(spreadTypeAdditionalProperties.nullable, true); strictEqual(spreadType.additionalPropertiesNullable, true); strictEqual(removeNullFromUnionType(spreadTypeAdditionalProperties).kind, "union"); @@ -946,7 +931,6 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkTypeWithNull.values[0].kind, "enum"); strictEqual(sdkTypeWithNull.values[0].name, "PetKind"); strictEqual(sdkTypeWithNull.values[1].kind, "null"); - // eslint-disable-next-line deprecation/deprecation strictEqual(sdkTypeWithNull.nullable, true); strictEqual(isNullable(sdkTypeWithNull), true); @@ -977,7 +961,6 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkTypeWithNull.values[0].kind, "enum"); strictEqual(sdkTypeWithNull.values[0].name, "HomePet"); strictEqual(sdkTypeWithNull.values[1].kind, "null"); - // eslint-disable-next-line deprecation/deprecation strictEqual(sdkTypeWithNull.nullable, true); strictEqual(isNullable(sdkTypeWithNull), true); @@ -1054,7 +1037,6 @@ describe("typespec-client-generator-core: types", () => { const nullableModel = models.find((x) => x.kind === "model" && x.name === "TestNullable"); ok(nullableModel); strictEqual(model.properties[0].type.kind, "union"); - // eslint-disable-next-line deprecation/deprecation strictEqual(model.properties[0].type.nullable, false); strictEqual(model.properties[0].nullable, false); strictEqual(isNullable(model.properties[0].type), false); @@ -1068,7 +1050,6 @@ describe("typespec-client-generator-core: types", () => { } } strictEqual(nullableModel.properties[0].type.kind, "union"); - // eslint-disable-next-line deprecation/deprecation strictEqual(nullableModel.properties[0].type.nullable, true); strictEqual(nullableModel.properties[0].nullable, true); strictEqual(nullableModel.properties[0].type.values.length, 4); @@ -1665,7 +1646,6 @@ describe("typespec-client-generator-core: types", () => { const enumType = removeNullFromUnionType(enumTypeWithNull); strictEqual(enumType.kind, "enum"); strictEqual(enumType.name, "Test"); - // eslint-disable-next-line deprecation/deprecation strictEqual(enumType.nullable, true); strictEqual(enumType.isUnionAsEnum, true); const values = enumType.values; @@ -1717,7 +1697,6 @@ describe("typespec-client-generator-core: types", () => { strictEqual(unionType.kind, "union"); strictEqual(unionType.name, "Test"); - // eslint-disable-next-line deprecation/deprecation strictEqual(unionType.nullable, true); strictEqual(isNullable(unionType), true); @@ -2089,7 +2068,6 @@ describe("typespec-client-generator-core: types", () => { (x) => x.kind === "property" && x.serializedName === "encodedWireName" ); ok(jsonEncodedProp); - // eslint-disable-next-line deprecation/deprecation strictEqual(jsonEncodedProp.nameInClient, "jsonEncodedAndProjectedName"); strictEqual(jsonEncodedProp.name, "jsonEncodedAndProjectedName"); @@ -2098,7 +2076,6 @@ describe("typespec-client-generator-core: types", () => { (x) => x.kind === "property" && x.serializedName === "realWireName" ); ok(jsonProjectedProp); - //eslint-disable-next-line deprecation/deprecation strictEqual(jsonProjectedProp.nameInClient, "jsonProjectedName"); strictEqual(jsonProjectedProp.name, "jsonProjectedName"); @@ -2107,7 +2084,6 @@ describe("typespec-client-generator-core: types", () => { (x) => x.kind === "property" && x.serializedName === "regular" ); ok(regularProp); - // eslint-disable-next-line deprecation/deprecation strictEqual(regularProp.nameInClient, "regular"); strictEqual(regularProp.name, "regular"); }); @@ -2359,7 +2335,6 @@ describe("typespec-client-generator-core: types", () => { strictEqual(recursiveModel.properties.length, 1); const prop = recursiveModel.properties[0]; strictEqual(prop.kind, "property"); - //eslint-disable-next-line deprecation/deprecation strictEqual(prop.nameInClient, "prop"); strictEqual(prop.name, "prop"); strictEqual(prop.type.kind, "model"); @@ -3539,12 +3514,10 @@ describe("typespec-client-generator-core: types", () => { const models = getAllModels(runner.context); strictEqual(models.length, 1); strictEqual(models[0].kind, "model"); - // eslint-disable-next-line deprecation/deprecation strictEqual(models[0].isError, true); const rawModel = models[0].__raw; ok(rawModel); strictEqual(rawModel.kind, "Model"); - // eslint-disable-next-line deprecation/deprecation strictEqual(isErrorOrChildOfError(runner.context, rawModel), true); }); @@ -3580,7 +3553,6 @@ describe("typespec-client-generator-core: types", () => { `); const models = getAllModels(runner.context); strictEqual(models.length, 5); - // eslint-disable-next-line deprecation/deprecation const errorModels = models.filter((x) => x.kind === "model" && x.isError); deepStrictEqual(errorModels.map((x) => x.name).sort(), [ "ApiError", @@ -3588,7 +3560,6 @@ describe("typespec-client-generator-core: types", () => { "FourHundredError", "FourZeroFourError", ]); - // eslint-disable-next-line deprecation/deprecation const validModel = models.filter((x) => x.kind === "model" && !x.isError); deepStrictEqual( validModel.map((x) => x.name), @@ -3661,7 +3632,6 @@ describe("typespec-client-generator-core: types", () => { strictEqual(models.length, 1); const model = models[0]; strictEqual(model.kind, "model"); - // eslint-disable-next-line deprecation/deprecation strictEqual(model.isFormDataType, true); ok((model.usage & UsageFlags.MultipartFormData) > 0); strictEqual(model.name, "MultiPartRequest"); @@ -3713,7 +3683,6 @@ describe("typespec-client-generator-core: types", () => { const modelA = models.find((x) => x.name === "A"); ok(modelA); strictEqual(modelA.kind, "model"); - // eslint-disable-next-line deprecation/deprecation strictEqual(modelA.isFormDataType, true); ok((modelA.usage & UsageFlags.MultipartFormData) > 0); strictEqual(modelA.properties.length, 1); @@ -3724,7 +3693,6 @@ describe("typespec-client-generator-core: types", () => { const modelB = models.find((x) => x.name === "B"); ok(modelB); strictEqual(modelB.kind, "model"); - // eslint-disable-next-line deprecation/deprecation strictEqual(modelB.isFormDataType, false); ok((modelB.usage & UsageFlags.MultipartFormData) === 0); strictEqual(modelB.properties.length, 1); @@ -3812,14 +3780,12 @@ describe("typespec-client-generator-core: types", () => { const pictureWrapper = models.find((x) => x.name === "PictureWrapper"); ok(pictureWrapper); - // eslint-disable-next-line deprecation/deprecation strictEqual(pictureWrapper.isFormDataType, true); ok((pictureWrapper.usage & UsageFlags.MultipartFormData) > 0); const errorResponse = models.find((x) => x.name === "ErrorResponse"); ok(errorResponse); strictEqual(errorResponse.kind, "model"); - // eslint-disable-next-line deprecation/deprecation strictEqual(errorResponse.isFormDataType, false); ok((errorResponse.usage & UsageFlags.MultipartFormData) === 0); }); From 22953fd1f7c4efb348d5fe4697912ee00e56875a Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Tue, 21 May 2024 12:05:57 -0400 Subject: [PATCH 09/23] rename to getUnderlyingNullableType --- ...move_nullable_to_type-2024-4-21-18-6-54.md | 2 +- .../DataPlane Generation - DPG/06types.mdx | 7 +++- .../src/public-utils.ts | 2 +- .../src/types.ts | 6 +-- .../test/types.test.ts | 40 +++++++++---------- 5 files changed, 31 insertions(+), 26 deletions(-) diff --git a/.chronus/changes/move_nullable_to_type-2024-4-21-18-6-54.md b/.chronus/changes/move_nullable_to_type-2024-4-21-18-6-54.md index 3ec1a8f9ea..78f07c46df 100644 --- a/.chronus/changes/move_nullable_to_type-2024-4-21-18-6-54.md +++ b/.chronus/changes/move_nullable_to_type-2024-4-21-18-6-54.md @@ -4,4 +4,4 @@ packages: - "@azure-tools/typespec-client-generator-core" --- -treat null as a type and nullable type as a union \ No newline at end of file +treat null as a type and nullable type as a union. Expose helper functions `isNullable` and `getUnderlyingNullableType` to handle these nullable types. diff --git a/docs/howtos/DataPlane Generation - DPG/06types.mdx b/docs/howtos/DataPlane Generation - DPG/06types.mdx index 7dda19850d..058fe31df2 100644 --- a/docs/howtos/DataPlane Generation - DPG/06types.mdx +++ b/docs/howtos/DataPlane Generation - DPG/06types.mdx @@ -800,7 +800,12 @@ public final class Animal implements JsonSerializable { ### Nullable -TypeSpec use `| null` to represent nullable types. Different languages have different logic for nullable types. +TypeSpec uses `| null` to represent nullable types. Nullability is handled differently in languages, but emitter authors will find information +about nullability by inspecting the type of a property. + +A nullable type will always be a union type where null is unioned with actual types. TCGC has added helper functions `isNullable` and `getUnderlyingNullableType` to inspect these types. + +If a type `isNullable` and you want to determine what the value of the `SdkType` is, then you call `getUnderlyingNullableType` to get the underlying type. diff --git a/packages/typespec-client-generator-core/src/public-utils.ts b/packages/typespec-client-generator-core/src/public-utils.ts index 77bae5a4b4..1c0f47047b 100644 --- a/packages/typespec-client-generator-core/src/public-utils.ts +++ b/packages/typespec-client-generator-core/src/public-utils.ts @@ -631,7 +631,7 @@ export function isNullable(type: SdkType): boolean { * Only call after first making sure the type isNullable with our helper function * @param type */ -export function removeNullFromUnionType(type: SdkType): SdkType { +export function getUnderlyingNullableType(type: SdkType): SdkType { if (type.kind !== "union") return type; const values = type.values.filter((value) => value.kind !== "null"); if (values.length === 1) return values[0]; diff --git a/packages/typespec-client-generator-core/src/types.ts b/packages/typespec-client-generator-core/src/types.ts index 6496fb9c26..c8f4766666 100644 --- a/packages/typespec-client-generator-core/src/types.ts +++ b/packages/typespec-client-generator-core/src/types.ts @@ -99,7 +99,7 @@ import { getHttpOperationWithCache, getLibraryName, getPropertyNames, - removeNullFromUnionType, + getUnderlyingNullableType, } from "./public-utils.js"; import { getVersions } from "@typespec/versioning"; @@ -128,7 +128,7 @@ export function addFormatInfo( propertyType: SdkType ): void { const format = getFormat(context.program, type) ?? ""; - const innerType = removeNullFromUnionType(propertyType); + const innerType = getUnderlyingNullableType(propertyType); if (isSdkBuiltInKind(format)) innerType.kind = format; } @@ -147,7 +147,7 @@ export function addEncodeInfo( defaultContentType?: string ): [void, readonly Diagnostic[]] { const diagnostics = createDiagnosticCollector(); - const innerType = removeNullFromUnionType(propertyType); + const innerType = getUnderlyingNullableType(propertyType); const encodeData = getEncode(context.program, type); if (innerType.kind === "duration") { if (!encodeData) return diagnostics.wrap(undefined); diff --git a/packages/typespec-client-generator-core/test/types.test.ts b/packages/typespec-client-generator-core/test/types.test.ts index 0f5c9bfcf6..9bc7f03c3a 100644 --- a/packages/typespec-client-generator-core/test/types.test.ts +++ b/packages/typespec-client-generator-core/test/types.test.ts @@ -14,7 +14,7 @@ import { SdkUnionType, UsageFlags, } from "../src/interfaces.js"; -import { isErrorOrChildOfError, isNullable, removeNullFromUnionType } from "../src/public-utils.js"; +import { isErrorOrChildOfError, isNullable, getUnderlyingNullableType } from "../src/public-utils.js"; import { getAllModels, getAllModelsWithDiagnostics, @@ -370,7 +370,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkTypeWithNull.values[0].kind, "duration"); strictEqual(sdkTypeWithNull.values[1].kind, "null"); strictEqual(isNullable(sdkTypeWithNull), true); - const sdkType = removeNullFromUnionType(sdkTypeWithNull); + const sdkType = getUnderlyingNullableType(sdkTypeWithNull); strictEqual(sdkType.kind, "duration"); strictEqual(sdkType.wireType.kind, "float"); strictEqual(sdkType.encode, "seconds"); @@ -489,7 +489,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkTypeWithNull.values[0].kind, "utcDateTime"); strictEqual(sdkTypeWithNull.values[1].kind, "null"); strictEqual(isNullable(sdkTypeWithNull), true); - const sdkType = removeNullFromUnionType(sdkTypeWithNull); + const sdkType = getUnderlyingNullableType(sdkTypeWithNull); strictEqual(sdkType.kind, "utcDateTime"); strictEqual(sdkType.wireType.kind, "int64"); strictEqual(sdkType.encode, "unixTimestamp"); @@ -561,7 +561,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkType.values[0].kind, "float32"); strictEqual(sdkType.values[1].kind, "null"); strictEqual(isNullable(sdkType), true); - strictEqual(removeNullFromUnionType(sdkType).kind, "float32"); + strictEqual(getUnderlyingNullableType(sdkType).kind, "float32"); strictEqual(sdkType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; strictEqual(isNullable(nameProp.type), true); @@ -585,7 +585,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkType.values[2].kind, "null"); strictEqual(isNullable(sdkType), true); - const sdkTypeWithoutNull = removeNullFromUnionType(sdkType); + const sdkTypeWithoutNull = getUnderlyingNullableType(sdkType); strictEqual(sdkTypeWithoutNull.kind, "union"); strictEqual(sdkTypeWithoutNull.values.length, 2); strictEqual(sdkTypeWithoutNull.values[0].kind, "string"); @@ -612,7 +612,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(elementType.values.length, 2); strictEqual(elementType.values[0].kind, "float32"); strictEqual(elementType.values[1].kind, "null"); - strictEqual(removeNullFromUnionType(elementType).kind, "float32"); + strictEqual(getUnderlyingNullableType(elementType).kind, "float32"); strictEqual(isNullable(elementType), true); strictEqual(elementType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; @@ -660,7 +660,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkType.kind, "array"); const elementType = sdkType.valueType; strictEqual(elementType.kind, "union"); - strictEqual(removeNullFromUnionType(elementType).kind, "float32"); + strictEqual(getUnderlyingNullableType(elementType).kind, "float32"); strictEqual(elementType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; strictEqual(nameProp.nullable, false); @@ -692,7 +692,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkType.nullableValues, true); strictEqual(isNullable(nameProp.type), false); strictEqual(isNullable(elementType), true); - strictEqual(removeNullFromUnionType(elementType).kind, "union"); + strictEqual(getUnderlyingNullableType(elementType).kind, "union"); }); it("additional property is nullable", async function () { @@ -729,7 +729,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(isNullable(additionalProperties), true); strictEqual(additionalProperties.nullable, true); strictEqual(extendsType.additionalPropertiesNullable, true); - strictEqual(removeNullFromUnionType(additionalProperties).kind, "string"); + strictEqual(getUnderlyingNullableType(additionalProperties).kind, "string"); const isType = models.find((x) => x.name === "TestIs"); ok(isType); @@ -740,7 +740,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(isNullable(isTypeAdditionalProperties), true); strictEqual(isTypeAdditionalProperties.nullable, true); strictEqual(isType.additionalPropertiesNullable, true); - strictEqual(removeNullFromUnionType(isTypeAdditionalProperties).kind, "string"); + strictEqual(getUnderlyingNullableType(isTypeAdditionalProperties).kind, "string"); const spreadType = models.find((x) => x.name === "TestSpread"); ok(spreadType); @@ -751,7 +751,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(spreadTypeAdditionalProperties.nullable, true); strictEqual(spreadType.additionalPropertiesNullable, true); strictEqual(isNullable(spreadTypeAdditionalProperties), true); - strictEqual(removeNullFromUnionType(spreadTypeAdditionalProperties).kind, "string"); + strictEqual(getUnderlyingNullableType(spreadTypeAdditionalProperties).kind, "string"); }); it("additional property nullable with more types", async function () { @@ -795,7 +795,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(isNullable(extendsTypeAdditionalProperties), true); strictEqual(extendsTypeAdditionalProperties.nullable, true); strictEqual(extendsType.additionalPropertiesNullable, true); - strictEqual(removeNullFromUnionType(extendsTypeAdditionalProperties).kind, "union"); + strictEqual(getUnderlyingNullableType(extendsTypeAdditionalProperties).kind, "union"); const isType = models.find((x) => x.name === "TestIs"); ok(isType); @@ -812,7 +812,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(isNullable(isTypeAdditionalProperties), true); strictEqual(isTypeAdditionalProperties.nullable, true); strictEqual(isType.additionalPropertiesNullable, true); - strictEqual(removeNullFromUnionType(isTypeAdditionalProperties).kind, "union"); + strictEqual(getUnderlyingNullableType(isTypeAdditionalProperties).kind, "union"); const spreadType = models.find((x) => x.name === "TestSpread"); ok(spreadType); @@ -830,7 +830,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(isNullable(spreadTypeAdditionalProperties), true); strictEqual(spreadTypeAdditionalProperties.nullable, true); strictEqual(spreadType.additionalPropertiesNullable, true); - strictEqual(removeNullFromUnionType(spreadTypeAdditionalProperties).kind, "union"); + strictEqual(getUnderlyingNullableType(spreadTypeAdditionalProperties).kind, "union"); }); it("model with simple union property", async function () { @@ -934,7 +934,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkTypeWithNull.nullable, true); strictEqual(isNullable(sdkTypeWithNull), true); - const sdkType = removeNullFromUnionType(sdkTypeWithNull); + const sdkType = getUnderlyingNullableType(sdkTypeWithNull); strictEqual(sdkType.kind, "enum"); strictEqual(sdkTypeWithNull.values[0], sdkType); strictEqual(sdkType.isUnionAsEnum, false); @@ -964,7 +964,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkTypeWithNull.nullable, true); strictEqual(isNullable(sdkTypeWithNull), true); - const sdkType = removeNullFromUnionType(sdkTypeWithNull); + const sdkType = getUnderlyingNullableType(sdkTypeWithNull); strictEqual(sdkType.kind, "enum"); strictEqual(sdkTypeWithNull.values[0], sdkType); strictEqual(sdkType.isUnionAsEnum, true); @@ -1002,7 +1002,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkTypeWithNull.values[0].name, "PropertyModel"); strictEqual(sdkTypeWithNull.values[1].kind, "null"); - const sdkType = removeNullFromUnionType(sdkTypeWithNull); + const sdkType = getUnderlyingNullableType(sdkTypeWithNull); strictEqual(sdkType.kind, "model"); strictEqual(sdkType.name, "PropertyModel"); strictEqual(model.properties[0].nullable, true); @@ -1056,7 +1056,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(nullableModel.properties[0].type.values[3].kind, "null"); // now check without null with help of helper function - const sdkTypeWithoutNull = removeNullFromUnionType(nullableModel.properties[0].type); + const sdkTypeWithoutNull = getUnderlyingNullableType(nullableModel.properties[0].type); strictEqual(sdkTypeWithoutNull.kind, "union"); for (const v of sdkTypeWithoutNull.values) { if (v.kind === "model") { @@ -1643,7 +1643,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(enumTypeWithNull.values[0].kind, "enum"); strictEqual(enumTypeWithNull.values[1].kind, "null"); - const enumType = removeNullFromUnionType(enumTypeWithNull); + const enumType = getUnderlyingNullableType(enumTypeWithNull); strictEqual(enumType.kind, "enum"); strictEqual(enumType.name, "Test"); strictEqual(enumType.nullable, true); @@ -1700,7 +1700,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(unionType.nullable, true); strictEqual(isNullable(unionType), true); - const unionTypeWithoutNull = removeNullFromUnionType(unionType); + const unionTypeWithoutNull = getUnderlyingNullableType(unionType); strictEqual(unionTypeWithoutNull.kind, "union"); strictEqual(unionTypeWithoutNull.values.length, 3); strictEqual(unionTypeWithoutNull.name, "Test"); From ec036eb286a522b85bcb0270e9759402c43632ed Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Tue, 21 May 2024 12:09:43 -0400 Subject: [PATCH 10/23] format --- packages/typespec-client-generator-core/test/types.test.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/typespec-client-generator-core/test/types.test.ts b/packages/typespec-client-generator-core/test/types.test.ts index 9bc7f03c3a..bdacc9753c 100644 --- a/packages/typespec-client-generator-core/test/types.test.ts +++ b/packages/typespec-client-generator-core/test/types.test.ts @@ -14,7 +14,11 @@ import { SdkUnionType, UsageFlags, } from "../src/interfaces.js"; -import { isErrorOrChildOfError, isNullable, getUnderlyingNullableType } from "../src/public-utils.js"; +import { + getUnderlyingNullableType, + isErrorOrChildOfError, + isNullable, +} from "../src/public-utils.js"; import { getAllModels, getAllModelsWithDiagnostics, From e78db93469051c6046f6733263f75c9ab1658315 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Tue, 21 May 2024 14:21:26 -0400 Subject: [PATCH 11/23] add github issue link --- packages/typespec-client-generator-core/src/interfaces.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/typespec-client-generator-core/src/interfaces.ts b/packages/typespec-client-generator-core/src/interfaces.ts index a1e4d362b9..add5bfa0a2 100644 --- a/packages/typespec-client-generator-core/src/interfaces.ts +++ b/packages/typespec-client-generator-core/src/interfaces.ts @@ -82,6 +82,7 @@ interface SdkTypeBase { kind: string; /** * @deprecated Use helper function `isNullable` instead. + * https://github.com/Azure/typespec-azure/issues/891 */ nullable: boolean; deprecation?: string; @@ -232,6 +233,7 @@ export interface SdkArrayType extends SdkTypeBase { valueType: SdkType; /** * @deprecated Pass `valueType` to `isNullable` instead. + * https://github.com/Azure/typespec-azure/issues/891 */ nullableValues: boolean; } @@ -247,6 +249,7 @@ export interface SdkDictionaryType extends SdkTypeBase { valueType: SdkType; /** * @deprecated Pass `valueType` to `isNullable` instead. + * https://github.com/Azure/typespec-azure/issues/891 */ nullableValues: boolean; } @@ -308,6 +311,7 @@ export interface SdkModelType extends SdkTypeBase { additionalProperties?: SdkType; /** * @deprecated This property is deprecated. Pass the type of the additionalProperties to `isNullable` instead. + * https://github.com/Azure/typespec-azure/issues/891 */ additionalPropertiesNullable?: boolean; discriminatorValue?: string; @@ -347,6 +351,7 @@ export interface SdkModelPropertyTypeBase { optional: boolean; /** * @deprecated Use exported helper function `isNullable` instead, and pass the type of the property to the function. + * https://github.com/Azure/typespec-azure/issues/891 */ nullable: boolean; } @@ -434,6 +439,7 @@ export interface SdkServiceResponseHeader { details?: string; /** * @deprecated This property is deprecated. Pass the type of response header to `isNullable` instead. + * https://github.com/Azure/typespec-azure/issues/891 */ nullable: boolean; } @@ -443,6 +449,7 @@ export interface SdkMethodResponse { type?: SdkType; /** * @deprecated This property is deprecated. Pass the type of response to `isNullable` instead. + * https://github.com/Azure/typespec-azure/issues/891 */ nullable: boolean; resultPath?: string; // if exists, tells you how to get from the service response to the method response. @@ -454,6 +461,7 @@ export interface SdkServiceResponse { apiVersions: string[]; /** * @deprecated This property is deprecated. Pass the type of response to `isNullable` instead. + * https://github.com/Azure/typespec-azure/issues/891 */ nullable: boolean; } From 4fb76b5034b07b9d8597ea89f63198ebd037d16c Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Tue, 21 May 2024 14:23:00 -0400 Subject: [PATCH 12/23] add comment to isNullableInternal --- packages/typespec-client-generator-core/src/internal-utils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/typespec-client-generator-core/src/internal-utils.ts b/packages/typespec-client-generator-core/src/internal-utils.ts index 4864b1bf8e..b554c0b35b 100644 --- a/packages/typespec-client-generator-core/src/internal-utils.ts +++ b/packages/typespec-client-generator-core/src/internal-utils.ts @@ -383,6 +383,7 @@ export function getAllResponseBodies( /** * Determines if a type is nullable. * @deprecated We want to move away from .nullable on SdkPropertyTypes and have people pass the type to `isNullable` instead. + * https://github.com/Azure/typespec-azure/issues/891 * @param type * @returns */ From caaff02e527fd945d9478053b0aa3fc68944b745 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Tue, 21 May 2024 15:27:27 -0400 Subject: [PATCH 13/23] create new SdkNullableType --- .../src/interfaces.ts | 7 +- .../src/public-utils.ts | 37 --- .../src/types.ts | 50 ++-- .../test/types.test.ts | 270 +++++++----------- 4 files changed, 125 insertions(+), 239 deletions(-) diff --git a/packages/typespec-client-generator-core/src/interfaces.ts b/packages/typespec-client-generator-core/src/interfaces.ts index add5bfa0a2..0684f4b2c2 100644 --- a/packages/typespec-client-generator-core/src/interfaces.ts +++ b/packages/typespec-client-generator-core/src/interfaces.ts @@ -97,6 +97,7 @@ export type SdkType = | SdkArrayType | SdkTupleType | SdkDictionaryType + | SdkNullableType | SdkEnumType | SdkEnumValueType | SdkConstantType @@ -156,7 +157,6 @@ enum SdkBuiltInKindsMiscellaneousEnum { plainDate = "plainDate", plainTime = "plainTime", any = "any", - null = "null", } export type SdkBuiltInKinds = @@ -254,6 +254,11 @@ export interface SdkDictionaryType extends SdkTypeBase { nullableValues: boolean; } +export interface SdkNullableType extends SdkTypeBase { + kind: "nullable"; + valueType: SdkType; +} + export interface SdkEnumType extends SdkTypeBase { kind: "enum"; name: string; diff --git a/packages/typespec-client-generator-core/src/public-utils.ts b/packages/typespec-client-generator-core/src/public-utils.ts index 1c0f47047b..e5b5434219 100644 --- a/packages/typespec-client-generator-core/src/public-utils.ts +++ b/packages/typespec-client-generator-core/src/public-utils.ts @@ -605,40 +605,3 @@ export function getHttpOperationWithCache( context.httpOperationCache.set(operation, httpOperation); return httpOperation; } - -/** - * Determines if a type is nullable. - * @param type - * @returns - */ -export function isNullable(type: SdkType): boolean { - if (type.kind !== "union") return false; - let nullCount = 0; - let nonNullCount = 0; - for (const value of type.values) { - if (value.kind === "null") { - nullCount++; - } else { - nonNullCount++; - } - } - return nullCount > 0 && nonNullCount > 0; -} - -/** - * Since we don't remove null types from the values of a union type, this helper function helps return the type without null unioned in. - * - * Only call after first making sure the type isNullable with our helper function - * @param type - */ -export function getUnderlyingNullableType(type: SdkType): SdkType { - if (type.kind !== "union") return type; - const values = type.values.filter((value) => value.kind !== "null"); - if (values.length === 1) return values[0]; - else { - return { - ...type, - values, - }; - } -} diff --git a/packages/typespec-client-generator-core/src/types.ts b/packages/typespec-client-generator-core/src/types.ts index c8f4766666..da2a6ebd19 100644 --- a/packages/typespec-client-generator-core/src/types.ts +++ b/packages/typespec-client-generator-core/src/types.ts @@ -66,6 +66,7 @@ import { SdkModelPropertyType, SdkModelPropertyTypeBase, SdkModelType, + SdkNullableType, SdkOperationGroup, SdkTupleType, SdkType, @@ -99,13 +100,13 @@ import { getHttpOperationWithCache, getLibraryName, getPropertyNames, - getUnderlyingNullableType, } from "./public-utils.js"; import { getVersions } from "@typespec/versioning"; import { UnionEnumVariant } from "../../typespec-azure-core/dist/src/helpers/union-enums.js"; import { getSdkHttpParameter, isSdkHttpParameter } from "./http.js"; import { TCGCContext } from "./internal-utils.js"; +import { get } from "http"; function getEncodeHelper(context: TCGCContext, type: Type, kind: string): string { if (type.kind === "ModelProperty" || type.kind === "Scalar") { @@ -128,8 +129,7 @@ export function addFormatInfo( propertyType: SdkType ): void { const format = getFormat(context.program, type) ?? ""; - const innerType = getUnderlyingNullableType(propertyType); - if (isSdkBuiltInKind(format)) innerType.kind = format; + if (isSdkBuiltInKind(format)) propertyType.kind = format; } /** @@ -147,40 +147,39 @@ export function addEncodeInfo( defaultContentType?: string ): [void, readonly Diagnostic[]] { const diagnostics = createDiagnosticCollector(); - const innerType = getUnderlyingNullableType(propertyType); const encodeData = getEncode(context.program, type); - if (innerType.kind === "duration") { + if (propertyType.kind === "duration") { if (!encodeData) return diagnostics.wrap(undefined); - innerType.encode = encodeData.encoding as DurationKnownEncoding; - innerType.wireType = diagnostics.pipe( + propertyType.encode = encodeData.encoding as DurationKnownEncoding; + propertyType.wireType = diagnostics.pipe( getClientTypeWithDiagnostics(context, encodeData.type) ) as SdkBuiltInType; // eslint-disable-next-line deprecation/deprecation if (type.kind === "ModelProperty" && isNullableInternal(type.type)) { - innerType.wireType.nullable = true; // eslint-disable-line deprecation/deprecation + propertyType.wireType.nullable = true; // eslint-disable-line deprecation/deprecation } } - if (innerType.kind === "utcDateTime" || innerType.kind === "offsetDateTime") { + if (propertyType.kind === "utcDateTime" || propertyType.kind === "offsetDateTime") { if (encodeData) { - innerType.encode = encodeData.encoding as DateTimeKnownEncoding; - innerType.wireType = diagnostics.pipe( + propertyType.encode = encodeData.encoding as DateTimeKnownEncoding; + propertyType.wireType = diagnostics.pipe( getClientTypeWithDiagnostics(context, encodeData.type) ) as SdkBuiltInType; } else if (type.kind === "ModelProperty" && isHeader(context.program, type)) { - innerType.encode = "rfc7231"; + propertyType.encode = "rfc7231"; } // eslint-disable-next-line deprecation/deprecation if (type.kind === "ModelProperty" && isNullableInternal(type.type)) { - innerType.wireType.nullable = true; // eslint-disable-line deprecation/deprecation + propertyType.wireType.nullable = true; // eslint-disable-line deprecation/deprecation } } - if (innerType.kind === "bytes") { + if (propertyType.kind === "bytes") { if (encodeData) { - innerType.encode = encodeData.encoding as BytesKnownEncoding; + propertyType.encode = encodeData.encoding as BytesKnownEncoding; } else if (!defaultContentType || defaultContentType === "application/json") { - innerType.encode = "base64"; + propertyType.encode = "base64"; } else { - innerType.encode = "bytes"; + propertyType.encode = "bytes"; } } return diagnostics.wrap(undefined); @@ -191,7 +190,7 @@ export function addEncodeInfo( * @param scalar the original typespec scalar * @returns the corresponding sdk built in kind */ -function getScalarKind(scalar: Scalar | IntrinsicType): SdkBuiltInKinds { +function getScalarKind(scalar: Scalar): SdkBuiltInKinds { if (isSdkBuiltInKind(scalar.name)) { return scalar.name; } @@ -211,7 +210,7 @@ function getSdkBuiltInTypeWithDiagnostics( const diagnostics = createDiagnosticCollector(); if (context.program.checker.isStdType(type) || type.kind === "Intrinsic") { let kind: SdkBuiltInKinds = "any"; - if (type.kind === "Scalar" || type.kind === "Intrinsic") { + if (type.kind === "Scalar") { if (isSdkBuiltInKind(type.name)) { kind = getScalarKind(type); } @@ -353,16 +352,9 @@ export function getSdkUnionWithDiagnostics( // convert type only with one non-null variant if (nullOption && nonNullOptions.length === 1) { return diagnostics.wrap({ - ...getSdkTypeBaseHelper(context, type, "union"), - name: getLibraryName(context, type) || getGeneratedName(context, type), - isGeneratedName: !type.name, - kind: "union", - values: [ - diagnostics.pipe(getClientTypeWithDiagnostics(context, nonNullOptions[0], operation)), - diagnostics.pipe(getClientTypeWithDiagnostics(context, nullOption, operation)), - ], - nullable: true, - }); + ...getSdkTypeBaseHelper(context, type, "nullable"), + valueType: diagnostics.pipe(getClientTypeWithDiagnostics(context, nonNullOptions[0], operation)), + }) } // judge if the union can be converted to enum diff --git a/packages/typespec-client-generator-core/test/types.test.ts b/packages/typespec-client-generator-core/test/types.test.ts index bdacc9753c..8fdd84fb94 100644 --- a/packages/typespec-client-generator-core/test/types.test.ts +++ b/packages/typespec-client-generator-core/test/types.test.ts @@ -15,9 +15,7 @@ import { UsageFlags, } from "../src/interfaces.js"; import { - getUnderlyingNullableType, isErrorOrChildOfError, - isNullable, } from "../src/public-utils.js"; import { getAllModels, @@ -368,21 +366,17 @@ describe("typespec-client-generator-core: types", () => { } ` ); - const sdkTypeWithNull = getSdkTypeHelper(runner); - strictEqual(sdkTypeWithNull.kind, "union"); - strictEqual(sdkTypeWithNull.values.length, 2); - strictEqual(sdkTypeWithNull.values[0].kind, "duration"); - strictEqual(sdkTypeWithNull.values[1].kind, "null"); - strictEqual(isNullable(sdkTypeWithNull), true); - const sdkType = getUnderlyingNullableType(sdkTypeWithNull); - strictEqual(sdkType.kind, "duration"); - strictEqual(sdkType.wireType.kind, "float"); - strictEqual(sdkType.encode, "seconds"); - strictEqual(isNullable(sdkType.wireType), false); - strictEqual(sdkType.wireType.nullable, true); + const nullableType = getSdkTypeHelper(runner); + strictEqual(nullableType.kind, "nullable"); + strictEqual(nullableType.nullable, true); + const durationType = nullableType.valueType; + + strictEqual(durationType.kind, "duration"); + strictEqual(durationType.wireType.kind, "float"); + strictEqual(durationType.encode, "seconds"); + strictEqual(durationType.wireType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; strictEqual(nameProp.nullable, true); - strictEqual(isNullable(nameProp.type), true); }); it("float seconds decorated scalar", async function () { @@ -487,23 +481,17 @@ describe("typespec-client-generator-core: types", () => { } ` ); - const sdkTypeWithNull = getSdkTypeHelper(runner); - strictEqual(sdkTypeWithNull.kind, "union"); - strictEqual(sdkTypeWithNull.values.length, 2); - strictEqual(sdkTypeWithNull.values[0].kind, "utcDateTime"); - strictEqual(sdkTypeWithNull.values[1].kind, "null"); - strictEqual(isNullable(sdkTypeWithNull), true); - const sdkType = getUnderlyingNullableType(sdkTypeWithNull); + const nullableType = getSdkTypeHelper(runner); + strictEqual(nullableType.kind, "nullable"); + + const sdkType = nullableType.valueType; strictEqual(sdkType.kind, "utcDateTime"); strictEqual(sdkType.wireType.kind, "int64"); strictEqual(sdkType.encode, "unixTimestamp"); - strictEqual(isNullable(sdkType), false); strictEqual(sdkType.nullable, false); - strictEqual(isNullable(sdkType.wireType), false); strictEqual(sdkType.wireType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; strictEqual(nameProp.nullable, true); - strictEqual(isNullable(nameProp.type), true); }); it("unixTimestamp array", async function () { @@ -559,16 +547,13 @@ describe("typespec-client-generator-core: types", () => { } `); - const sdkType = getSdkTypeHelper(runner); - strictEqual(sdkType.kind, "union"); - strictEqual(sdkType.values.length, 2); - strictEqual(sdkType.values[0].kind, "float32"); - strictEqual(sdkType.values[1].kind, "null"); - strictEqual(isNullable(sdkType), true); - strictEqual(getUnderlyingNullableType(sdkType).kind, "float32"); + const nullableType = getSdkTypeHelper(runner); + strictEqual(nullableType.kind, "nullable"); + + const sdkType = nullableType.valueType; + strictEqual(sdkType.kind, "float32"); strictEqual(sdkType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; - strictEqual(isNullable(nameProp.type), true); strictEqual(nameProp.nullable, true); }); @@ -581,22 +566,16 @@ describe("typespec-client-generator-core: types", () => { } `); - const sdkType = getSdkTypeHelper(runner); + const nullableType = getSdkTypeHelper(runner); + strictEqual(nullableType.kind, "nullable"); + + const sdkType = nullableType.valueType; strictEqual(sdkType.kind, "union"); - strictEqual(sdkType.values.length, 3); + strictEqual(sdkType.values.length, 2); strictEqual(sdkType.values[0].kind, "string"); strictEqual(sdkType.values[1].kind, "float32"); - strictEqual(sdkType.values[2].kind, "null"); - strictEqual(isNullable(sdkType), true); - - const sdkTypeWithoutNull = getUnderlyingNullableType(sdkType); - strictEqual(sdkTypeWithoutNull.kind, "union"); - strictEqual(sdkTypeWithoutNull.values.length, 2); - strictEqual(sdkTypeWithoutNull.values[0].kind, "string"); - strictEqual(sdkTypeWithoutNull.values[1].kind, "float32"); strictEqual(sdkType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; - strictEqual(isNullable(nameProp.type), true); strictEqual(nameProp.nullable, true); }); @@ -612,15 +591,10 @@ describe("typespec-client-generator-core: types", () => { const sdkType = getSdkTypeHelper(runner); strictEqual(sdkType.kind, "dict"); const elementType = sdkType.valueType; - strictEqual(elementType.kind, "union"); - strictEqual(elementType.values.length, 2); - strictEqual(elementType.values[0].kind, "float32"); - strictEqual(elementType.values[1].kind, "null"); - strictEqual(getUnderlyingNullableType(elementType).kind, "float32"); - strictEqual(isNullable(elementType), true); + strictEqual(elementType.kind, "nullable"); + strictEqual(elementType.valueType.kind, "float32"); strictEqual(elementType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; - strictEqual(isNullable(nameProp.type), false); strictEqual(nameProp.nullable, false); strictEqual(sdkType.nullableValues, true); }); @@ -637,17 +611,15 @@ describe("typespec-client-generator-core: types", () => { const sdkType = getSdkTypeHelper(runner); strictEqual(sdkType.kind, "dict"); const elementType = sdkType.valueType; - strictEqual(elementType.kind, "union"); - strictEqual(elementType.values.length, 3); - strictEqual(elementType.values[0].kind, "string"); - strictEqual(elementType.values[1].kind, "float32"); - strictEqual(elementType.values[2].kind, "null"); - strictEqual(elementType.nullable, true); - strictEqual(isNullable(elementType), true); + strictEqual(elementType.kind, "nullable"); + const elementTypeValueType = elementType.valueType; + strictEqual(elementTypeValueType.kind, "union"); + strictEqual(elementTypeValueType.values.length, 3); + strictEqual(elementTypeValueType.values[0].kind, "string"); + strictEqual(elementTypeValueType.values[1].kind, "float32"); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; strictEqual(nameProp.nullable, false); - strictEqual(isNullable(nameProp.type), false); strictEqual(sdkType.nullableValues, true); }); @@ -663,14 +635,12 @@ describe("typespec-client-generator-core: types", () => { const sdkType = getSdkTypeHelper(runner); strictEqual(sdkType.kind, "array"); const elementType = sdkType.valueType; - strictEqual(elementType.kind, "union"); - strictEqual(getUnderlyingNullableType(elementType).kind, "float32"); + strictEqual(elementType.kind, "nullable"); strictEqual(elementType.nullable, true); + strictEqual(elementType.valueType.kind, "float32"); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; strictEqual(nameProp.nullable, false); - strictEqual(isNullable(nameProp.type), false); strictEqual(sdkType.nullableValues, true); - strictEqual(isNullable(elementType), true); }); it("array with nullable with more types", async function () { @@ -685,18 +655,17 @@ describe("typespec-client-generator-core: types", () => { const sdkType = getSdkTypeHelper(runner); strictEqual(sdkType.kind, "array"); const elementType = sdkType.valueType; - strictEqual(elementType.kind, "union"); - strictEqual(elementType.values.length, 3); - strictEqual(elementType.values[0].kind, "string"); - strictEqual(elementType.values[1].kind, "float32"); - strictEqual(elementType.values[2].kind, "null"); + strictEqual(elementType.kind, "nullable"); strictEqual(elementType.nullable, true); + + const elementTypeValueType = elementType.valueType; + strictEqual(elementTypeValueType.kind, "union"); + strictEqual(elementTypeValueType.values.length, 2); + strictEqual(elementTypeValueType.values[0].kind, "string"); + strictEqual(elementTypeValueType.values[1].kind, "float32"); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; strictEqual(nameProp.nullable, false); strictEqual(sdkType.nullableValues, true); - strictEqual(isNullable(nameProp.type), false); - strictEqual(isNullable(elementType), true); - strictEqual(getUnderlyingNullableType(elementType).kind, "union"); }); it("additional property is nullable", async function () { @@ -729,33 +698,30 @@ describe("typespec-client-generator-core: types", () => { strictEqual(extendsType.kind, "model"); const additionalProperties = extendsType.additionalProperties; ok(additionalProperties); - strictEqual(additionalProperties.kind, "union"); - strictEqual(isNullable(additionalProperties), true); + strictEqual(additionalProperties.kind, "nullable"); strictEqual(additionalProperties.nullable, true); strictEqual(extendsType.additionalPropertiesNullable, true); - strictEqual(getUnderlyingNullableType(additionalProperties).kind, "string"); + strictEqual(additionalProperties.valueType.kind, "string"); const isType = models.find((x) => x.name === "TestIs"); ok(isType); strictEqual(isType.kind, "model"); const isTypeAdditionalProperties = isType.additionalProperties; ok(isTypeAdditionalProperties); - strictEqual(isTypeAdditionalProperties.kind, "union"); - strictEqual(isNullable(isTypeAdditionalProperties), true); + strictEqual(isTypeAdditionalProperties.kind, "nullable"); strictEqual(isTypeAdditionalProperties.nullable, true); strictEqual(isType.additionalPropertiesNullable, true); - strictEqual(getUnderlyingNullableType(isTypeAdditionalProperties).kind, "string"); + strictEqual(isTypeAdditionalProperties.valueType.kind, "string"); const spreadType = models.find((x) => x.name === "TestSpread"); ok(spreadType); strictEqual(spreadType.kind, "model"); const spreadTypeAdditionalProperties = spreadType.additionalProperties; ok(spreadTypeAdditionalProperties); - strictEqual(spreadTypeAdditionalProperties.kind, "union"); + strictEqual(spreadTypeAdditionalProperties.kind, "nullable"); strictEqual(spreadTypeAdditionalProperties.nullable, true); strictEqual(spreadType.additionalPropertiesNullable, true); - strictEqual(isNullable(spreadTypeAdditionalProperties), true); - strictEqual(getUnderlyingNullableType(spreadTypeAdditionalProperties).kind, "string"); + strictEqual(spreadTypeAdditionalProperties.valueType.kind, "string"); }); it("additional property nullable with more types", async function () { @@ -789,34 +755,33 @@ describe("typespec-client-generator-core: types", () => { const extendsTypeAdditionalProperties = extendsType.additionalProperties; ok(extendsTypeAdditionalProperties); - strictEqual(extendsTypeAdditionalProperties.kind, "union"); - strictEqual(extendsTypeAdditionalProperties.name, "TestExtendsAdditionalProperty"); - strictEqual(extendsTypeAdditionalProperties.isGeneratedName, true); - strictEqual(extendsTypeAdditionalProperties.values.length, 3); - strictEqual(extendsTypeAdditionalProperties.values[0].kind, "string"); - strictEqual(extendsTypeAdditionalProperties.values[1].kind, "float32"); - strictEqual(extendsTypeAdditionalProperties.values[2].kind, "null"); - strictEqual(isNullable(extendsTypeAdditionalProperties), true); + strictEqual(extendsTypeAdditionalProperties.kind, "nullable"); + const extendsAdPropUnderlyingType = extendsTypeAdditionalProperties.valueType; + strictEqual(extendsAdPropUnderlyingType.kind, "union"); + strictEqual(extendsAdPropUnderlyingType.name, "TestExtendsAdditionalProperty"); + strictEqual(extendsAdPropUnderlyingType.isGeneratedName, true); + strictEqual(extendsAdPropUnderlyingType.values.length, 2); + strictEqual(extendsAdPropUnderlyingType.values[0].kind, "string"); + strictEqual(extendsAdPropUnderlyingType.values[1].kind, "float32"); strictEqual(extendsTypeAdditionalProperties.nullable, true); strictEqual(extendsType.additionalPropertiesNullable, true); - strictEqual(getUnderlyingNullableType(extendsTypeAdditionalProperties).kind, "union"); const isType = models.find((x) => x.name === "TestIs"); ok(isType); strictEqual(isType.kind, "model"); const isTypeAdditionalProperties = isType.additionalProperties; ok(isTypeAdditionalProperties); - strictEqual(isTypeAdditionalProperties.kind, "union"); - strictEqual(isTypeAdditionalProperties.name, "TestIsAdditionalProperty"); - strictEqual(isTypeAdditionalProperties.isGeneratedName, true); - strictEqual(isTypeAdditionalProperties.values.length, 3); - strictEqual(isTypeAdditionalProperties.values[0].kind, "string"); - strictEqual(isTypeAdditionalProperties.values[1].kind, "float32"); - strictEqual(isTypeAdditionalProperties.values[2].kind, "null"); - strictEqual(isNullable(isTypeAdditionalProperties), true); + strictEqual(isTypeAdditionalProperties.kind, "nullable"); + + const isTypeAdditionalPropertiesUnderlyingType = isTypeAdditionalProperties.valueType; + strictEqual(isTypeAdditionalPropertiesUnderlyingType.kind, "union"); + strictEqual(isTypeAdditionalPropertiesUnderlyingType.name, "TestIsAdditionalProperty"); + strictEqual(isTypeAdditionalPropertiesUnderlyingType.isGeneratedName, true); + strictEqual(isTypeAdditionalPropertiesUnderlyingType.values.length, 2); + strictEqual(isTypeAdditionalPropertiesUnderlyingType.values[0].kind, "string"); + strictEqual(isTypeAdditionalPropertiesUnderlyingType.values[1].kind, "float32"); strictEqual(isTypeAdditionalProperties.nullable, true); strictEqual(isType.additionalPropertiesNullable, true); - strictEqual(getUnderlyingNullableType(isTypeAdditionalProperties).kind, "union"); const spreadType = models.find((x) => x.name === "TestSpread"); ok(spreadType); @@ -824,17 +789,17 @@ describe("typespec-client-generator-core: types", () => { const spreadTypeAdditionalProperties = spreadType.additionalProperties; ok(spreadTypeAdditionalProperties); - strictEqual(spreadTypeAdditionalProperties.kind, "union"); - strictEqual(spreadTypeAdditionalProperties.name, "TestSpreadAdditionalProperty"); - strictEqual(spreadTypeAdditionalProperties.isGeneratedName, true); - strictEqual(spreadTypeAdditionalProperties.values.length, 3); - strictEqual(spreadTypeAdditionalProperties.values[0].kind, "string"); - strictEqual(spreadTypeAdditionalProperties.values[1].kind, "float32"); - strictEqual(spreadTypeAdditionalProperties.values[2].kind, "null"); - strictEqual(isNullable(spreadTypeAdditionalProperties), true); + strictEqual(spreadTypeAdditionalProperties.kind, "nullable"); + + const spreadTypeAdditionalPropertiesUnderlyingType = spreadTypeAdditionalProperties.valueType; + strictEqual(spreadTypeAdditionalPropertiesUnderlyingType.kind, "union"); + strictEqual(spreadTypeAdditionalPropertiesUnderlyingType.name, "TestSpreadAdditionalProperty"); + strictEqual(spreadTypeAdditionalPropertiesUnderlyingType.isGeneratedName, true); + strictEqual(spreadTypeAdditionalPropertiesUnderlyingType.values.length, 2); + strictEqual(spreadTypeAdditionalPropertiesUnderlyingType.values[0].kind, "string"); + strictEqual(spreadTypeAdditionalPropertiesUnderlyingType.values[1].kind, "float32"); strictEqual(spreadTypeAdditionalProperties.nullable, true); strictEqual(spreadType.additionalPropertiesNullable, true); - strictEqual(getUnderlyingNullableType(spreadTypeAdditionalProperties).kind, "union"); }); it("model with simple union property", async function () { @@ -927,20 +892,11 @@ describe("typespec-client-generator-core: types", () => { } `); - const sdkTypeWithNull = getSdkTypeHelper(runner); - strictEqual(sdkTypeWithNull.kind, "union"); - strictEqual(sdkTypeWithNull.name, "HomePet"); - strictEqual(sdkTypeWithNull.isGeneratedName, true); - strictEqual(sdkTypeWithNull.values.length, 2); - strictEqual(sdkTypeWithNull.values[0].kind, "enum"); - strictEqual(sdkTypeWithNull.values[0].name, "PetKind"); - strictEqual(sdkTypeWithNull.values[1].kind, "null"); - strictEqual(sdkTypeWithNull.nullable, true); - strictEqual(isNullable(sdkTypeWithNull), true); + const nullableType = getSdkTypeHelper(runner); + strictEqual(nullableType.kind, "nullable"); - const sdkType = getUnderlyingNullableType(sdkTypeWithNull); + const sdkType = nullableType.valueType; strictEqual(sdkType.kind, "enum"); - strictEqual(sdkTypeWithNull.values[0], sdkType); strictEqual(sdkType.isUnionAsEnum, false); strictEqual(sdkType.name, "PetKind"); @@ -959,18 +915,11 @@ describe("typespec-client-generator-core: types", () => { } `); - const sdkTypeWithNull = getSdkTypeHelper(runner); - strictEqual(sdkTypeWithNull.kind, "union"); - strictEqual(sdkTypeWithNull.values.length, 2); - strictEqual(sdkTypeWithNull.values[0].kind, "enum"); - strictEqual(sdkTypeWithNull.values[0].name, "HomePet"); - strictEqual(sdkTypeWithNull.values[1].kind, "null"); - strictEqual(sdkTypeWithNull.nullable, true); - strictEqual(isNullable(sdkTypeWithNull), true); + const nullableType = getSdkTypeHelper(runner); + strictEqual(nullableType.kind, "nullable"); - const sdkType = getUnderlyingNullableType(sdkTypeWithNull); + const sdkType = nullableType.valueType; strictEqual(sdkType.kind, "enum"); - strictEqual(sdkTypeWithNull.values[0], sdkType); strictEqual(sdkType.isUnionAsEnum, true); strictEqual(sdkType.name, "HomePet"); @@ -999,18 +948,13 @@ describe("typespec-client-generator-core: types", () => { strictEqual(models.length, 2); const model = models.find((x) => x.kind === "model" && x.name === "Test"); ok(model); - const sdkTypeWithNull = model.properties[0].type; - strictEqual(sdkTypeWithNull.kind, "union"); - strictEqual(sdkTypeWithNull.values.length, 2); - strictEqual(sdkTypeWithNull.values[0].kind, "model"); - strictEqual(sdkTypeWithNull.values[0].name, "PropertyModel"); - strictEqual(sdkTypeWithNull.values[1].kind, "null"); - - const sdkType = getUnderlyingNullableType(sdkTypeWithNull); + const nullableType = model.properties[0].type; + strictEqual(nullableType.kind, "nullable"); + + const sdkType = nullableType.valueType; strictEqual(sdkType.kind, "model"); strictEqual(sdkType.name, "PropertyModel"); strictEqual(model.properties[0].nullable, true); - strictEqual(sdkTypeWithNull.values[0], sdkType); }); it("mix types", async function () { @@ -1043,7 +987,6 @@ describe("typespec-client-generator-core: types", () => { strictEqual(model.properties[0].type.kind, "union"); strictEqual(model.properties[0].type.nullable, false); strictEqual(model.properties[0].nullable, false); - strictEqual(isNullable(model.properties[0].type), false); const unionType = model.properties[0].type; strictEqual(unionType.kind, "union"); for (const v of unionType.values) { @@ -1053,16 +996,18 @@ describe("typespec-client-generator-core: types", () => { strictEqual(v.kind, "constant"); } } - strictEqual(nullableModel.properties[0].type.kind, "union"); - strictEqual(nullableModel.properties[0].type.nullable, true); - strictEqual(nullableModel.properties[0].nullable, true); - strictEqual(nullableModel.properties[0].type.values.length, 4); - strictEqual(nullableModel.properties[0].type.values[3].kind, "null"); + const nullableProp = nullableModel.properties[0]; + strictEqual(nullableProp.type.kind, "nullable"); + strictEqual(nullableProp.type.nullable, true); + strictEqual(nullableProp.nullable, true); + strictEqual(nullableProp.type.valueType.kind, "union"); + strictEqual(nullableProp.type.valueType.values.length, 4); // now check without null with help of helper function - const sdkTypeWithoutNull = getUnderlyingNullableType(nullableModel.properties[0].type); - strictEqual(sdkTypeWithoutNull.kind, "union"); - for (const v of sdkTypeWithoutNull.values) { + strictEqual(nullableModel.properties[0].type.kind, "nullable"); + const sdkType = nullableProp.type.valueType; + strictEqual(sdkType.kind, "union"); + for (const v of sdkType.values) { if (v.kind === "model") { strictEqual(v.name, "ModelType"); } else { @@ -1639,15 +1584,10 @@ describe("typespec-client-generator-core: types", () => { ` )) as { Test: Union }; - const enumTypeWithNull = getClientType(runner.context, Test); - strictEqual(enumTypeWithNull.kind, "union"); - strictEqual(enumTypeWithNull.name, "Test"); - strictEqual(isNullable(enumTypeWithNull), true); - strictEqual(enumTypeWithNull.values.length, 2); - strictEqual(enumTypeWithNull.values[0].kind, "enum"); - strictEqual(enumTypeWithNull.values[1].kind, "null"); + const nullableType = getClientType(runner.context, Test); + strictEqual(nullableType.kind, "nullable"); - const enumType = getUnderlyingNullableType(enumTypeWithNull); + const enumType = nullableType.valueType; strictEqual(enumType.kind, "enum"); strictEqual(enumType.name, "Test"); strictEqual(enumType.nullable, true); @@ -1697,18 +1637,13 @@ describe("typespec-client-generator-core: types", () => { ` )) as { Test: Union }; - const unionType = getClientType(runner.context, Test); + const nullableType = getClientType(runner.context, Test); + strictEqual(nullableType.kind, "nullable"); + const unionType = nullableType.valueType; strictEqual(unionType.kind, "union"); strictEqual(unionType.name, "Test"); strictEqual(unionType.nullable, true); - strictEqual(isNullable(unionType), true); - - const unionTypeWithoutNull = getUnderlyingNullableType(unionType); - strictEqual(unionTypeWithoutNull.kind, "union"); - strictEqual(unionTypeWithoutNull.values.length, 3); - strictEqual(unionTypeWithoutNull.name, "Test"); - strictEqual(isNullable(unionTypeWithoutNull), false); const values = unionType.values; strictEqual(values.length, 4); @@ -1721,9 +1656,6 @@ describe("typespec-client-generator-core: types", () => { strictEqual(a.values[1].name, "A2"); strictEqual(a.values[1].value, "A2"); - // make sure values are the same after removing null - strictEqual(values[0], unionTypeWithoutNull.values[0]); - const b = values[1] as SdkEnumType; strictEqual(b.name, "B"); strictEqual(b.kind, "enum"); @@ -1731,9 +1663,6 @@ describe("typespec-client-generator-core: types", () => { strictEqual(b.values[0].name, "B"); strictEqual(b.values[0].value, "B"); - // make sure values are the same after removing null - strictEqual(values[1], unionTypeWithoutNull.values[1]); - const c = values[2] as SdkEnumType; strictEqual(c.name, "C"); strictEqual(c.kind, "enum"); @@ -1741,9 +1670,6 @@ describe("typespec-client-generator-core: types", () => { strictEqual(c.values[0].name, "C"); strictEqual(c.values[0].value, "C"); - // make sure values are the same after removing null - strictEqual(values[2], unionTypeWithoutNull.values[2]); - const d = values[3] as SdkBuiltInType; strictEqual(d.kind, "null"); }); From 800e0c5c4de575d84b64017195422b2e3aabe4b3 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Tue, 21 May 2024 17:44:59 -0400 Subject: [PATCH 14/23] passing tests --- .../src/public-utils.ts | 1 - .../src/types.ts | 102 +++++++++--------- .../test/public-utils.test.ts | 4 +- .../test/types.test.ts | 20 ++-- 4 files changed, 59 insertions(+), 68 deletions(-) diff --git a/packages/typespec-client-generator-core/src/public-utils.ts b/packages/typespec-client-generator-core/src/public-utils.ts index e5b5434219..d9a45ebfa7 100644 --- a/packages/typespec-client-generator-core/src/public-utils.ts +++ b/packages/typespec-client-generator-core/src/public-utils.ts @@ -35,7 +35,6 @@ import { listOperationGroups, listOperationsInOperationGroup, } from "./decorators.js"; -import { SdkType } from "./interfaces.js"; import { TCGCContext, TspLiteralType, diff --git a/packages/typespec-client-generator-core/src/types.ts b/packages/typespec-client-generator-core/src/types.ts index da2a6ebd19..480c31a444 100644 --- a/packages/typespec-client-generator-core/src/types.ts +++ b/packages/typespec-client-generator-core/src/types.ts @@ -28,6 +28,7 @@ import { ignoreDiagnostics, isErrorModel, isNeverType, + isNullType, } from "@typespec/compiler"; import { Authentication, @@ -66,7 +67,6 @@ import { SdkModelPropertyType, SdkModelPropertyTypeBase, SdkModelType, - SdkNullableType, SdkOperationGroup, SdkTupleType, SdkType, @@ -82,7 +82,6 @@ import { getDocHelper, getLocationOfOperation, getNonNullOptions, - getNullOption, getSdkTypeBaseHelper, intOrFloat, isAzureCoreModel, @@ -106,7 +105,6 @@ import { getVersions } from "@typespec/versioning"; import { UnionEnumVariant } from "../../typespec-azure-core/dist/src/helpers/union-enums.js"; import { getSdkHttpParameter, isSdkHttpParameter } from "./http.js"; import { TCGCContext } from "./internal-utils.js"; -import { get } from "http"; function getEncodeHelper(context: TCGCContext, type: Type, kind: string): string { if (type.kind === "ModelProperty" || type.kind === "Scalar") { @@ -147,39 +145,40 @@ export function addEncodeInfo( defaultContentType?: string ): [void, readonly Diagnostic[]] { const diagnostics = createDiagnosticCollector(); + const innerType = propertyType.kind === "nullable" ? propertyType.valueType : propertyType; const encodeData = getEncode(context.program, type); - if (propertyType.kind === "duration") { + if (innerType.kind === "duration") { if (!encodeData) return diagnostics.wrap(undefined); - propertyType.encode = encodeData.encoding as DurationKnownEncoding; - propertyType.wireType = diagnostics.pipe( + innerType.encode = encodeData.encoding as DurationKnownEncoding; + innerType.wireType = diagnostics.pipe( getClientTypeWithDiagnostics(context, encodeData.type) ) as SdkBuiltInType; // eslint-disable-next-line deprecation/deprecation if (type.kind === "ModelProperty" && isNullableInternal(type.type)) { - propertyType.wireType.nullable = true; // eslint-disable-line deprecation/deprecation + innerType.wireType.nullable = true; // eslint-disable-line deprecation/deprecation } } - if (propertyType.kind === "utcDateTime" || propertyType.kind === "offsetDateTime") { + if (innerType.kind === "utcDateTime" || innerType.kind === "offsetDateTime") { if (encodeData) { - propertyType.encode = encodeData.encoding as DateTimeKnownEncoding; - propertyType.wireType = diagnostics.pipe( + innerType.encode = encodeData.encoding as DateTimeKnownEncoding; + innerType.wireType = diagnostics.pipe( getClientTypeWithDiagnostics(context, encodeData.type) ) as SdkBuiltInType; } else if (type.kind === "ModelProperty" && isHeader(context.program, type)) { - propertyType.encode = "rfc7231"; + innerType.encode = "rfc7231"; } // eslint-disable-next-line deprecation/deprecation if (type.kind === "ModelProperty" && isNullableInternal(type.type)) { - propertyType.wireType.nullable = true; // eslint-disable-line deprecation/deprecation + innerType.wireType.nullable = true; // eslint-disable-line deprecation/deprecation } } - if (propertyType.kind === "bytes") { + if (innerType.kind === "bytes") { if (encodeData) { - propertyType.encode = encodeData.encoding as BytesKnownEncoding; + innerType.encode = encodeData.encoding as BytesKnownEncoding; } else if (!defaultContentType || defaultContentType === "application/json") { - propertyType.encode = "base64"; + innerType.encode = "base64"; } else { - propertyType.encode = "bytes"; + innerType.encode = "bytes"; } } return diagnostics.wrap(undefined); @@ -342,25 +341,22 @@ export function getSdkUnionWithDiagnostics( ): [SdkType, readonly Diagnostic[]] { const diagnostics = createDiagnosticCollector(); const nonNullOptions = getNonNullOptions(type); - const nullOption = getNullOption(type); + let retval: SdkType | undefined = undefined; if (nonNullOptions.length === 0) { diagnostics.add(createDiagnostic({ code: "union-null", target: type })); return diagnostics.wrap(getAnyType()); } - - // convert type only with one non-null variant - if (nullOption && nonNullOptions.length === 1) { - return diagnostics.wrap({ - ...getSdkTypeBaseHelper(context, type, "nullable"), - valueType: diagnostics.pipe(getClientTypeWithDiagnostics(context, nonNullOptions[0], operation)), - }) + const nullable = Boolean( + [...type.variants.values()].map((x) => x.type).filter((t) => isNullType(t))[0] + ); + if (nonNullOptions.length === 1) { + retval = diagnostics.pipe(getClientTypeWithDiagnostics(context, nonNullOptions[0], operation)); } - // judge if the union can be converted to enum // if language does not need flatten union as enum // filter the case that union is composed of union or enum - if ( + else if ( context.flattenUnionAsEnum || ![...type.variants.values()].some((variant) => { return variant.type.kind === "Union" || variant.type.kind === "Enum"; @@ -368,35 +364,30 @@ export function getSdkUnionWithDiagnostics( ) { const unionAsEnum = diagnostics.pipe(getUnionAsEnum(type)); if (unionAsEnum) { - const enumType = getSdkUnionEnum(context, unionAsEnum, operation); - if (nullOption === undefined) { - return diagnostics.wrap(enumType); - } else { - return diagnostics.wrap({ - ...getSdkTypeBaseHelper(context, type, "union"), - name: getLibraryName(context, type) || getGeneratedName(context, type), - isGeneratedName: !type.name, - kind: "union", - values: [ - enumType, - diagnostics.pipe(getClientTypeWithDiagnostics(context, nullOption, operation)), - ], - nullable: true, - }); - } + retval = getSdkUnionEnum(context, unionAsEnum, operation); } } + if (retval === undefined) { + retval = { + ...getSdkTypeBaseHelper(context, type, "union"), + name: getLibraryName(context, type) || getGeneratedName(context, type), + isGeneratedName: !type.name, + values: nonNullOptions.map((x) => + diagnostics.pipe(getClientTypeWithDiagnostics(context, x, operation)) + ), + nullable: isNullableInternal(type), // eslint-disable-line deprecation/deprecation + }; + } + if (nullable) { + retval = { + ...getSdkTypeBaseHelper(context, type, "nullable"), + nullable, + valueType: retval, + }; + } // other cases - return diagnostics.wrap({ - ...getSdkTypeBaseHelper(context, type, "union"), - name: getLibraryName(context, type) || getGeneratedName(context, type), - isGeneratedName: !type.name, - values: [...type.variants.values()].map((x) => - diagnostics.pipe(getClientTypeWithDiagnostics(context, x.type, operation)) - ), - nullable: isNullableInternal(type), // eslint-disable-line deprecation/deprecation - }); + return diagnostics.wrap(retval); } function getSdkConstantWithDiagnostics( @@ -1205,8 +1196,9 @@ function updateUsageOfModel( options = options ?? {}; options.propagation = options?.propagation ?? true; options.ignoreSubTypeStack = options.ignoreSubTypeStack ?? []; - if (!type || !["model", "enum", "array", "dict", "union", "enumvalue"].includes(type.kind)) - return; + // if (!type || !["model", "enum", "array", "dict", "union", "enumvalue"].includes(type.kind)) + // return; + if (!type) return; if (options?.seenModelNames === undefined) { options.seenModelNames = new Set(); } @@ -1224,6 +1216,10 @@ function updateUsageOfModel( updateUsageOfModel(context, usage, type.enumType, options); return; } + if (type.kind === "nullable") { + updateUsageOfModel(context, usage, type.valueType, options); + return; + } if (type.kind !== "model" && type.kind !== "enum") return; options.seenModelNames.add(type); diff --git a/packages/typespec-client-generator-core/test/public-utils.test.ts b/packages/typespec-client-generator-core/test/public-utils.test.ts index 28b0b08cb3..39a5d4a929 100644 --- a/packages/typespec-client-generator-core/test/public-utils.test.ts +++ b/packages/typespec-client-generator-core/test/public-utils.test.ts @@ -1370,9 +1370,9 @@ describe("typespec-client-generator-core: public-utils", () => { ok( models.find( (x) => - x.name === "AB1" && + x.name === "AB" && x.isGeneratedName && - x.crossLanguageDefinitionId === "TestService.AB1" + x.crossLanguageDefinitionId === "TestService.AB" ) ); }); diff --git a/packages/typespec-client-generator-core/test/types.test.ts b/packages/typespec-client-generator-core/test/types.test.ts index 8fdd84fb94..8b7c387198 100644 --- a/packages/typespec-client-generator-core/test/types.test.ts +++ b/packages/typespec-client-generator-core/test/types.test.ts @@ -7,16 +7,13 @@ import { beforeEach, describe, it } from "vitest"; import { SdkArrayType, SdkBodyModelPropertyType, - SdkBuiltInType, SdkEnumType, SdkModelType, SdkType, SdkUnionType, UsageFlags, } from "../src/interfaces.js"; -import { - isErrorOrChildOfError, -} from "../src/public-utils.js"; +import { isErrorOrChildOfError } from "../src/public-utils.js"; import { getAllModels, getAllModelsWithDiagnostics, @@ -552,7 +549,6 @@ describe("typespec-client-generator-core: types", () => { const sdkType = nullableType.valueType; strictEqual(sdkType.kind, "float32"); - strictEqual(sdkType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; strictEqual(nameProp.nullable, true); }); @@ -615,7 +611,7 @@ describe("typespec-client-generator-core: types", () => { const elementTypeValueType = elementType.valueType; strictEqual(elementTypeValueType.kind, "union"); - strictEqual(elementTypeValueType.values.length, 3); + strictEqual(elementTypeValueType.values.length, 2); strictEqual(elementTypeValueType.values[0].kind, "string"); strictEqual(elementTypeValueType.values[1].kind, "float32"); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; @@ -793,7 +789,10 @@ describe("typespec-client-generator-core: types", () => { const spreadTypeAdditionalPropertiesUnderlyingType = spreadTypeAdditionalProperties.valueType; strictEqual(spreadTypeAdditionalPropertiesUnderlyingType.kind, "union"); - strictEqual(spreadTypeAdditionalPropertiesUnderlyingType.name, "TestSpreadAdditionalProperty"); + strictEqual( + spreadTypeAdditionalPropertiesUnderlyingType.name, + "TestSpreadAdditionalProperty" + ); strictEqual(spreadTypeAdditionalPropertiesUnderlyingType.isGeneratedName, true); strictEqual(spreadTypeAdditionalPropertiesUnderlyingType.values.length, 2); strictEqual(spreadTypeAdditionalPropertiesUnderlyingType.values[0].kind, "string"); @@ -1001,7 +1000,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(nullableProp.type.nullable, true); strictEqual(nullableProp.nullable, true); strictEqual(nullableProp.type.valueType.kind, "union"); - strictEqual(nullableProp.type.valueType.values.length, 4); + strictEqual(nullableProp.type.valueType.values.length, 3); // now check without null with help of helper function strictEqual(nullableModel.properties[0].type.kind, "nullable"); @@ -1646,7 +1645,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(unionType.nullable, true); const values = unionType.values; - strictEqual(values.length, 4); + strictEqual(values.length, 3); const a = values[0] as SdkEnumType; strictEqual(a.name, "A"); strictEqual(a.kind, "enum"); @@ -1669,9 +1668,6 @@ describe("typespec-client-generator-core: types", () => { strictEqual(c.isUnionAsEnum, false); strictEqual(c.values[0].name, "C"); strictEqual(c.values[0].value, "C"); - - const d = values[3] as SdkBuiltInType; - strictEqual(d.kind, "null"); }); it("anonymous union as enum with hierarchy", async () => { From e83cb6b126519901a6332e2a1ad91361938cc737 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Tue, 21 May 2024 17:45:37 -0400 Subject: [PATCH 15/23] add changetset --- .chronus/changes/move_nullable_to_type-2024-4-21-18-6-54.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.chronus/changes/move_nullable_to_type-2024-4-21-18-6-54.md b/.chronus/changes/move_nullable_to_type-2024-4-21-18-6-54.md index 78f07c46df..449735721e 100644 --- a/.chronus/changes/move_nullable_to_type-2024-4-21-18-6-54.md +++ b/.chronus/changes/move_nullable_to_type-2024-4-21-18-6-54.md @@ -4,4 +4,4 @@ packages: - "@azure-tools/typespec-client-generator-core" --- -treat null as a type and nullable type as a union. Expose helper functions `isNullable` and `getUnderlyingNullableType` to handle these nullable types. +return nullable types as a new type called `SdkNullableType` From 404ce80736bec5f13d0a8ada8d278c69e2493e41 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Tue, 21 May 2024 17:53:01 -0400 Subject: [PATCH 16/23] udpate docs --- .../DataPlane Generation - DPG/06types.mdx | 49 ++++++------------- 1 file changed, 15 insertions(+), 34 deletions(-) diff --git a/docs/howtos/DataPlane Generation - DPG/06types.mdx b/docs/howtos/DataPlane Generation - DPG/06types.mdx index 058fe31df2..2235a40fc2 100644 --- a/docs/howtos/DataPlane Generation - DPG/06types.mdx +++ b/docs/howtos/DataPlane Generation - DPG/06types.mdx @@ -714,16 +714,11 @@ model Animal { } ], "additionalProperties": { - "kind": "union", - "values": [ - { + "kind": "nullable", + "valueType": { "kind": "string", "encode": "string" - }, - { - "kind": "null" - } - ] + } } } ``` @@ -803,10 +798,6 @@ public final class Animal implements JsonSerializable { TypeSpec uses `| null` to represent nullable types. Nullability is handled differently in languages, but emitter authors will find information about nullability by inspecting the type of a property. -A nullable type will always be a union type where null is unioned with actual types. TCGC has added helper functions `isNullable` and `getUnderlyingNullableType` to inspect these types. - -If a type `isNullable` and you want to determine what the value of the `SdkType` is, then you call `getUnderlyingNullableType` to get the underlying type. - @@ -824,6 +815,8 @@ model Bar { +A nullable type has kind `nullable` and property `valueType`. The kind of the type tells you the property is nullable, while the `valueType` tells you the underlying type you want to generate. + ```json { "kind": "model", @@ -835,17 +828,11 @@ model Bar { "serializedName": "basicNullableProperty", "optional": false, "type": { - "kind": "union", - "name": "", - "values": [ - { - "kind": "string", - "encode": "string" - }, - { - "kind": "null" - } - ] + "kind": "nullable", + "valueType": { + "kind": "string", + "encode": "string" + } } }, { @@ -854,10 +841,8 @@ model Bar { "serializedName": "modelNullableProperty", "optional": false, "type": { - "kind": "union", - "name": "", - "values": [ - { + "kind": "nullable", + "valueType": { "kind": "model", "name": "Bar", "properties": [ @@ -873,12 +858,8 @@ model Bar { } ] }, - { - "kind": "null" - } - ] - } - } + } + } ] } ``` @@ -886,7 +867,7 @@ model Bar { -Python treat nullable as optional. +Python treat nullable as optional. If you actually want to send the value `null` to the service without the property being ignored, you can send in `corehttp.serialization.NULL`. Python does not restrict you from setting any property to this value. ```python class Bar(_model_base.Model): From 831b3def8aa5f3b5f90c8cba4b357ad3f17c59d3 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Tue, 21 May 2024 18:12:59 -0400 Subject: [PATCH 17/23] format --- .../DataPlane Generation - DPG/06types.mdx | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/howtos/DataPlane Generation - DPG/06types.mdx b/docs/howtos/DataPlane Generation - DPG/06types.mdx index 2235a40fc2..166d5d71d0 100644 --- a/docs/howtos/DataPlane Generation - DPG/06types.mdx +++ b/docs/howtos/DataPlane Generation - DPG/06types.mdx @@ -716,8 +716,8 @@ model Animal { "additionalProperties": { "kind": "nullable", "valueType": { - "kind": "string", - "encode": "string" + "kind": "string", + "encode": "string" } } } @@ -843,23 +843,23 @@ A nullable type has kind `nullable` and property `valueType`. The kind of the ty "type": { "kind": "nullable", "valueType": { - "kind": "model", - "name": "Bar", - "properties": [ - { - "kind": "property", - "name": "prop", - "serializedName": "prop", - "optional": false, - "type": { - "kind": "string", - "encode": "string" - } + "kind": "model", + "name": "Bar", + "properties": [ + { + "kind": "property", + "name": "prop", + "serializedName": "prop", + "optional": false, + "type": { + "kind": "string", + "encode": "string" } - ] - }, + } + ] } - } + } + } ] } ``` From 332d0780c0c3870c09495c8e22e3f6b72ce78c6e Mon Sep 17 00:00:00 2001 From: tadelesh Date: Wed, 22 May 2024 11:46:43 +0800 Subject: [PATCH 18/23] refine logic and doc --- .../DataPlane Generation - DPG/06types.mdx | 106 +++++++++++++++++- .../src/types.ts | 42 +++---- .../test/types.test.ts | 22 +++- 3 files changed, 141 insertions(+), 29 deletions(-) diff --git a/docs/howtos/DataPlane Generation - DPG/06types.mdx b/docs/howtos/DataPlane Generation - DPG/06types.mdx index 166d5d71d0..9fdced5ecd 100644 --- a/docs/howtos/DataPlane Generation - DPG/06types.mdx +++ b/docs/howtos/DataPlane Generation - DPG/06types.mdx @@ -805,11 +805,22 @@ about nullability by inspecting the type of a property. model Foo { basicNullableProperty: string | null; modelNullableProperty: Bar | null; + unionNullableProperty: Bar | Baz | null; + enumNullableProperty: LR | null; } model Bar { prop: string; } + +model Baz { + prop: int32; +} + +enum LR { + left, + right, +} ``` @@ -859,6 +870,83 @@ A nullable type has kind `nullable` and property `valueType`. The kind of the ty ] } } + }, + { + "kind": "property", + "name": "unionNullableProperty", + "serializedName": "unionNullableProperty", + "optional": false, + "type": { + "kind": "nullable", + "valueType": { + "kind": "union", + "values": [ + { + "kind": "model", + "name": "Bar", + "properties": [ + { + "kind": "property", + "name": "prop", + "serializedName": "prop", + "optional": false, + "type": { + "kind": "string", + "encode": "string" + } + } + ] + }, + { + "kind": "model", + "name": "Baz", + "properties": [ + { + "kind": "property", + "name": "prop", + "serializedName": "prop", + "optional": false, + "type": { + "kind": "int32", + "encode": "int32" + } + } + ] + } + ] + } + } + }, + { + "kind": "property", + "name": "enumNullableProperty", + "serializedName": "enumNullableProperty", + "optional": false, + "type": { + "kind": "nullable", + "valueType": { + "kind": "enum", + "name": "LR", + "generatedName": false, + "valueType": { + "kind": "string" + }, + "values": [ + { + "kind": "enumvalue", + "name": "left", + "value": "left" + }, + { + "kind": "enumvalue", + "name": "right", + "value": "right" + } + ], + "isFixed": true, + "isUnionAsEnum": false + } + } } ] } @@ -870,12 +958,24 @@ A nullable type has kind `nullable` and property `valueType`. The kind of the ty Python treat nullable as optional. If you actually want to send the value `null` to the service without the property being ignored, you can send in `corehttp.serialization.NULL`. Python does not restrict you from setting any property to this value. ```python +from enum import Enum +from corehttp.utils import CaseInsensitiveEnumMeta + class Bar(_model_base.Model): - prop: Optional[str] = rest_field() + prop: Optional[str] = rest_field() + +class Baz(_model_base.Model): + prop: Optional[str] = rest_field() + +class LR(str, Enum, metaclass=CaseInsensitiveEnumMeta): + LEFT = "left" + RIGHT = "right" class Foo(_model_base.Model): - basicNullableProperty: Optional[str] = rest_field() - modelNullableProperty: Optional["_models.Bar"] = rest_field() + basicNullableProperty: Optional[str] = rest_field() + modelNullableProperty: Optional["_models.Bar"] = rest_field() + unionNullableProperty: Optional[Union["_models.Bar", "_models.Baz"]] = rest_field() + enumNullableProperty: Optional["LR"] = rest_field() ``` diff --git a/packages/typespec-client-generator-core/src/types.ts b/packages/typespec-client-generator-core/src/types.ts index 480c31a444..00ad2fa759 100644 --- a/packages/typespec-client-generator-core/src/types.ts +++ b/packages/typespec-client-generator-core/src/types.ts @@ -28,7 +28,6 @@ import { ignoreDiagnostics, isErrorModel, isNeverType, - isNullType, } from "@typespec/compiler"; import { Authentication, @@ -82,6 +81,7 @@ import { getDocHelper, getLocationOfOperation, getNonNullOptions, + getNullOption, getSdkTypeBaseHelper, intOrFloat, isAzureCoreModel, @@ -126,8 +126,9 @@ export function addFormatInfo( type: ModelProperty | Scalar, propertyType: SdkType ): void { + const innerType = propertyType.kind === "nullable" ? propertyType.valueType : propertyType; const format = getFormat(context.program, type) ?? ""; - if (isSdkBuiltInKind(format)) propertyType.kind = format; + if (isSdkBuiltInKind(format)) innerType.kind = format; } /** @@ -341,22 +342,20 @@ export function getSdkUnionWithDiagnostics( ): [SdkType, readonly Diagnostic[]] { const diagnostics = createDiagnosticCollector(); const nonNullOptions = getNonNullOptions(type); + const nullOption = getNullOption(type); let retval: SdkType | undefined = undefined; if (nonNullOptions.length === 0) { diagnostics.add(createDiagnostic({ code: "union-null", target: type })); return diagnostics.wrap(getAnyType()); } - const nullable = Boolean( - [...type.variants.values()].map((x) => x.type).filter((t) => isNullType(t))[0] - ); + if (nonNullOptions.length === 1) { retval = diagnostics.pipe(getClientTypeWithDiagnostics(context, nonNullOptions[0], operation)); - } - // judge if the union can be converted to enum - // if language does not need flatten union as enum - // filter the case that union is composed of union or enum - else if ( + } else if ( + // judge if the union can be converted to enum + // if language does not need flatten union as enum + // filter the case that union is composed of union or enum context.flattenUnionAsEnum || ![...type.variants.values()].some((variant) => { return variant.type.kind === "Union" || variant.type.kind === "Enum"; @@ -367,6 +366,8 @@ export function getSdkUnionWithDiagnostics( retval = getSdkUnionEnum(context, unionAsEnum, operation); } } + + // other cases if (retval === undefined) { retval = { ...getSdkTypeBaseHelper(context, type, "union"), @@ -375,18 +376,19 @@ export function getSdkUnionWithDiagnostics( values: nonNullOptions.map((x) => diagnostics.pipe(getClientTypeWithDiagnostics(context, x, operation)) ), - nullable: isNullableInternal(type), // eslint-disable-line deprecation/deprecation + nullable: false, }; } - if (nullable) { + + if (nullOption !== undefined) { + retval.nullable = true; // eslint-disable-line deprecation/deprecation retval = { ...getSdkTypeBaseHelper(context, type, "nullable"), - nullable, + nullable: true, valueType: retval, }; } - // other cases return diagnostics.wrap(retval); } @@ -640,13 +642,13 @@ function getSdkEnumValueType( } function getUnionAsEnumValueType(context: TCGCContext, union: Union): SdkBuiltInType | undefined { - for (const variant of union.variants.values()) { - const variantType = variant.type; - if (variantType.kind === "Union") { - const ret = getUnionAsEnumValueType(context, variantType); + const nonNullOptions = getNonNullOptions(union); + for (const option of nonNullOptions) { + if (option.kind === "Union") { + const ret = getUnionAsEnumValueType(context, option); if (ret) return ret; - } else if (variantType.kind === "Scalar") { - return getClientType(context, variantType) as SdkBuiltInType; + } else if (option.kind === "Scalar") { + return getClientType(context, option) as SdkBuiltInType; } } diff --git a/packages/typespec-client-generator-core/test/types.test.ts b/packages/typespec-client-generator-core/test/types.test.ts index 8b7c387198..da49a74a5a 100644 --- a/packages/typespec-client-generator-core/test/types.test.ts +++ b/packages/typespec-client-generator-core/test/types.test.ts @@ -366,12 +366,13 @@ describe("typespec-client-generator-core: types", () => { const nullableType = getSdkTypeHelper(runner); strictEqual(nullableType.kind, "nullable"); strictEqual(nullableType.nullable, true); - const durationType = nullableType.valueType; - strictEqual(durationType.kind, "duration"); - strictEqual(durationType.wireType.kind, "float"); - strictEqual(durationType.encode, "seconds"); - strictEqual(durationType.wireType.nullable, true); + const sdkType = nullableType.valueType; + strictEqual(sdkType.kind, "duration"); + strictEqual(sdkType.wireType.kind, "float"); + strictEqual(sdkType.encode, "seconds"); + strictEqual(sdkType.nullable, true); + strictEqual(sdkType.wireType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; strictEqual(nameProp.nullable, true); }); @@ -485,7 +486,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkType.kind, "utcDateTime"); strictEqual(sdkType.wireType.kind, "int64"); strictEqual(sdkType.encode, "unixTimestamp"); - strictEqual(sdkType.nullable, false); + strictEqual(sdkType.nullable, true); strictEqual(sdkType.wireType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; strictEqual(nameProp.nullable, true); @@ -614,6 +615,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(elementTypeValueType.values.length, 2); strictEqual(elementTypeValueType.values[0].kind, "string"); strictEqual(elementTypeValueType.values[1].kind, "float32"); + strictEqual(elementTypeValueType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; strictEqual(nameProp.nullable, false); strictEqual(sdkType.nullableValues, true); @@ -634,6 +636,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(elementType.kind, "nullable"); strictEqual(elementType.nullable, true); strictEqual(elementType.valueType.kind, "float32"); + strictEqual(elementType.valueType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; strictEqual(nameProp.nullable, false); strictEqual(sdkType.nullableValues, true); @@ -659,6 +662,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(elementTypeValueType.values.length, 2); strictEqual(elementTypeValueType.values[0].kind, "string"); strictEqual(elementTypeValueType.values[1].kind, "float32"); + strictEqual(elementTypeValueType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; strictEqual(nameProp.nullable, false); strictEqual(sdkType.nullableValues, true); @@ -698,6 +702,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(additionalProperties.nullable, true); strictEqual(extendsType.additionalPropertiesNullable, true); strictEqual(additionalProperties.valueType.kind, "string"); + strictEqual(additionalProperties.valueType.nullable, true); const isType = models.find((x) => x.name === "TestIs"); ok(isType); @@ -708,6 +713,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(isTypeAdditionalProperties.nullable, true); strictEqual(isType.additionalPropertiesNullable, true); strictEqual(isTypeAdditionalProperties.valueType.kind, "string"); + strictEqual(isTypeAdditionalProperties.valueType.nullable, true); const spreadType = models.find((x) => x.name === "TestSpread"); ok(spreadType); @@ -718,6 +724,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(spreadTypeAdditionalProperties.nullable, true); strictEqual(spreadType.additionalPropertiesNullable, true); strictEqual(spreadTypeAdditionalProperties.valueType.kind, "string"); + strictEqual(spreadTypeAdditionalProperties.valueType.nullable, true); }); it("additional property nullable with more types", async function () { @@ -759,6 +766,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(extendsAdPropUnderlyingType.values.length, 2); strictEqual(extendsAdPropUnderlyingType.values[0].kind, "string"); strictEqual(extendsAdPropUnderlyingType.values[1].kind, "float32"); + strictEqual(extendsAdPropUnderlyingType.nullable, true); strictEqual(extendsTypeAdditionalProperties.nullable, true); strictEqual(extendsType.additionalPropertiesNullable, true); @@ -776,6 +784,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(isTypeAdditionalPropertiesUnderlyingType.values.length, 2); strictEqual(isTypeAdditionalPropertiesUnderlyingType.values[0].kind, "string"); strictEqual(isTypeAdditionalPropertiesUnderlyingType.values[1].kind, "float32"); + strictEqual(isTypeAdditionalPropertiesUnderlyingType.nullable, true); strictEqual(isTypeAdditionalProperties.nullable, true); strictEqual(isType.additionalPropertiesNullable, true); @@ -797,6 +806,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(spreadTypeAdditionalPropertiesUnderlyingType.values.length, 2); strictEqual(spreadTypeAdditionalPropertiesUnderlyingType.values[0].kind, "string"); strictEqual(spreadTypeAdditionalPropertiesUnderlyingType.values[1].kind, "float32"); + strictEqual(spreadTypeAdditionalPropertiesUnderlyingType.nullable, true); strictEqual(spreadTypeAdditionalProperties.nullable, true); strictEqual(spreadType.additionalPropertiesNullable, true); }); From 058450f3103cb719d0637ffc412a0ca220bf1eb9 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Wed, 22 May 2024 11:24:00 -0400 Subject: [PATCH 19/23] remove nullable properties --- .../doc/types.tsp | 2 - .../src/http.ts | 7 --- .../src/interfaces.ts | 40 ------------- .../src/internal-utils.ts | 25 +------- .../src/package.ts | 9 --- .../src/types.ts | 25 -------- .../test/package.test.ts | 45 +++++--------- .../test/types.test.ts | 60 ------------------- 8 files changed, 16 insertions(+), 197 deletions(-) diff --git a/packages/typespec-client-generator-core/doc/types.tsp b/packages/typespec-client-generator-core/doc/types.tsp index 46da305b4b..8a4b18b7a7 100644 --- a/packages/typespec-client-generator-core/doc/types.tsp +++ b/packages/typespec-client-generator-core/doc/types.tsp @@ -9,13 +9,11 @@ namespace TypeSpec.ClientGenerator.Core; * * @property __raw: the original TypeSpec type * @property kind: the kind of type - * @property nullable: Whether the type is nullable on the wire * @property deprecation: deprecated message if the type is deprecated */ @discriminator("kind") model SdkType { __raw?: unknown; - nullable: boolean; deprecation?: string; } diff --git a/packages/typespec-client-generator-core/src/http.ts b/packages/typespec-client-generator-core/src/http.ts index 73fdae83b4..5a8d2a90a7 100644 --- a/packages/typespec-client-generator-core/src/http.ts +++ b/packages/typespec-client-generator-core/src/http.ts @@ -45,7 +45,6 @@ import { isAcceptHeader, isContentTypeHeader, isNeverOrVoidType, - isNullableInternal, isSubscriptionId, } from "./internal-utils.js"; import { createDiagnostic } from "./lib.js"; @@ -165,7 +164,6 @@ function getSdkHttpParameters( apiVersions: getAvailableApiVersions(context, tspBody.type, httpOperation.operation), type, optional: false, - nullable: isNullableInternal(tspBody.type), // eslint-disable-line deprecation/deprecation correspondingMethodParams, }; } @@ -234,7 +232,6 @@ function createContentTypeOrAcceptHeader( let type: SdkType = { kind: "string", encode: "string", - nullable: false, }; // for contentType, we treat it as a constant IFF there's one value and it's application/json. // this is to prevent a breaking change when a service adds more content types in the future. @@ -250,7 +247,6 @@ function createContentTypeOrAcceptHeader( ) { // in this case, we just want a content type of application/json type = { - nullable: false, kind: "constant", value: bodyObject.contentTypes[0], valueType: type, @@ -268,7 +264,6 @@ function createContentTypeOrAcceptHeader( isApiVersionParam: false, onClient: false, optional: false, - nullable: false, }; } @@ -397,7 +392,6 @@ function getSdkHttpResponseAndExceptions( details: getDocHelper(context, header).details, serializedName: getHeaderFieldName(context.program, header), type: clientType, - nullable: isNullableInternal(header.type), // eslint-disable-line deprecation/deprecation }); } if (innerResponse.body && !isNeverOrVoidType(innerResponse.body.type)) { @@ -437,7 +431,6 @@ function getSdkHttpResponseAndExceptions( httpOperation.operation, httpOperation.operation ), - nullable: body ? isNullableInternal(body) : true, // eslint-disable-line deprecation/deprecation }; if (response.statusCodes === "*" || (body && isErrorModel(context.program, body))) { exceptions.set(response.statusCodes, sdkResponse); diff --git a/packages/typespec-client-generator-core/src/interfaces.ts b/packages/typespec-client-generator-core/src/interfaces.ts index 0684f4b2c2..3e23efaba1 100644 --- a/packages/typespec-client-generator-core/src/interfaces.ts +++ b/packages/typespec-client-generator-core/src/interfaces.ts @@ -80,11 +80,6 @@ export interface SdkOperationGroup { interface SdkTypeBase { __raw?: Type; kind: string; - /** - * @deprecated Use helper function `isNullable` instead. - * https://github.com/Azure/typespec-azure/issues/891 - */ - nullable: boolean; deprecation?: string; description?: string; details?: string; @@ -231,11 +226,6 @@ export interface SdkDurationType extends SdkTypeBase { export interface SdkArrayType extends SdkTypeBase { kind: "array"; valueType: SdkType; - /** - * @deprecated Pass `valueType` to `isNullable` instead. - * https://github.com/Azure/typespec-azure/issues/891 - */ - nullableValues: boolean; } export interface SdkTupleType extends SdkTypeBase { @@ -247,11 +237,6 @@ export interface SdkDictionaryType extends SdkTypeBase { kind: "dict"; keyType: SdkType; valueType: SdkType; - /** - * @deprecated Pass `valueType` to `isNullable` instead. - * https://github.com/Azure/typespec-azure/issues/891 - */ - nullableValues: boolean; } export interface SdkNullableType extends SdkTypeBase { @@ -314,11 +299,6 @@ export interface SdkModelType extends SdkTypeBase { access?: AccessFlags; usage: UsageFlags; additionalProperties?: SdkType; - /** - * @deprecated This property is deprecated. Pass the type of the additionalProperties to `isNullable` instead. - * https://github.com/Azure/typespec-azure/issues/891 - */ - additionalPropertiesNullable?: boolean; discriminatorValue?: string; discriminatedSubtypes?: Record; discriminatorProperty?: SdkModelPropertyType; @@ -354,11 +334,6 @@ export interface SdkModelPropertyTypeBase { clientDefaultValue?: any; isApiVersionParam: boolean; optional: boolean; - /** - * @deprecated Use exported helper function `isNullable` instead, and pass the type of the property to the function. - * https://github.com/Azure/typespec-azure/issues/891 - */ - nullable: boolean; } export interface SdkEndpointParameter extends SdkModelPropertyTypeBase { @@ -442,21 +417,11 @@ export interface SdkServiceResponseHeader { type: SdkType; description?: string; details?: string; - /** - * @deprecated This property is deprecated. Pass the type of response header to `isNullable` instead. - * https://github.com/Azure/typespec-azure/issues/891 - */ - nullable: boolean; } export interface SdkMethodResponse { kind: "method"; type?: SdkType; - /** - * @deprecated This property is deprecated. Pass the type of response to `isNullable` instead. - * https://github.com/Azure/typespec-azure/issues/891 - */ - nullable: boolean; resultPath?: string; // if exists, tells you how to get from the service response to the method response. } @@ -464,11 +429,6 @@ export interface SdkServiceResponse { type?: SdkType; headers: SdkServiceResponseHeader[]; apiVersions: string[]; - /** - * @deprecated This property is deprecated. Pass the type of response to `isNullable` instead. - * https://github.com/Azure/typespec-azure/issues/891 - */ - nullable: boolean; } export interface SdkHttpResponse extends SdkServiceResponse { diff --git a/packages/typespec-client-generator-core/src/internal-utils.ts b/packages/typespec-client-generator-core/src/internal-utils.ts index b554c0b35b..cd04e384b7 100644 --- a/packages/typespec-client-generator-core/src/internal-utils.ts +++ b/packages/typespec-client-generator-core/src/internal-utils.ts @@ -243,13 +243,12 @@ export function getHashForType(type: SdkType): string { interface DefaultSdkTypeBase { __raw: Type; - nullable: boolean; deprecation?: string; kind: TKind; } /** - * Helper function to return default values for nullable, encode etc + * Helper function to return default values for encode etc * @param type */ export function getSdkTypeBaseHelper( @@ -259,7 +258,6 @@ export function getSdkTypeBaseHelper( ): DefaultSdkTypeBase { return { __raw: type, - nullable: false, deprecation: getDeprecationDetails(context.program, type)?.message, kind, }; @@ -362,8 +360,7 @@ function getAllResponseBodiesAndNonBodyExists( let nonBodyExists = false; for (const response of responses.values()) { if (response.type) { - // eslint-disable-next-line deprecation/deprecation - if (response.nullable) { + if (response.type.kind === "nullable") { nonBodyExists = true; } allResponseBodies.push(response.type); @@ -380,23 +377,6 @@ export function getAllResponseBodies( return getAllResponseBodiesAndNonBodyExists(responses).allResponseBodies; } -/** - * Determines if a type is nullable. - * @deprecated We want to move away from .nullable on SdkPropertyTypes and have people pass the type to `isNullable` instead. - * https://github.com/Azure/typespec-azure/issues/891 - * @param type - * @returns - */ -export function isNullableInternal(type: Type | SdkServiceOperation): boolean { - if (type.kind === "Union") { - if (getNullOption(type) !== undefined) return true; - return Boolean(ignoreDiagnostics(getUnionAsEnum(type))?.nullable); - } - if (type.kind === "http") { - return getAllResponseBodiesAndNonBodyExists(type.responses).nonBodyExists; - } - return false; -} /** * Use this if you are trying to create a generated name for something without an original TypeSpec type. * @@ -449,6 +429,5 @@ export function getAnyType(): SdkBuiltInType { return { kind: "any", encode: "string", - nullable: true, // any is nullable }; } diff --git a/packages/typespec-client-generator-core/src/package.ts b/packages/typespec-client-generator-core/src/package.ts index 3f58f09242..ce310de2f6 100644 --- a/packages/typespec-client-generator-core/src/package.ts +++ b/packages/typespec-client-generator-core/src/package.ts @@ -57,7 +57,6 @@ import { getLocationOfOperation, getSdkTypeBaseHelper, isNeverOrVoidType, - isNullableInternal, updateWithApiVersionInformation, } from "./internal-utils.js"; import { createDiagnostic } from "./lib.js"; @@ -214,7 +213,6 @@ function getSdkMethodResponse( __raw: operation, kind: "union", values: allResponseBodies, - nullable: isNullableInternal(sdkOperation), // eslint-disable-line deprecation/deprecation name: createGeneratedName(operation, "UnionResponse"), isGeneratedName: true, }; @@ -224,7 +222,6 @@ function getSdkMethodResponse( return { kind: "method", type, - nullable: isNullableInternal(sdkOperation), // eslint-disable-line deprecation/deprecation }; } @@ -382,7 +379,6 @@ function getSdkInitializationType< isGeneratedName: true, access: client.kind === "SdkClient" ? "public" : "internal", usage: UsageFlags.Input, - nullable: false, crossLanguageDefinitionId: `${getNamespaceFullName(client.service.namespace!)}.${name}`, apiVersions: context.__tspTypeToApiVersions.get(client.type)!, isFormDataType: false, @@ -412,7 +408,6 @@ function getSdkMethodParameter( name, isGeneratedName: Boolean(libraryName), optional: false, - nullable: false, discriminator: false, serializedName: name, isApiVersionParam: false, @@ -469,7 +464,6 @@ function getSdkEndpointParameter( const name = "endpoint"; type = { kind: "endpoint", - nullable: false, serverUrl: "{endpoint}", templateArguments: [ { @@ -479,7 +473,6 @@ function getSdkEndpointParameter( description: "Service host", kind: "path", onClient: true, - nullable: false, urlEncode: false, optional: false, serializedName: "endpoint", @@ -498,7 +491,6 @@ function getSdkEndpointParameter( const templateArguments: SdkPathParameter[] = []; type = { kind: "endpoint", - nullable: false, serverUrl: servers[0].url, templateArguments, }; @@ -539,7 +531,6 @@ function getSdkEndpointParameter( apiVersions: context.__tspTypeToApiVersions.get(client.type)!, optional, isApiVersionParam: false, - nullable: false, }); } diff --git a/packages/typespec-client-generator-core/src/types.ts b/packages/typespec-client-generator-core/src/types.ts index 1998a37877..b79f372ca8 100644 --- a/packages/typespec-client-generator-core/src/types.ts +++ b/packages/typespec-client-generator-core/src/types.ts @@ -88,7 +88,6 @@ import { isMultipartFormData, isMultipartOperation, isNeverOrVoidType, - isNullableInternal, updateWithApiVersionInformation, } from "./internal-utils.js"; import { createDiagnostic } from "./lib.js"; @@ -154,10 +153,6 @@ export function addEncodeInfo( innerType.wireType = diagnostics.pipe( getClientTypeWithDiagnostics(context, encodeData.type) ) as SdkBuiltInType; - // eslint-disable-next-line deprecation/deprecation - if (type.kind === "ModelProperty" && isNullableInternal(type.type)) { - innerType.wireType.nullable = true; // eslint-disable-line deprecation/deprecation - } } if (innerType.kind === "utcDateTime" || innerType.kind === "offsetDateTime") { if (encodeData) { @@ -168,10 +163,6 @@ export function addEncodeInfo( } else if (type.kind === "ModelProperty" && isHeader(context.program, type)) { innerType.encode = "rfc7231"; } - // eslint-disable-next-line deprecation/deprecation - if (type.kind === "ModelProperty" && isNullableInternal(type.type)) { - innerType.wireType.nullable = true; // eslint-disable-line deprecation/deprecation - } } if (innerType.kind === "bytes") { if (encodeData) { @@ -294,14 +285,12 @@ export function getSdkArrayOrDictWithDiagnostics( getClientTypeWithDiagnostics(context, type.indexer.key, operation) ), valueType, - nullableValues: isNullableInternal(type.indexer.value!), // eslint-disable-line deprecation/deprecation }); } else if (name === "integer") { // only array's index key name is integer return diagnostics.wrap({ ...getSdkTypeBaseHelper(context, type, "array"), valueType, - nullableValues: isNullableInternal(type.indexer.value!), // eslint-disable-line deprecation/deprecation }); } } @@ -376,15 +365,12 @@ export function getSdkUnionWithDiagnostics( values: nonNullOptions.map((x) => diagnostics.pipe(getClientTypeWithDiagnostics(context, x, operation)) ), - nullable: false, }; } if (nullOption !== undefined) { - retval.nullable = true; // eslint-disable-line deprecation/deprecation retval = { ...getSdkTypeBaseHelper(context, type, "nullable"), - nullable: true, valueType: retval, }; } @@ -500,7 +486,6 @@ function addDiscriminatorToModelType( } } else { discriminatorType = { - nullable: false, kind: "string", encode: "string", }; @@ -525,7 +510,6 @@ function addDiscriminatorToModelType( isApiVersionParam: false, isMultipartFileInput: false, // discriminator property cannot be a file flatten: false, // discriminator properties can not be flattened - nullable: false, }); model.discriminatorProperty = model.properties[0]; } @@ -576,14 +560,12 @@ export function getSdkModelWithDiagnostics( sdkType.additionalProperties = diagnostics.pipe( getClientTypeWithDiagnostics(context, type.sourceModel!.indexer!.value!, operation) ); - sdkType.additionalPropertiesNullable = isNullableInternal(type.sourceModel!.indexer!.value!); // eslint-disable-line deprecation/deprecation } // model MyModel { ...Record<>} should be model with additional properties if (type.indexer) { sdkType.additionalProperties = diagnostics.pipe( getClientTypeWithDiagnostics(context, type.indexer.value, operation) ); - sdkType.additionalPropertiesNullable = isNullableInternal(type.indexer.value); // eslint-disable-line deprecation/deprecation } // propreties should be generated first since base model'sdiscriminator handling is depend on derived model's properties diagnostics.pipe(addPropertiesToModelType(context, type, sdkType, operation)); @@ -596,7 +578,6 @@ export function getSdkModelWithDiagnostics( if (baseModel.kind === "dict") { // model MyModel extends Record<> {} should be model with additional properties sdkType.additionalProperties = baseModel.valueType; - sdkType.additionalPropertiesNullable = isNullableInternal(baseModel.valueType.__raw!); // eslint-disable-line deprecation/deprecation } else { sdkType.baseModel = baseModel; } @@ -718,7 +699,6 @@ function getSdkUnionEnumValues( value: member.value, valueType: enumType.valueType, enumType, - nullable: false, }); } return values; @@ -740,7 +720,6 @@ export function getSdkUnionEnum(context: TCGCContext, type: UnionEnum, operation getUnionAsEnumValueType(context, type.union) ?? getSdkEnumValueType(context, type.flattenedMembers.values()), values: [], - nullable: type.nullable, isFixed: !type.open, isFlags: false, usage: UsageFlags.None, // We will add usage as we loop through the operations @@ -943,7 +922,6 @@ function getSdkCredentialType( __raw: client.service, kind: "credential", scheme: scheme, - nullable: false, }); } } @@ -952,7 +930,6 @@ function getSdkCredentialType( __raw: client.service, kind: "union", values: credentialTypes, - nullable: false, name: createGeneratedName(client.service, "CredentialUnion"), isGeneratedName: true, }; @@ -978,7 +955,6 @@ export function getSdkCredentialParameter( onClient: true, optional: false, isApiVersionParam: false, - nullable: false, }; } @@ -1009,7 +985,6 @@ export function getSdkModelPropertyTypeBase( name, isGeneratedName: false, optional: type.optional, - nullable: isNullableInternal(type.type), // eslint-disable-line deprecation/deprecation ...updateWithApiVersionInformation( context, type, diff --git a/packages/typespec-client-generator-core/test/package.test.ts b/packages/typespec-client-generator-core/test/package.test.ts index d607e01939..ad30cdb5c4 100644 --- a/packages/typespec-client-generator-core/test/package.test.ts +++ b/packages/typespec-client-generator-core/test/package.test.ts @@ -936,7 +936,6 @@ describe("typespec-client-generator-core: package", () => { strictEqual(methodParam.onClient, false); strictEqual(methodParam.isApiVersionParam, false); strictEqual(methodParam.type.kind, "string"); - strictEqual(methodParam.nullable, false); const serviceOperation = method.operation; strictEqual(serviceOperation.bodyParam, undefined); @@ -954,7 +953,6 @@ describe("typespec-client-generator-core: package", () => { strictEqual(pathParam.isApiVersionParam, false); strictEqual(pathParam.type.kind, "string"); strictEqual(pathParam.urlEncode, true); - strictEqual(pathParam.nullable, false); strictEqual(method.response.kind, "method"); strictEqual(method.response.type, undefined); @@ -974,11 +972,11 @@ describe("typespec-client-generator-core: package", () => { const sdkPackage = runner.context.experimental_sdkPackage; const method = getServiceMethodOfClient(sdkPackage); const methodParam = method.parameters[0]; - strictEqual(methodParam.nullable, true); + strictEqual(methodParam.type.kind, "nullable"); const serviceOperation = method.operation; const pathParam = serviceOperation.parameters[0]; - strictEqual(pathParam.nullable, true); + strictEqual(pathParam.type.kind, "nullable"); }); it("path defined in model", async () => { @@ -1007,7 +1005,6 @@ describe("typespec-client-generator-core: package", () => { strictEqual(pathMethod.onClient, false); strictEqual(pathMethod.isApiVersionParam, false); strictEqual(pathMethod.type.kind, "string"); - strictEqual(pathMethod.nullable, false); const serviceOperation = method.operation; strictEqual(serviceOperation.bodyParam, undefined); @@ -1021,7 +1018,6 @@ describe("typespec-client-generator-core: package", () => { strictEqual(pathParam.isApiVersionParam, false); strictEqual(pathParam.type.kind, "string"); strictEqual(pathParam.urlEncode, true); - strictEqual(pathParam.nullable, false); strictEqual(pathParam.correspondingMethodParams.length, 1); deepStrictEqual(pathParam.correspondingMethodParams[0], pathMethod); }); @@ -1048,7 +1044,6 @@ describe("typespec-client-generator-core: package", () => { strictEqual(methodParam.onClient, false); strictEqual(methodParam.isApiVersionParam, false); strictEqual(methodParam.type.kind, "string"); - strictEqual(methodParam.nullable, false); const serviceOperation = method.operation; strictEqual(serviceOperation.bodyParam, undefined); @@ -1066,7 +1061,6 @@ describe("typespec-client-generator-core: package", () => { strictEqual(headerParam.isApiVersionParam, false); strictEqual(headerParam.type.kind, "string"); strictEqual(headerParam.collectionFormat, undefined); - strictEqual(headerParam.nullable, false); const correspondingMethodParams = headerParam.correspondingMethodParams; strictEqual(correspondingMethodParams.length, 1); @@ -1084,11 +1078,11 @@ describe("typespec-client-generator-core: package", () => { const sdkPackage = runner.context.experimental_sdkPackage; const method = getServiceMethodOfClient(sdkPackage); const methodParam = method.parameters[0]; - strictEqual(methodParam.nullable, true); + strictEqual(methodParam.type.kind, "nullable"); const serviceOperation = method.operation; const headerParam = serviceOperation.parameters[0]; - strictEqual(headerParam.nullable, true); + strictEqual(headerParam.type.kind, "nullable"); }); it("header collection format", async () => { @@ -1129,7 +1123,6 @@ describe("typespec-client-generator-core: package", () => { strictEqual(methodParam.onClient, false); strictEqual(methodParam.isApiVersionParam, false); strictEqual(methodParam.type.kind, "string"); - strictEqual(methodParam.nullable, false); const serviceOperation = method.operation; strictEqual(serviceOperation.bodyParam, undefined); @@ -1146,7 +1139,6 @@ describe("typespec-client-generator-core: package", () => { strictEqual(queryParam.isApiVersionParam, false); strictEqual(queryParam.type.kind, "string"); strictEqual(queryParam.collectionFormat, undefined); - strictEqual(queryParam.nullable, false); const correspondingMethodParams = queryParam.correspondingMethodParams; strictEqual(correspondingMethodParams.length, 1); @@ -1164,11 +1156,11 @@ describe("typespec-client-generator-core: package", () => { const sdkPackage = runner.context.experimental_sdkPackage; const method = getServiceMethodOfClient(sdkPackage); const methodParam = method.parameters[0]; - strictEqual(methodParam.nullable, true); + strictEqual(methodParam.type.kind, "nullable"); const serviceOperation = method.operation; const queryParam = serviceOperation.parameters[0]; - strictEqual(queryParam.nullable, true); + strictEqual(queryParam.type.kind, "nullable"); }); it("query collection format", async () => { @@ -1214,7 +1206,6 @@ describe("typespec-client-generator-core: package", () => { strictEqual(methodBodyParam.onClient, false); strictEqual(methodBodyParam.isApiVersionParam, false); strictEqual(methodBodyParam.type, sdkPackage.models[0]); - strictEqual(methodBodyParam.nullable, false); const methodContentTypeParam = method.parameters.find((x) => x.name === "contentType"); ok(methodContentTypeParam); @@ -1232,7 +1223,6 @@ describe("typespec-client-generator-core: package", () => { strictEqual(bodyParameter.onClient, false); strictEqual(bodyParameter.optional, false); strictEqual(bodyParameter.type, sdkPackage.models[0]); - strictEqual(bodyParameter.nullable, false); const correspondingMethodParams = bodyParameter.correspondingMethodParams; strictEqual(correspondingMethodParams.length, 1); @@ -1268,11 +1258,11 @@ describe("typespec-client-generator-core: package", () => { const method = getServiceMethodOfClient(sdkPackage); const methodBodyParam = method.parameters.find((x) => x.name === "body"); ok(methodBodyParam); - strictEqual(methodBodyParam.nullable, true); + strictEqual(methodBodyParam.type.kind, "nullable"); const serviceOperation = method.operation; ok(serviceOperation.bodyParam); - strictEqual(serviceOperation.bodyParam.nullable, true); + strictEqual(serviceOperation.bodyParam.type.kind, "nullable"); }); it("body optional", async () => { @@ -1802,8 +1792,6 @@ describe("typespec-client-generator-core: package", () => { ); strictEqual(createResponse.headers.length, 1); strictEqual(createResponse.headers[0].serializedName, "id"); - strictEqual(createResponse.headers[0].nullable, false); - strictEqual(createResponse.nullable, false); strictEqual( createResponse.type, sdkPackage.models.find((x) => x.name === "Widget") @@ -1811,7 +1799,6 @@ describe("typespec-client-generator-core: package", () => { strictEqual(method.response.resultPath, undefined); strictEqual(method.response.kind, "method"); - strictEqual(method.response.nullable, false); const methodResponseType = method.response.type; ok(methodResponseType); strictEqual( @@ -1838,10 +1825,9 @@ describe("typespec-client-generator-core: package", () => { const createResponse = serviceResponses.get(200); ok(createResponse); - strictEqual(createResponse.headers[0].nullable, true); - strictEqual(createResponse.nullable, true); - - strictEqual(method.response.nullable, true); + strictEqual(createResponse.headers[0].type.kind, "nullable"); + strictEqual(createResponse.type?.kind, "nullable"); + strictEqual(method.response.type?.kind, "nullable"); }); it("OkResponse with NoContentResponse", async () => { @@ -1860,13 +1846,11 @@ describe("typespec-client-generator-core: package", () => { const okResponse = serviceResponses.get(200); ok(okResponse); - strictEqual(okResponse.nullable, false); const noContentResponse = serviceResponses.get(204); ok(noContentResponse); - strictEqual(noContentResponse.nullable, true); - - strictEqual(method.response.nullable, true); + strictEqual(noContentResponse.type?.kind, "nullable"); + strictEqual(method.response.type?.kind, "nullable"); }); it("NoContentResponse", async () => { @@ -1879,7 +1863,7 @@ describe("typespec-client-generator-core: package", () => { const method = getServiceMethodOfClient(sdkPackage); strictEqual(sdkPackage.models.length, 0); strictEqual(method.name, "delete"); - strictEqual(method.response.nullable, true); + strictEqual(method.response.type?.kind, "nullale"); const serviceResponses = method.operation.responses; strictEqual(serviceResponses.size, 1); @@ -1888,7 +1872,6 @@ describe("typespec-client-generator-core: package", () => { strictEqual(voidResponse.kind, "http"); strictEqual(voidResponse.type, undefined); strictEqual(voidResponse.headers.length, 0); - strictEqual(voidResponse.nullable, true); strictEqual(method.response.type, undefined); strictEqual(method.response.resultPath, undefined); diff --git a/packages/typespec-client-generator-core/test/types.test.ts b/packages/typespec-client-generator-core/test/types.test.ts index 6c0137477f..2c8429671e 100644 --- a/packages/typespec-client-generator-core/test/types.test.ts +++ b/packages/typespec-client-generator-core/test/types.test.ts @@ -365,16 +365,12 @@ describe("typespec-client-generator-core: types", () => { ); const nullableType = getSdkTypeHelper(runner); strictEqual(nullableType.kind, "nullable"); - strictEqual(nullableType.nullable, true); const sdkType = nullableType.valueType; strictEqual(sdkType.kind, "duration"); strictEqual(sdkType.wireType.kind, "float"); strictEqual(sdkType.encode, "seconds"); - strictEqual(sdkType.nullable, true); - strictEqual(sdkType.wireType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; - strictEqual(nameProp.nullable, true); }); it("float seconds decorated scalar", async function () { @@ -486,10 +482,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkType.kind, "utcDateTime"); strictEqual(sdkType.wireType.kind, "int64"); strictEqual(sdkType.encode, "unixTimestamp"); - strictEqual(sdkType.nullable, true); - strictEqual(sdkType.wireType.nullable, true); const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; - strictEqual(nameProp.nullable, true); }); it("unixTimestamp array", async function () { @@ -550,8 +543,6 @@ describe("typespec-client-generator-core: types", () => { const sdkType = nullableType.valueType; strictEqual(sdkType.kind, "float32"); - const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; - strictEqual(nameProp.nullable, true); }); it("nullable with more types", async function () { @@ -571,9 +562,6 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkType.values.length, 2); strictEqual(sdkType.values[0].kind, "string"); strictEqual(sdkType.values[1].kind, "float32"); - strictEqual(sdkType.nullable, true); - const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; - strictEqual(nameProp.nullable, true); }); it("record with nullable", async function () { @@ -590,10 +578,6 @@ describe("typespec-client-generator-core: types", () => { const elementType = sdkType.valueType; strictEqual(elementType.kind, "nullable"); strictEqual(elementType.valueType.kind, "float32"); - strictEqual(elementType.nullable, true); - const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; - strictEqual(nameProp.nullable, false); - strictEqual(sdkType.nullableValues, true); }); it("record with nullable with more types", async function () { @@ -615,10 +599,6 @@ describe("typespec-client-generator-core: types", () => { strictEqual(elementTypeValueType.values.length, 2); strictEqual(elementTypeValueType.values[0].kind, "string"); strictEqual(elementTypeValueType.values[1].kind, "float32"); - strictEqual(elementTypeValueType.nullable, true); - const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; - strictEqual(nameProp.nullable, false); - strictEqual(sdkType.nullableValues, true); }); it("array with nullable", async function () { @@ -634,12 +614,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkType.kind, "array"); const elementType = sdkType.valueType; strictEqual(elementType.kind, "nullable"); - strictEqual(elementType.nullable, true); strictEqual(elementType.valueType.kind, "float32"); - strictEqual(elementType.valueType.nullable, true); - const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; - strictEqual(nameProp.nullable, false); - strictEqual(sdkType.nullableValues, true); }); it("array with nullable with more types", async function () { @@ -655,17 +630,11 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkType.kind, "array"); const elementType = sdkType.valueType; strictEqual(elementType.kind, "nullable"); - strictEqual(elementType.nullable, true); - const elementTypeValueType = elementType.valueType; strictEqual(elementTypeValueType.kind, "union"); strictEqual(elementTypeValueType.values.length, 2); strictEqual(elementTypeValueType.values[0].kind, "string"); strictEqual(elementTypeValueType.values[1].kind, "float32"); - strictEqual(elementTypeValueType.nullable, true); - const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; - strictEqual(nameProp.nullable, false); - strictEqual(sdkType.nullableValues, true); }); it("additional property is nullable", async function () { @@ -699,10 +668,7 @@ describe("typespec-client-generator-core: types", () => { const additionalProperties = extendsType.additionalProperties; ok(additionalProperties); strictEqual(additionalProperties.kind, "nullable"); - strictEqual(additionalProperties.nullable, true); - strictEqual(extendsType.additionalPropertiesNullable, true); strictEqual(additionalProperties.valueType.kind, "string"); - strictEqual(additionalProperties.valueType.nullable, true); const isType = models.find((x) => x.name === "TestIs"); ok(isType); @@ -710,10 +676,7 @@ describe("typespec-client-generator-core: types", () => { const isTypeAdditionalProperties = isType.additionalProperties; ok(isTypeAdditionalProperties); strictEqual(isTypeAdditionalProperties.kind, "nullable"); - strictEqual(isTypeAdditionalProperties.nullable, true); - strictEqual(isType.additionalPropertiesNullable, true); strictEqual(isTypeAdditionalProperties.valueType.kind, "string"); - strictEqual(isTypeAdditionalProperties.valueType.nullable, true); const spreadType = models.find((x) => x.name === "TestSpread"); ok(spreadType); @@ -721,10 +684,7 @@ describe("typespec-client-generator-core: types", () => { const spreadTypeAdditionalProperties = spreadType.additionalProperties; ok(spreadTypeAdditionalProperties); strictEqual(spreadTypeAdditionalProperties.kind, "nullable"); - strictEqual(spreadTypeAdditionalProperties.nullable, true); - strictEqual(spreadType.additionalPropertiesNullable, true); strictEqual(spreadTypeAdditionalProperties.valueType.kind, "string"); - strictEqual(spreadTypeAdditionalProperties.valueType.nullable, true); }); it("additional property nullable with more types", async function () { @@ -766,9 +726,6 @@ describe("typespec-client-generator-core: types", () => { strictEqual(extendsAdPropUnderlyingType.values.length, 2); strictEqual(extendsAdPropUnderlyingType.values[0].kind, "string"); strictEqual(extendsAdPropUnderlyingType.values[1].kind, "float32"); - strictEqual(extendsAdPropUnderlyingType.nullable, true); - strictEqual(extendsTypeAdditionalProperties.nullable, true); - strictEqual(extendsType.additionalPropertiesNullable, true); const isType = models.find((x) => x.name === "TestIs"); ok(isType); @@ -784,9 +741,6 @@ describe("typespec-client-generator-core: types", () => { strictEqual(isTypeAdditionalPropertiesUnderlyingType.values.length, 2); strictEqual(isTypeAdditionalPropertiesUnderlyingType.values[0].kind, "string"); strictEqual(isTypeAdditionalPropertiesUnderlyingType.values[1].kind, "float32"); - strictEqual(isTypeAdditionalPropertiesUnderlyingType.nullable, true); - strictEqual(isTypeAdditionalProperties.nullable, true); - strictEqual(isType.additionalPropertiesNullable, true); const spreadType = models.find((x) => x.name === "TestSpread"); ok(spreadType); @@ -806,9 +760,6 @@ describe("typespec-client-generator-core: types", () => { strictEqual(spreadTypeAdditionalPropertiesUnderlyingType.values.length, 2); strictEqual(spreadTypeAdditionalPropertiesUnderlyingType.values[0].kind, "string"); strictEqual(spreadTypeAdditionalPropertiesUnderlyingType.values[1].kind, "float32"); - strictEqual(spreadTypeAdditionalPropertiesUnderlyingType.nullable, true); - strictEqual(spreadTypeAdditionalProperties.nullable, true); - strictEqual(spreadType.additionalPropertiesNullable, true); }); it("model with simple union property", async function () { @@ -909,8 +860,6 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkType.isUnionAsEnum, false); strictEqual(sdkType.name, "PetKind"); - const pet = runner.context.experimental_sdkPackage.models[0].properties[0]; - strictEqual(pet.nullable, true); const values = sdkType.values; strictEqual(values.length, 3); }); @@ -932,8 +881,6 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkType.isUnionAsEnum, true); strictEqual(sdkType.name, "HomePet"); - const pet = runner.context.experimental_sdkPackage.models[0].properties[0]; - strictEqual(pet.nullable, true); const values = sdkType.values; strictEqual(values.length, 3); }); @@ -963,7 +910,6 @@ describe("typespec-client-generator-core: types", () => { const sdkType = nullableType.valueType; strictEqual(sdkType.kind, "model"); strictEqual(sdkType.name, "PropertyModel"); - strictEqual(model.properties[0].nullable, true); }); it("mix types", async function () { @@ -994,8 +940,6 @@ describe("typespec-client-generator-core: types", () => { const nullableModel = models.find((x) => x.kind === "model" && x.name === "TestNullable"); ok(nullableModel); strictEqual(model.properties[0].type.kind, "union"); - strictEqual(model.properties[0].type.nullable, false); - strictEqual(model.properties[0].nullable, false); const unionType = model.properties[0].type; strictEqual(unionType.kind, "union"); for (const v of unionType.values) { @@ -1007,8 +951,6 @@ describe("typespec-client-generator-core: types", () => { } const nullableProp = nullableModel.properties[0]; strictEqual(nullableProp.type.kind, "nullable"); - strictEqual(nullableProp.type.nullable, true); - strictEqual(nullableProp.nullable, true); strictEqual(nullableProp.type.valueType.kind, "union"); strictEqual(nullableProp.type.valueType.values.length, 3); @@ -1599,7 +1541,6 @@ describe("typespec-client-generator-core: types", () => { const enumType = nullableType.valueType; strictEqual(enumType.kind, "enum"); strictEqual(enumType.name, "Test"); - strictEqual(enumType.nullable, true); strictEqual(enumType.isUnionAsEnum, true); const values = enumType.values; strictEqual(values.length, 4); @@ -1652,7 +1593,6 @@ describe("typespec-client-generator-core: types", () => { strictEqual(unionType.kind, "union"); strictEqual(unionType.name, "Test"); - strictEqual(unionType.nullable, true); const values = unionType.values; strictEqual(values.length, 3); From da58ec0402ad21e8c2fec97ea15c355de5af6cbc Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Wed, 22 May 2024 11:24:42 -0400 Subject: [PATCH 20/23] format --- packages/typespec-client-generator-core/src/internal-utils.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/typespec-client-generator-core/src/internal-utils.ts b/packages/typespec-client-generator-core/src/internal-utils.ts index cd04e384b7..91f0b27507 100644 --- a/packages/typespec-client-generator-core/src/internal-utils.ts +++ b/packages/typespec-client-generator-core/src/internal-utils.ts @@ -1,4 +1,3 @@ -import { getUnionAsEnum } from "@azure-tools/typespec-azure-core"; import { BooleanLiteral, Diagnostic, @@ -17,7 +16,6 @@ import { getDoc, getNamespaceFullName, getSummary, - ignoreDiagnostics, isNeverType, isNullType, isVoidType, @@ -33,7 +31,6 @@ import { SdkModelPropertyType, SdkModelType, SdkParameter, - SdkServiceOperation, SdkType, SdkUnionType, } from "./interfaces.js"; From 219cc6b88e100dbc7fd681542d6a73cbacefe778 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Wed, 22 May 2024 11:45:55 -0400 Subject: [PATCH 21/23] have response type return nullable in case of unions with no content response --- .../src/internal-utils.ts | 2 +- .../typespec-client-generator-core/src/package.ts | 15 ++++++++------- .../test/package.test.ts | 8 ++++++-- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/typespec-client-generator-core/src/internal-utils.ts b/packages/typespec-client-generator-core/src/internal-utils.ts index 91f0b27507..af56cc92f7 100644 --- a/packages/typespec-client-generator-core/src/internal-utils.ts +++ b/packages/typespec-client-generator-core/src/internal-utils.ts @@ -347,7 +347,7 @@ export function getNullOption(type: Union): Type | undefined { return [...type.variants.values()].map((x) => x.type).filter((t) => isNullType(t))[0]; } -function getAllResponseBodiesAndNonBodyExists( +export function getAllResponseBodiesAndNonBodyExists( responses: Map ): { allResponseBodies: SdkType[]; diff --git a/packages/typespec-client-generator-core/src/package.ts b/packages/typespec-client-generator-core/src/package.ts index ce310de2f6..4f6593091d 100644 --- a/packages/typespec-client-generator-core/src/package.ts +++ b/packages/typespec-client-generator-core/src/package.ts @@ -41,7 +41,6 @@ import { SdkServiceMethod, SdkServiceOperation, SdkServiceParameter, - SdkServiceResponseHeader, SdkType, UsageFlags, } from "./interfaces.js"; @@ -49,7 +48,7 @@ import { TCGCContext, createGeneratedName, filterApiVersionsWithDecorators, - getAllResponseBodies, + getAllResponseBodiesAndNonBodyExists, getAvailableApiVersions, getClientNamespaceStringHelper, getDocHelper, @@ -200,11 +199,7 @@ function getSdkMethodResponse( ): SdkMethodResponse { const responses = sdkOperation.responses; // TODO: put head as bool here - const headers: SdkServiceResponseHeader[] = []; - for (const response of Object.values(responses)) { - headers.push(...response.headers); - } - const allResponseBodies = getAllResponseBodies(responses); + const { allResponseBodies, nonBodyExists } = getAllResponseBodiesAndNonBodyExists(responses); const responseTypes = new Set(allResponseBodies.map((x) => getHashForType(x))); let type: SdkType | undefined = undefined; if (responseTypes.size > 1) { @@ -219,6 +214,12 @@ function getSdkMethodResponse( } else if (responseTypes) { type = allResponseBodies[0]; } + if (nonBodyExists && type) { + type = { + kind: "nullable", + valueType: type, + }; + } return { kind: "method", type, diff --git a/packages/typespec-client-generator-core/test/package.test.ts b/packages/typespec-client-generator-core/test/package.test.ts index ad30cdb5c4..7412371fdf 100644 --- a/packages/typespec-client-generator-core/test/package.test.ts +++ b/packages/typespec-client-generator-core/test/package.test.ts @@ -1849,8 +1849,12 @@ describe("typespec-client-generator-core: package", () => { const noContentResponse = serviceResponses.get(204); ok(noContentResponse); - strictEqual(noContentResponse.type?.kind, "nullable"); + strictEqual(noContentResponse.type, undefined); strictEqual(method.response.type?.kind, "nullable"); + strictEqual( + method.response.type?.valueType, + sdkPackage.models.find((x) => x.name === "Widget") + ); }); it("NoContentResponse", async () => { @@ -1863,7 +1867,7 @@ describe("typespec-client-generator-core: package", () => { const method = getServiceMethodOfClient(sdkPackage); strictEqual(sdkPackage.models.length, 0); strictEqual(method.name, "delete"); - strictEqual(method.response.type?.kind, "nullale"); + strictEqual(method.response.type, undefined); const serviceResponses = method.operation.responses; strictEqual(serviceResponses.size, 1); From fea14406f7c29fa42f806cf0f1bdfffa0e2c1be0 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Thu, 23 May 2024 12:56:14 -0400 Subject: [PATCH 22/23] lint --- packages/typespec-client-generator-core/test/types.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/typespec-client-generator-core/test/types.test.ts b/packages/typespec-client-generator-core/test/types.test.ts index 2c8429671e..6777df9a6a 100644 --- a/packages/typespec-client-generator-core/test/types.test.ts +++ b/packages/typespec-client-generator-core/test/types.test.ts @@ -370,7 +370,6 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkType.kind, "duration"); strictEqual(sdkType.wireType.kind, "float"); strictEqual(sdkType.encode, "seconds"); - const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; }); it("float seconds decorated scalar", async function () { @@ -482,7 +481,6 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkType.kind, "utcDateTime"); strictEqual(sdkType.wireType.kind, "int64"); strictEqual(sdkType.encode, "unixTimestamp"); - const nameProp = runner.context.experimental_sdkPackage.models[0].properties[0]; }); it("unixTimestamp array", async function () { From 5b27b6da6801ddb36edbf595d499a44fbe58b38f Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Fri, 31 May 2024 15:26:19 -0400 Subject: [PATCH 23/23] switch valueType -> type --- .../src/interfaces.ts | 2 +- .../src/package.ts | 2 +- .../src/types.ts | 8 ++-- .../test/package.test.ts | 2 +- .../test/types.test.ts | 44 +++++++++---------- 5 files changed, 29 insertions(+), 29 deletions(-) diff --git a/packages/typespec-client-generator-core/src/interfaces.ts b/packages/typespec-client-generator-core/src/interfaces.ts index c799f5734d..d2d4cf9ec0 100644 --- a/packages/typespec-client-generator-core/src/interfaces.ts +++ b/packages/typespec-client-generator-core/src/interfaces.ts @@ -241,7 +241,7 @@ export interface SdkDictionaryType extends SdkTypeBase { export interface SdkNullableType extends SdkTypeBase { kind: "nullable"; - valueType: SdkType; + type: SdkType; } export interface SdkEnumType extends SdkTypeBase { diff --git a/packages/typespec-client-generator-core/src/package.ts b/packages/typespec-client-generator-core/src/package.ts index 4f6593091d..41f57cb7fa 100644 --- a/packages/typespec-client-generator-core/src/package.ts +++ b/packages/typespec-client-generator-core/src/package.ts @@ -217,7 +217,7 @@ function getSdkMethodResponse( if (nonBodyExists && type) { type = { kind: "nullable", - valueType: type, + type: type, }; } return { diff --git a/packages/typespec-client-generator-core/src/types.ts b/packages/typespec-client-generator-core/src/types.ts index 5c7fd6549d..9ef1b97655 100644 --- a/packages/typespec-client-generator-core/src/types.ts +++ b/packages/typespec-client-generator-core/src/types.ts @@ -125,7 +125,7 @@ export function addFormatInfo( type: ModelProperty | Scalar, propertyType: SdkType ): void { - const innerType = propertyType.kind === "nullable" ? propertyType.valueType : propertyType; + const innerType = propertyType.kind === "nullable" ? propertyType.type : propertyType; const format = getFormat(context.program, type) ?? ""; if (isSdkBuiltInKind(format)) innerType.kind = format; } @@ -145,7 +145,7 @@ export function addEncodeInfo( defaultContentType?: string ): [void, readonly Diagnostic[]] { const diagnostics = createDiagnosticCollector(); - const innerType = propertyType.kind === "nullable" ? propertyType.valueType : propertyType; + const innerType = propertyType.kind === "nullable" ? propertyType.type : propertyType; const encodeData = getEncode(context.program, type); if (innerType.kind === "duration") { if (!encodeData) return diagnostics.wrap(undefined); @@ -371,7 +371,7 @@ export function getSdkUnionWithDiagnostics( if (nullOption !== undefined) { retval = { ...getSdkTypeBaseHelper(context, type, "nullable"), - valueType: retval, + type: retval, }; } @@ -1195,7 +1195,7 @@ function updateUsageOfModel( return; } if (type.kind === "nullable") { - updateUsageOfModel(context, usage, type.valueType, options); + updateUsageOfModel(context, usage, type.type, options); return; } if (type.kind !== "model" && type.kind !== "enum") return; diff --git a/packages/typespec-client-generator-core/test/package.test.ts b/packages/typespec-client-generator-core/test/package.test.ts index 7412371fdf..4a048e0ee6 100644 --- a/packages/typespec-client-generator-core/test/package.test.ts +++ b/packages/typespec-client-generator-core/test/package.test.ts @@ -1852,7 +1852,7 @@ describe("typespec-client-generator-core: package", () => { strictEqual(noContentResponse.type, undefined); strictEqual(method.response.type?.kind, "nullable"); strictEqual( - method.response.type?.valueType, + method.response.type?.type, sdkPackage.models.find((x) => x.name === "Widget") ); }); diff --git a/packages/typespec-client-generator-core/test/types.test.ts b/packages/typespec-client-generator-core/test/types.test.ts index 8618ad529f..4ca2eb576d 100644 --- a/packages/typespec-client-generator-core/test/types.test.ts +++ b/packages/typespec-client-generator-core/test/types.test.ts @@ -366,7 +366,7 @@ describe("typespec-client-generator-core: types", () => { const nullableType = getSdkTypeHelper(runner); strictEqual(nullableType.kind, "nullable"); - const sdkType = nullableType.valueType; + const sdkType = nullableType.type; strictEqual(sdkType.kind, "duration"); strictEqual(sdkType.wireType.kind, "float"); strictEqual(sdkType.encode, "seconds"); @@ -477,7 +477,7 @@ describe("typespec-client-generator-core: types", () => { const nullableType = getSdkTypeHelper(runner); strictEqual(nullableType.kind, "nullable"); - const sdkType = nullableType.valueType; + const sdkType = nullableType.type; strictEqual(sdkType.kind, "utcDateTime"); strictEqual(sdkType.wireType.kind, "int64"); strictEqual(sdkType.encode, "unixTimestamp"); @@ -539,7 +539,7 @@ describe("typespec-client-generator-core: types", () => { const nullableType = getSdkTypeHelper(runner); strictEqual(nullableType.kind, "nullable"); - const sdkType = nullableType.valueType; + const sdkType = nullableType.type; strictEqual(sdkType.kind, "float32"); }); @@ -555,7 +555,7 @@ describe("typespec-client-generator-core: types", () => { const nullableType = getSdkTypeHelper(runner); strictEqual(nullableType.kind, "nullable"); - const sdkType = nullableType.valueType; + const sdkType = nullableType.type; strictEqual(sdkType.kind, "union"); strictEqual(sdkType.values.length, 2); strictEqual(sdkType.values[0].kind, "string"); @@ -575,7 +575,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkType.kind, "dict"); const elementType = sdkType.valueType; strictEqual(elementType.kind, "nullable"); - strictEqual(elementType.valueType.kind, "float32"); + strictEqual(elementType.type.kind, "float32"); }); it("record with nullable with more types", async function () { @@ -592,7 +592,7 @@ describe("typespec-client-generator-core: types", () => { const elementType = sdkType.valueType; strictEqual(elementType.kind, "nullable"); - const elementTypeValueType = elementType.valueType; + const elementTypeValueType = elementType.type; strictEqual(elementTypeValueType.kind, "union"); strictEqual(elementTypeValueType.values.length, 2); strictEqual(elementTypeValueType.values[0].kind, "string"); @@ -612,7 +612,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkType.kind, "array"); const elementType = sdkType.valueType; strictEqual(elementType.kind, "nullable"); - strictEqual(elementType.valueType.kind, "float32"); + strictEqual(elementType.type.kind, "float32"); }); it("array with nullable with more types", async function () { @@ -628,7 +628,7 @@ describe("typespec-client-generator-core: types", () => { strictEqual(sdkType.kind, "array"); const elementType = sdkType.valueType; strictEqual(elementType.kind, "nullable"); - const elementTypeValueType = elementType.valueType; + const elementTypeValueType = elementType.type; strictEqual(elementTypeValueType.kind, "union"); strictEqual(elementTypeValueType.values.length, 2); strictEqual(elementTypeValueType.values[0].kind, "string"); @@ -666,7 +666,7 @@ describe("typespec-client-generator-core: types", () => { const additionalProperties = extendsType.additionalProperties; ok(additionalProperties); strictEqual(additionalProperties.kind, "nullable"); - strictEqual(additionalProperties.valueType.kind, "string"); + strictEqual(additionalProperties.type.kind, "string"); const isType = models.find((x) => x.name === "TestIs"); ok(isType); @@ -674,7 +674,7 @@ describe("typespec-client-generator-core: types", () => { const isTypeAdditionalProperties = isType.additionalProperties; ok(isTypeAdditionalProperties); strictEqual(isTypeAdditionalProperties.kind, "nullable"); - strictEqual(isTypeAdditionalProperties.valueType.kind, "string"); + strictEqual(isTypeAdditionalProperties.type.kind, "string"); const spreadType = models.find((x) => x.name === "TestSpread"); ok(spreadType); @@ -682,7 +682,7 @@ describe("typespec-client-generator-core: types", () => { const spreadTypeAdditionalProperties = spreadType.additionalProperties; ok(spreadTypeAdditionalProperties); strictEqual(spreadTypeAdditionalProperties.kind, "nullable"); - strictEqual(spreadTypeAdditionalProperties.valueType.kind, "string"); + strictEqual(spreadTypeAdditionalProperties.type.kind, "string"); }); it("additional property nullable with more types", async function () { @@ -717,7 +717,7 @@ describe("typespec-client-generator-core: types", () => { const extendsTypeAdditionalProperties = extendsType.additionalProperties; ok(extendsTypeAdditionalProperties); strictEqual(extendsTypeAdditionalProperties.kind, "nullable"); - const extendsAdPropUnderlyingType = extendsTypeAdditionalProperties.valueType; + const extendsAdPropUnderlyingType = extendsTypeAdditionalProperties.type; strictEqual(extendsAdPropUnderlyingType.kind, "union"); strictEqual(extendsAdPropUnderlyingType.name, "TestExtendsAdditionalProperty"); strictEqual(extendsAdPropUnderlyingType.isGeneratedName, true); @@ -732,7 +732,7 @@ describe("typespec-client-generator-core: types", () => { ok(isTypeAdditionalProperties); strictEqual(isTypeAdditionalProperties.kind, "nullable"); - const isTypeAdditionalPropertiesUnderlyingType = isTypeAdditionalProperties.valueType; + const isTypeAdditionalPropertiesUnderlyingType = isTypeAdditionalProperties.type; strictEqual(isTypeAdditionalPropertiesUnderlyingType.kind, "union"); strictEqual(isTypeAdditionalPropertiesUnderlyingType.name, "TestIsAdditionalProperty"); strictEqual(isTypeAdditionalPropertiesUnderlyingType.isGeneratedName, true); @@ -748,7 +748,7 @@ describe("typespec-client-generator-core: types", () => { ok(spreadTypeAdditionalProperties); strictEqual(spreadTypeAdditionalProperties.kind, "nullable"); - const spreadTypeAdditionalPropertiesUnderlyingType = spreadTypeAdditionalProperties.valueType; + const spreadTypeAdditionalPropertiesUnderlyingType = spreadTypeAdditionalProperties.type; strictEqual(spreadTypeAdditionalPropertiesUnderlyingType.kind, "union"); strictEqual( spreadTypeAdditionalPropertiesUnderlyingType.name, @@ -853,7 +853,7 @@ describe("typespec-client-generator-core: types", () => { const nullableType = getSdkTypeHelper(runner); strictEqual(nullableType.kind, "nullable"); - const sdkType = nullableType.valueType; + const sdkType = nullableType.type; strictEqual(sdkType.kind, "enum"); strictEqual(sdkType.isUnionAsEnum, false); strictEqual(sdkType.name, "PetKind"); @@ -874,7 +874,7 @@ describe("typespec-client-generator-core: types", () => { const nullableType = getSdkTypeHelper(runner); strictEqual(nullableType.kind, "nullable"); - const sdkType = nullableType.valueType; + const sdkType = nullableType.type; strictEqual(sdkType.kind, "enum"); strictEqual(sdkType.isUnionAsEnum, true); strictEqual(sdkType.name, "HomePet"); @@ -905,7 +905,7 @@ describe("typespec-client-generator-core: types", () => { const nullableType = model.properties[0].type; strictEqual(nullableType.kind, "nullable"); - const sdkType = nullableType.valueType; + const sdkType = nullableType.type; strictEqual(sdkType.kind, "model"); strictEqual(sdkType.name, "PropertyModel"); }); @@ -949,12 +949,12 @@ describe("typespec-client-generator-core: types", () => { } const nullableProp = nullableModel.properties[0]; strictEqual(nullableProp.type.kind, "nullable"); - strictEqual(nullableProp.type.valueType.kind, "union"); - strictEqual(nullableProp.type.valueType.values.length, 3); + strictEqual(nullableProp.type.type.kind, "union"); + strictEqual(nullableProp.type.type.values.length, 3); // now check without null with help of helper function strictEqual(nullableModel.properties[0].type.kind, "nullable"); - const sdkType = nullableProp.type.valueType; + const sdkType = nullableProp.type.type; strictEqual(sdkType.kind, "union"); for (const v of sdkType.values) { if (v.kind === "model") { @@ -1536,7 +1536,7 @@ describe("typespec-client-generator-core: types", () => { const nullableType = getClientType(runner.context, Test); strictEqual(nullableType.kind, "nullable"); - const enumType = nullableType.valueType; + const enumType = nullableType.type; strictEqual(enumType.kind, "enum"); strictEqual(enumType.name, "Test"); strictEqual(enumType.isUnionAsEnum, true); @@ -1587,7 +1587,7 @@ describe("typespec-client-generator-core: types", () => { const nullableType = getClientType(runner.context, Test); strictEqual(nullableType.kind, "nullable"); - const unionType = nullableType.valueType; + const unionType = nullableType.type; strictEqual(unionType.kind, "union"); strictEqual(unionType.name, "Test");