From 088863a7a222990f30ed68e4f4e53ca142e89d59 Mon Sep 17 00:00:00 2001 From: Chenjie Shi Date: Thu, 9 May 2024 00:20:53 +0800 Subject: [PATCH] [tcgc] add void and never handling for parameter and return types (#797) resolve: https://github.com/Azure/typespec-azure/issues/795 --- .../changes/fix_void-2024-4-8-13-39-22.md | 7 +++++ .../src/http.ts | 29 +++++++++++-------- .../src/internal-utils.ts | 6 ++++ .../src/package.ts | 6 ++-- .../src/types.ts | 9 ++++-- .../test/package.test.ts | 23 +++++++++++++++ .../test/types.test.ts | 20 +++++++++++++ 7 files changed, 83 insertions(+), 17 deletions(-) create mode 100644 .chronus/changes/fix_void-2024-4-8-13-39-22.md diff --git a/.chronus/changes/fix_void-2024-4-8-13-39-22.md b/.chronus/changes/fix_void-2024-4-8-13-39-22.md new file mode 100644 index 0000000000..2e2c146950 --- /dev/null +++ b/.chronus/changes/fix_void-2024-4-8-13-39-22.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@azure-tools/typespec-client-generator-core" +--- + +add void and never handling for parameter and return types \ No newline at end of file diff --git a/packages/typespec-client-generator-core/src/http.ts b/packages/typespec-client-generator-core/src/http.ts index a5e3374b3f..6cbbb73415 100644 --- a/packages/typespec-client-generator-core/src/http.ts +++ b/packages/typespec-client-generator-core/src/http.ts @@ -44,6 +44,7 @@ import { getLocationOfOperation, isAcceptHeader, isContentTypeHeader, + isNeverOrVoidType, isNullable, isSubscriptionId, } from "./internal-utils.js"; @@ -108,6 +109,7 @@ function getSdkHttpParameters( bodyParam: undefined, }; retval.parameters = httpOperation.parameters.parameters + .filter((x) => !isNeverOrVoidType(x.param.type)) .map((x) => diagnostics.pipe(getSdkHttpParameter(context, x.param, httpOperation.operation, x.type)) ) @@ -124,7 +126,7 @@ function getSdkHttpParameters( const correspondingMethodParams: SdkModelPropertyType[] = []; if (tspBody) { // if there's a param on the body, we can just rely on getSdkHttpParameter - if (tspBody.parameter) { + if (tspBody.parameter && !isNeverOrVoidType(tspBody.parameter.type)) { const getParamResponse = diagnostics.pipe( getSdkHttpParameter(context, tspBody.parameter, httpOperation.operation, "body") ); @@ -143,7 +145,7 @@ function getSdkHttpParameters( return diagnostics.wrap(retval); } retval.bodyParam = getParamResponse; - } else { + } else if (!isNeverOrVoidType(tspBody.type)) { const type = diagnostics.pipe( getClientTypeWithDiagnostics(context, tspBody.type, httpOperation.operation) ); @@ -170,15 +172,17 @@ function getSdkHttpParameters( correspondingMethodParams, }; } - addContentTypeInfoToBodyParam(context, httpOperation, retval.bodyParam); - retval.bodyParam.correspondingMethodParams = diagnostics.pipe( - getCorrespondingMethodParams( - context, - httpOperation.operation, - methodParameters, - retval.bodyParam - ) - ); + if (retval.bodyParam) { + addContentTypeInfoToBodyParam(context, httpOperation, retval.bodyParam); + retval.bodyParam.correspondingMethodParams = diagnostics.pipe( + getCorrespondingMethodParams( + context, + httpOperation.operation, + methodParameters, + retval.bodyParam + ) + ); + } } if (retval.bodyParam && !headerParams.some((h) => isContentTypeHeader(h))) { // if we have a body param and no content type header, we add one @@ -380,6 +384,7 @@ function getSdkHttpResponseAndExceptions( for (const innerResponse of response.responses) { for (const header of Object.values(innerResponse.headers || [])) { + if (isNeverOrVoidType(header.type)) continue; const clientType = diagnostics.pipe(getClientTypeWithDiagnostics(context, header.type)); const defaultContentType = innerResponse.body?.contentTypes.includes("application/json") ? "application/json" @@ -395,7 +400,7 @@ function getSdkHttpResponseAndExceptions( nullable: isNullable(header.type), }); } - if (innerResponse.body) { + if (innerResponse.body && !isNeverOrVoidType(innerResponse.body.type)) { if (body && body !== innerResponse.body.type) { diagnostics.add( createDiagnostic({ diff --git a/packages/typespec-client-generator-core/src/internal-utils.ts b/packages/typespec-client-generator-core/src/internal-utils.ts index 2d5761132f..46329cd09d 100644 --- a/packages/typespec-client-generator-core/src/internal-utils.ts +++ b/packages/typespec-client-generator-core/src/internal-utils.ts @@ -15,7 +15,9 @@ import { getNamespaceFullName, getSummary, ignoreDiagnostics, + isNeverType, isNullType, + isVoidType, } from "@typespec/compiler"; import { HttpOperation, HttpStatusCodeRange } from "@typespec/http"; import { getAddedOnVersions, getRemovedOnVersions, getVersions } from "@typespec/versioning"; @@ -407,3 +409,7 @@ export function getLocationOfOperation(operation: Operation): Namespace | Interf // have to check interface first, because interfaces are more granular than namespaces return (operation.interface || operation.namespace)!; } + +export function isNeverOrVoidType(type: Type): boolean { + return isNeverType(type) || isVoidType(type); +} diff --git a/packages/typespec-client-generator-core/src/package.ts b/packages/typespec-client-generator-core/src/package.ts index 1f80b252f1..ca4d529f38 100644 --- a/packages/typespec-client-generator-core/src/package.ts +++ b/packages/typespec-client-generator-core/src/package.ts @@ -56,6 +56,7 @@ import { getHashForType, getLocationOfOperation, getSdkTypeBaseHelper, + isNeverOrVoidType, isNullable, updateWithApiVersionInformation, } from "./internal-utils.js"; @@ -241,14 +242,15 @@ function getSdkBasicServiceMethod< const parameters = httpOperation.parameters; // path/query/header parameters for (const param of parameters.parameters) { + if (isNeverOrVoidType(param.param.type)) continue; methodParameters.push(diagnostics.pipe(getSdkMethodParameter(context, param.param, operation))); } // body parameters - if (parameters.body?.parameter) { + if (parameters.body?.parameter && !isNeverOrVoidType(parameters.body.parameter.type)) { methodParameters.push( diagnostics.pipe(getSdkMethodParameter(context, parameters.body?.parameter, operation)) ); - } else if (parameters.body) { + } else if (parameters.body && !isNeverOrVoidType(parameters.body.type)) { if (parameters.body.type.kind === "Model") { const type = getEffectivePayloadType(context, parameters.body.type); // spread case diff --git a/packages/typespec-client-generator-core/src/types.ts b/packages/typespec-client-generator-core/src/types.ts index 8ec524d65d..401e57cce1 100644 --- a/packages/typespec-client-generator-core/src/types.ts +++ b/packages/typespec-client-generator-core/src/types.ts @@ -85,6 +85,7 @@ import { isAzureCoreModel, isMultipartFormData, isMultipartOperation, + isNeverOrVoidType, isNullable, updateWithApiVersionInformation, } from "./internal-utils.js"; @@ -1044,7 +1045,7 @@ function addPropertiesToModelType( for (const property of type.properties.values()) { if ( isStatusCode(context.program, property) || - isNeverType(property.type) || + isNeverOrVoidType(property.type) || sdkType.kind !== "model" ) { continue; @@ -1240,6 +1241,7 @@ function updateTypesFromOperation( const httpOperation = getHttpOperationWithCache(context, operation); const generateConvenient = shouldGenerateConvenient(context, operation); for (const param of operation.parameters.properties.values()) { + if (isNeverOrVoidType(param.type)) continue; const paramTypes = diagnostics.pipe(checkAndGetClientType(context, param.type, operation)); if (generateConvenient) { paramTypes.forEach((paramType) => { @@ -1248,6 +1250,7 @@ function updateTypesFromOperation( } } for (const param of httpOperation.parameters.parameters) { + if (isNeverOrVoidType(param.param.type)) continue; const paramTypes = diagnostics.pipe( checkAndGetClientType(context, param.param.type, operation) ); @@ -1258,7 +1261,7 @@ function updateTypesFromOperation( } } const httpBody = httpOperation.parameters.body; - if (httpBody) { + if (httpBody && !isNeverOrVoidType(httpBody.type)) { const bodies = diagnostics.pipe(checkAndGetClientType(context, httpBody.type, operation)); if (generateConvenient) { bodies.forEach((body) => { @@ -1282,7 +1285,7 @@ function updateTypesFromOperation( } for (const response of httpOperation.responses) { for (const innerResponse of response.responses) { - if (innerResponse.body?.type) { + if (innerResponse.body?.type && !isNeverOrVoidType(innerResponse.body.type)) { const responseBodies = diagnostics.pipe( checkAndGetClientType(context, innerResponse.body.type, operation) ); diff --git a/packages/typespec-client-generator-core/test/package.test.ts b/packages/typespec-client-generator-core/test/package.test.ts index 17cd41754b..7242bde72b 100644 --- a/packages/typespec-client-generator-core/test/package.test.ts +++ b/packages/typespec-client-generator-core/test/package.test.ts @@ -1679,6 +1679,29 @@ describe("typespec-client-generator-core: package", () => { ["apiVersion", "generationOptions", "contentType", "accept"] ); }); + + it("never void parameter or response", async () => { + await runner.compileWithBuiltInService(` + op TestTemplate< + headerType, + queryType, + bodyType, + responseHeaderType, + responseBodyType + >(@header h: headerType, @query q: queryType, @body b: bodyType): { + @header h: responseHeaderType; + @body b: responseBodyType; + }; + op test is TestTemplate; + `); + const sdkPackage = runner.context.experimental_sdkPackage; + const method = getServiceMethodOfClient(sdkPackage); + strictEqual(method.parameters.length, 0); + strictEqual(method.response.type, undefined); + strictEqual(method.operation.parameters.length, 0); + strictEqual(method.operation.responses.get(200)?.headers.length, 0); + strictEqual(method.operation.responses.get(200)?.type, undefined); + }); }); describe("Responses", () => { diff --git a/packages/typespec-client-generator-core/test/types.test.ts b/packages/typespec-client-generator-core/test/types.test.ts index 773a186666..b96d4e71b8 100644 --- a/packages/typespec-client-generator-core/test/types.test.ts +++ b/packages/typespec-client-generator-core/test/types.test.ts @@ -3282,6 +3282,26 @@ describe("typespec-client-generator-core: types", () => { ["ValidResponse"] ); }); + + it("never or void property", async () => { + await runner.compileAndDiagnose(` + @service({}) + @test namespace MyService { + @test + @usage(Usage.input | Usage.output) + @access(Access.public) + model Test{ + prop1: never; + prop2: void; + } + } + `); + + const models = runner.context.experimental_sdkPackage.models; + strictEqual(models.length, 1); + strictEqual(models[0].name, "Test"); + strictEqual(models[0].properties.length, 0); + }); }); describe("SdkArrayType", () => {