microsoft / microsoft/typespec

[Bug]: [http-server-csharp] `@error` model with a numeric-literal property emits a CS1763-invalid constructor (object x = N default)

Open
#10,713 1 comment 1 reaction 2 assignees Claimed by @markcowl View on GitHub
bug emitter:service:csharp triaged:core
Dominant language
Java
Stars
5.9k
Forks
394
Avg merge
1d 23h
Merged PRs (30d)
104

Description

### Describe the bug

### Summary

When an `@error` model has a property whose type is a non-string literal (e.g. `status: 404`), `@typespec/http-server-csharp` emits a constructor whose default parameter is typed `object` with the literal as its default value:

```csharp
public NotFoundProblem(string title, object status = 404) : base(400, ...)
```

This violates C# language rule [CS1763](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/cs1763): a default parameter of a reference type other than `string` may only be initialised with `null`. The generated file does not compile.

Within the same generated class the property itself is correctly typed `int`:

```csharp
public int Status { get; } = 404;
```

— so the in-class typing is inconsistent even before CS1763 fires.

### Versions

| 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.x` / `24.x` (both confirmed) |
| .NET SDK | `10.0.203` (any LTS) |

### Trigger

The bug requires **all three**:
1. A `model` decorated with `@error`.
2. A property on that model whose type is a non-string literal (numeric or boolean).
3. An `op` that references the `@error` model in its return type so the emitter actually emits it.

Plain models (without `@error`) emit literal-typed properties cleanly as read-only properties — only `@error` triggers the all-fields-constructor code path that produces the broken signature.

### Minimal repro source

```typespec
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;
}
```

### What gets emitted

`Config.cs` — **the control, clean**:

```csharp
public partial class Config
{
public string Name { get; set; }
public int Version { get; } = 1; // ✅ legal
}
```

`NotFoundProblem.cs` — **the bug**:

```csharp
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)
// if CS1763 weren't first
}
public string Title { get; set; }

public int Status { get; } = 404; // property is `int`, not `object` —
// inconsistent intra-class typing
}
```

### Build error

```
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.
```

(Column 53 is the `= 404` token.)

### Expected behaviour

One of:

- Drop the literal-ness from the constructor signature and use the base scalar:
```csharp
public NotFoundProblem(string title, int status = 404) : base(404, ...)
```
- Or fold the literal-typed property out of the constructor signature entirely (it is already pinned to `404` via the property initialiser):
```csharp
public NotFoundProblem(string title) : base(404, ...) { ... }
```

Either is valid C# and preserves the TypeSpec contract.

### Workaround

Replace the literal with the base scalar type:

```diff
@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. RFC-9457-style problem-detail subtypes ("this variant always means HTTP 404") cannot be expressed.

### Reproduction

The Playground does not run `@typespec/http-server-csharp`, so a standalone repo is needed: **https://github.com/TKI-Chemnitz/typespec-http-server-csharp-cs1763-repro**.

The repo contains exactly the minimal pieces:
- `main.tsp` — ~20 lines (control model + `@error` trigger model + one `op`).
- `package.json` — exact version pins (no caret/tilde).
- `pnpm-lock.yaml` — lockfile committed.
- `tspconfig.yaml` — minimal, only the C# server emitter.
- `Repro.csproj` — net10.0 library, `ImportDirectoryBuildProps=false`, `FrameworkReference Microsoft.AspNetCore.App`.

To reproduce:

```bash
corepack pnpm install
corepack pnpm exec tsp compile main.tsp
dotnet build Repro.csproj
```

Or in one go:

```bash
corepack pnpm run repro
```

Expected output: `dotnet build` exits with one CS1763 error pointing at `tsp-output/@typespec/http-server-csharp/generated/models/NotFoundProblem.cs(15,53)`.

### Checklist

- [x] Follow our [Code of Conduct](https://github.com/microsoft/typespec/blob/main/CODE_OF_CONDUCT.md)
- [x] Check that there isn't already an issue that request the same bug to avoid creating a duplicate.
- [x] Check that this is a concrete bug. For Q&A open a [GitHub Discussion](https://github.com/Microsoft/typespec/discussions).
- [x] The provided reproduction is a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) of the bug.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.