microsoft / microsoft/typespec
[CSharp] ClientProvider.BuildApiVersionFields produces duplicate field names for multi-service clients
- Dominant language
- Java
- Stars
- 5.9k
- Forks
- 394
- Avg merge
- 1d 23h
- Merged PRs (30d)
- 104
Description
## Bug Report
### Description
When a multi-service client is created via `@client({ service: [ServiceA, ServiceB] })`, `ClientProvider.BuildApiVersionFields()` can produce duplicate field names, causing `BuildMethods()` to crash with:
```
An item with the same key has already been added. Key: serviceTestsApiVersion
at Dictionary2.Add(TKey key, TValue value)
at Enumerable.ToDictionary(...)
at ClientProvider.BuildMethods()
```
### Impact
This crashes the **Azure management plane generator** (`@azure-typespec/http-client-csharp-mgmt`) when generating multi-service SDKs such as Azure Compute (which combines 4 services via `@client({ service: [Compute, ComputeDisk, ComputeGallery, ComputeSku] })`).
The mgmt generator does **not** use the `ClientProvider` or its API version fields at all — it replaces them with its own ARM resource-based client hierarchy. However, the crash occurs during `ClientProvider.BuildMethods()` before the mgmt generator gets a chance to override it, so the mgmt generator cannot work around this without patching the base `ClientProvider`.
### Root Cause
In `ClientOptionsProvider.BuildVersionProperties()`, the property name for each service version is computed via:
```csharp
string name = _inputClient.IsMultiServiceClient
? ClientHelper.BuildNameForService(inputEnumType.Namespace, "Service", "ApiVersion")
: "Version";
```
When two services have namespaces that reduce to the same identifier (e.g. both share a common namespace segment from the C# output namespace), `BuildNameForService` produces the same property name for both services.
Then in `ClientProvider.BuildApiVersionFields()`, when `VersionProperties.Count > 1`:
```csharp
string name = (count > 1) ? ("_" + propertyProvider.Name.ToVariableName()) : text;
```
Both services get the same field name (e.g. `_serviceTestsApiVersion`), and `Fields.ToDictionary()` in `BuildMethods()` crashes on the duplicate key.
### Reproduction
Two ARM services combined via `@client`, with the output C# namespace causing name collisions:
**ServiceOne/main.tsp:**
```typespec
@armProviderNamespace
@service(#{ title: "ServiceOne" })
@versioned(ServiceOne.Versions)
namespace ServiceOne;
enum Versions { v2024_01_01: "2024-01-01" }
// ... resource definition ...
```
**ServiceTwo/main.tsp:**
```typespec
@armProviderNamespace
@service(#{ title: "ServiceTwo" })
@versioned(ServiceTwo.Versions)
namespace ServiceTwo;
enum Versions { v2024_06_01: "2024-06-01" }
// ... resource definition ...
```
**client.tsp:**
```typespec
@client({ name: "MultiServiceClient", service: [ServiceOne, ServiceTwo] })
namespace MultiServiceCombine;
```
**tspconfig.yaml** with namespace `Azure.Generator.MgmtTypeSpec.MultiService.Tests`
### Expected Behavior
Each service should get a unique field name for its API version field, even when namespace segments collide.
### Actual Behavior
Both services produce the same field name `serviceTestsApiVersion`, causing `ToDictionary` to crash.
Contributor guide
Assessment
This issue has not been assessed yet.