Minimal reproduction of a bug in
@typespec/http-server-csharp
that emits a non-compilable C# constructor when an @error model has
a property whose type is a numeric literal (e.g. status: 404).
| Package | Version |
|---|---|
@typespec/compiler |
1.12.0 |
@typespec/http |
1.12.0 |
@typespec/rest |
0.82.0 |
@typespec/http-server-csharp |
0.58.0-alpha.28 |
| Node | >=22 |
| .NET SDK | 10.x (any LTS works) |
Pinned exactly — no caret/tilde.
# 1. Install (uses corepack-shipped pnpm 10.10.0)
corepack pnpm install
# 2. Emit
corepack pnpm exec tsp compile main.tsp
# 3. Build the emitted C#
dotnet build Repro.csprojOr in one go:
corepack pnpm run reproExpected outcome (today, alpha.28):
tsp-output\@typespec\http-server-csharp\generated\models\NotFoundProblem.cs(15,53):
error CS1763: "status" is of type "object". A default parameter value of a
reference type other than string can only be initialized with null.
import "@typespec/http";
import "@typespec/rest";
using Http;
using Rest;
@service(#{ title: "Repro" })
namespace Repro;
// Control: plain model with the same literal pattern — emits cleanly.
model Config {
name: string;
version: 1;
}
// Trigger: @error model with a numeric-literal-typed property.
@error
model NotFoundProblem {
title: string;
status: 404;
}
@route("/config")
interface ConfigApi {
@get
@route("{id}")
get(@path id: string): Config | NotFoundProblem;
}The plain model Config with the literal version: 1 is emitted
idiomatically: a read-only int property with the literal value as
its initializer. No constructor is generated for a non-@error model.
public partial class Config
{
public string Name { get; set; }
public int Version { get; } = 1; // ✅ legal
}public partial class NotFoundProblem : HttpServiceException
{
public NotFoundProblem(string title, object status = 404) : base(400, // ← CS1763
value: new { title = title, status = status })
{
Title = title;
Status = status; // ← would also fail (CS0029) but CS1763 fires first
}
public string Title { get; set; }
public int Status { get; } = 404; // and the property is `int`, not `object` —
// the type mapping is inconsistent within the same file
}Two things are wrong here:
- CS1763.
object x = <non-string non-null literal>is not a legal default-parameter expression in C#. The language rule limits reference-type defaults tonull(andstringdefaults to string-literal). The emitter types the constructor parameter asobjectbut supplies the integer literal as the default — a contradiction. - Inconsistent intra-class typing. The auto-generated property
public int Status { get; } = 404;correctly maps the literal404to a non-nullableint. The constructor signature for the same property isobject status = 404. Even if CS1763 didn't trip, the constructor bodyStatus = statuswould fail with CS0029 (no implicitobject→int).
A correct mapping would emit either of:
// Option A — drop the literal-ness, treat as int with a default
public NotFoundProblem(string title, int status = 404) : base(404, ...)
// Option B — fold the literal away from the constructor signature
public NotFoundProblem(string title) : base(404, ...) { ... }@error causes the emitter to generate an all-properties constructor on
the derived HttpServiceException so the model can be thrown directly.
Non-@error models don't get such a constructor, so the
numeric-literal-as-default-parameter code path never runs for them.
Replace the numeric literal type with the base scalar type:
@error
model NotFoundProblem {
title: string;
- status: 404;
+ status: int32;
}The trade-off: the TypeSpec source loses the per-subtype status discriminator the API contract intended. Downstream OpenAPI consumers no longer see „this variant means HTTP 404".
- #5024 — literal type is not properly generated
(closed 2024-11-26): a sibling symptom for string-literal
contentTypeheader parameters. Comment by@markcowl: „yeah, this now requires doing real processing of ValueType". Fix landed only for that code path; the@errorconstructor path remained unfixed. - #2240 — Some float value would map to int in enum values
(open since May 2024,
design:needed): same family — numeric-literal handling in the C# emitter is unreliable. - #10603 — JsonStringEnumConverter on nullable string properties
(open, May 2026,
alpha.27): unrelated symptom, same emitter, same alpha version family. - #10372 — Http-server-csharp alloy rewrite
(open PR): full rewrite of the C# emitter using
alloy. May change or resolve this bug — no merge date yet.
main.tsp— TypeSpec source, ~20 lines.tspconfig.yaml— emitter config.package.json— exact version pins.Repro.csproj— minimal SDK-styleMicrosoft.NET.Sdklibrary (targeting net10.0) that the .NET implicit Compile pattern picks up the emittedtsp-output/**/*.csfrom.