fix(http-server-csharp): emitter type errors - #11749
Conversation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
All changed packages have been documented.
Show changes
|
commit: |
There was a problem hiding this comment.
Pull request overview
This PR fixes several correctness issues in the @typespec/http-server-csharp emitter that surfaced when generating controllers/models from a larger real-world spec (nullable handling, multipart fallback behavior, error model ctor typing, operation canonicalization/service namespace resolution, controller call argument ordering, and models-only output). It also updates snapshots and adds targeted tests to prevent regressions.
Changes:
- Fixes nullable emission across models, interfaces, mocks, controllers, and error constructors (including avoiding invalid
T??). - Improves service discovery/namespace selection and introduces an operation-source map to better align controller generation with business/interface operations.
- Adds/updates tests and snapshots for models-only output, multipart fallback behavior, and controller/action response analysis.
Reviewed changes
Copilot reviewed 25 out of 28 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/http-server-csharp/test/snapshots/sample-service/generated/models/PetListResult.cs | Snapshot update: nullable reference type for optional nextLink. |
| packages/http-server-csharp/test/snapshots/sample-service/generated/models/Pet.cs | Snapshot update: nullable reference types for optional properties. |
| packages/http-server-csharp/test/snapshots/sample-service/generated/controllers/PetsController.cs | Snapshot update: DELETE returns 204 NoContent without assigning void result. |
| packages/http-server-csharp/test/nullable-parameters.test.ts | New regression test ensuring optional nullable value params don’t emit T??. |
| packages/http-server-csharp/test/generation.test.ts | Updates expectations for nullable reference types and error ctor optional nullables. |
| packages/http-server-csharp/test/emitter.test.ts | Adds test verifying output-type=models emits only models/support files. |
| packages/http-server-csharp/src/utils/namespace-utils.ts | Removes findServiceNamespace (service resolution moved to service-discovery). |
| packages/http-server-csharp/src/service-resolution.ts | Adds options to skip canonicalization; tracks canonical op → source operation map; uses declared service namespace. |
| packages/http-server-csharp/src/service-resolution.test.ts | Adds tests for declared service namespace precedence, operation source tracking, and canonicalization skipping. |
| packages/http-server-csharp/src/service-discovery.ts | Introduces getServiceNamespace and uses compiler helper for full namespace name. |
| packages/http-server-csharp/src/emitter.tsx | Implements models-only emission path; wires operation source context; avoids emitting mocks/project/docs in models-only mode. |
| packages/http-server-csharp/src/context/operation-source-context.ts | New context for canonical operation → source operation mapping. |
| packages/http-server-csharp/src/components/type-expression/type-expression.tsx | Adds helper for detecting nullable value-type unions; uses it in union type rendering. |
| packages/http-server-csharp/src/components/scaffolding/mock-implementations.tsx | Multipart fallback without canonical metadata; fixes nullable value union parameter emission in mocks. |
| packages/http-server-csharp/src/components/render-root.tsx | Detects multipart based on decorators when canonicalization is unavailable. |
| packages/http-server-csharp/src/components/multipart-fallback.test.tsx | New test ensuring multipart interfaces/mocks stay aligned without canonical metadata. |
| packages/http-server-csharp/src/components/models/models.tsx | Adjusts nullable semantics for properties; improves JsonNodes detection for inherited error models. |
| packages/http-server-csharp/src/components/models/model-helpers.ts | Enhances JsonNodes detection to recurse into tuples/arrays/records and optionally include inheritance. |
| packages/http-server-csharp/src/components/models/error-models.tsx | Uses TypeExpression for structured ctor param types; fixes optional/nullability behavior in error ctors. |
| packages/http-server-csharp/src/components/models/error-models.test.tsx | New tests for structured ctor parameter typing and inherited JsonObject using. |
| packages/http-server-csharp/src/components/interfaces/interfaces.tsx | Multipart fallback via decorators; fixes optional nullable value union parameters (avoids T??). |
| packages/http-server-csharp/src/components/interfaces/interfaces.test.tsx | New tests for nullable suffix correctness and multipart fallback behavior. |
| packages/http-server-csharp/src/components/controllers/controllers.tsx | Passes operation source into ControllerAction to improve call-site alignment. |
| packages/http-server-csharp/src/components/controllers/controllers.test.tsx | Adds controller ordering test using operation source context. |
| packages/http-server-csharp/src/components/controller-action/response-analysis.ts | Excludes error branches from success-response analysis; returns 204 when success is void. |
| packages/http-server-csharp/src/components/controller-action/controller-action.tsx | Reorders call args to match business/interface signature; updates ProducesResponseType/body handling for void unions. |
| packages/http-server-csharp/src/components/controller-action/controller-action.test.tsx | Adds tests for void-success unions, preserving value-success behavior, and argument ordering for request/protocol params. |
| .chronus/changes/sramsey-csharp-server-nullable-2026-7-12-14-45-16.md | Changelog entry for the emitter fixes. |
Suppressed comments (3)
packages/http-server-csharp/src/components/controller-action/controller-action.tsx:61
- Controller action parameters can still emit
int??forvalue?: int32 | nullbecauseTypeExpressionreturnsint?andoptional: trueadds another?. Passing the inner value type toTypeExpression(only when the parameter is optional and the union is a nullable value union) avoids the duplicate suffix.
type: <TypeExpression type={p.property.sourceType.type} />,
packages/http-server-csharp/src/components/controller-action/controller-action.tsx:72
- Same
T??risk exists for optional nullable value unions on query/header parameters;TypeExpressionemitsT?forT | nulland theoptionalflag adds another?. Apply the same inner-type substitution here to keep controller method signatures valid.
type: <TypeExpression type={p.property.sourceType.type} />,
packages/http-server-csharp/src/components/controllers/controllers.test.tsx:142
- The expected controller implementation calls
GetPetAsync(feature, petId, apiVersion), but the expected interface signature just above isGetPetAsync(string petId, string feature, string apiVersion). This would not compile as generated code; the argument order in the expected output should match the interface signature.
var result = await PetStoreImpl.GetPetAsync(feature, petId, apiVersion);
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| import * as cs from "@alloy-js/csharp"; | ||
| import { Attribute } from "@alloy-js/csharp"; | ||
| import { isErrorModel, isVoidType } from "@typespec/compiler"; | ||
| import { isErrorModel, isVoidType, type Operation } from "@typespec/compiler"; |
| function findServiceNs(ns: TspNamespace): TspNamespace | undefined { | ||
| for (const child of ns.namespaces.values()) { | ||
| if (isStdNamespace(child)) continue; | ||
| // If this namespace has content (models, interfaces, operations, enums), use it | ||
| // Otherwise, recurse deeper | ||
| const hasContent = | ||
| child.models.size > 0 || |
|
|
||
| expect( | ||
| <Wrapper> | ||
| <OperationSources.Provider value={new Map([[canonOp, businessGetPet]])}> |
|
You can try these changes here
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 26 out of 29 changed files in this pull request and generated no new comments.
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
packages/http-server-csharp/src/components/controller-action/controller-action.tsx:173
responseStatusCodefor[ProducesResponseType]is currently derived only fromhasBody, but the method body can returnAccepted(...)whenstatusCode === 202(andStatusCode(statusCode, ...)for other non-200 codes). This can cause the emitted attribute to advertiseOK/NoContenteven when the action returns 201/202, which makes the generated metadata inaccurate.
// Determine the success status code from the response
const { statusCode, hasBody } = getSuccessStatusCode($.program, props.operation);
// Determine response type for ProducesResponseType attribute
const returnType = props.operation.sourceType.returnType;
const responseStatusCode = hasBody ? "OK" : "NoContent";
let responseTypeExpr: Children | undefined = undefined;
Addresses errors I encountered while using the C# server emitter to emit code from the ai foundry spec for the agent contracts api service package. Includes tests
Issues addressed:
int??toint?)<Unresolved Symbol ...>by falling back to the original@multipartBodymetadata when canonicalization data is unavailableRecord,Array, orobjectinstead of the same concrete structured types used by their propertiesvoid | @errorresponses generating invalid result assignments by excluding error branches from success-response analysis and emitting direct awaits withNoContent()| nulloutput-type:modelswas emitting controllers and responses, and it now emits models only