microsoft / microsoft/typespec

[csharp] Generated back-compat overloads in ModelFactory can have non-compiling bodies after large signature changes

Open
#10,595 3 comments 0 reactions 1 assignee Claimed by @ArcturusZhang View on GitHub
bug emitter:client:csharp
Dominant language
Java
Stars
5.9k
Forks
394
Avg merge
1d 23h
Merged PRs (30d)
104

Description

## Bug

When generating C# back-compat overloads in `*ModelFactory` based on the previously-shipped contract, the generator can emit overloads whose **body does not compile** — the `return` expression forwards to the primary overload using arguments that are inconsistent with that primary overload's current parameter list (wrong types, missing/renamed parameters, named-then-positional ordering, etc.).

The signatures themselves are fine; only the bodies are wrong.

Encountered while migrating `Azure.ResourceManager.Compute` (Swagger → TypeSpec). After regeneration we observed **96 raw / 48 unique** compilation errors across **13 distinct factory methods**, all in EditorBrowsable(Never) back-compat overloads:

| C# error | Count | Example |
|---|---:|---|
| CS1739 (parameter name doesn't exist) | 78 | `'RestorePointData' has no parameter 'instantAccessDurationMinutes'` |
| CS1503 (cannot convert) | 12 | `cannot convert 'GalleryImageIdentifier' to 'SharedGalleryIdentifier'` |
| CS1744 (positional after named) | 6 | back-compat call passes `reservationType: default` then a positional arg |

100% of the broken methods carry `[EditorBrowsable(EditorBrowsableState.Never)]` (i.e. they are auto-generated back-compat shims, not user-authored code).

## Reproducer

`Azure.ResourceManager.Compute` migration branch (will share repo + branch on request).

Representative case — `CapacityReservationGroupData`:

**Primary overload (current generated, valid):**
```csharp
public static CapacityReservationGroupData CapacityReservationGroupData(
ResourceIdentifier id = default, string name = default, ResourceType resourceType = default, SystemData systemData = default,
IDictionary tags = default, AzureLocation location = default,
IEnumerable zones = default,
IEnumerable capacityReservations = default,
IEnumerable virtualMachinesAssociated = default,
CapacityReservationGroupInstanceView instanceView = default,
IEnumerable sharingSubscriptionIds = default,
CapacityReservationType? reservationType = default)
{ ... }
```

**Back-compat overload emitted by generator (does not compile):**
```csharp
[EditorBrowsable(EditorBrowsableState.Never)]
public static CapacityReservationGroupData CapacityReservationGroupData(
ResourceIdentifier id, string name, ResourceType resourceType, SystemData systemData,
IDictionary tags, AzureLocation location,
IEnumerable zones,
IEnumerable capacityReservations,
IEnumerable virtualMachinesAssociated,
CapacityReservationGroupInstanceView instanceView,
IEnumerable sharingSubscriptionIds)
{
return CapacityReservationGroupData(
id, name, resourceType, systemData, tags, location,
capacityReservations, virtualMachinesAssociated, instanceView,
reservationType: default, // CS1744 follows ↓
sharingSubscriptionIds, zones); // positional after named
}
```

Three distinct issues in this single body:
1. `zones` is passed last; in the primary it's the 7th parameter (after `location`).
2. `reservationType: default` is a named argument followed by positional arguments.
3. Several other methods (`RestorePointData`, `ManagedDiskData`, `CapacityReservationData`, `SharedGalleryImageData`, `SnapshotData`, `AvailabilitySetData/Patch`, `GalleryData/ImageData`, `ManagedDiskPatch`, `CapacityReservationGroupPatch`, `CapacityReservationPatch`) follow the same pattern with renamed/added parameters that the back-compat call site doesn't account for.

## Expected

For back-compat overload generation, **only emit the overload when the generator can produce a valid forwarding body.** If the parameter list of the current primary diverges in a way the generator can't reconcile (renames, type changes, removed parameters), it is more useful to **omit the overload** and let the SDK author add a hand-written back-compat shim, than to emit code that does not compile.

A "compiles or skips" guarantee on emitted back-compat overloads would dramatically reduce the manual cleanup cost during large migrations.

## Workaround used today

Exclude the entire generated `ArmComputeModelFactory.cs` from compilation via `` plus `[CodeGenSuppress]` for the affected overloads, and hand-author replacements in customization code. Both have to be re-evaluated each regen.

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.