-
Notifications
You must be signed in to change notification settings - Fork 344
Description
Bug Description
TypeFactory.CreateCSharpType crashes with System.ArgumentException: An item with the same key has already been added when processing self-referencing models — models that contain an array property of their own type (e.g. QueryFilter with property and: QueryFilter[]).
Root Cause
CreateCSharpType caches resolved types in TypeCache (a Dictionary<InputType, CSharpType?>). When it encounters a self-referencing model:
- Outer call:
CreateCSharpType(QueryFilter)→ cache miss → callsCreateCSharpTypeCore CreateCSharpTypeCore→CreateModel→BuildProperties→ encountersand: QueryFilter[]- Inner (re-entrant) call:
CreateCSharpType(QueryFilter)→ cache miss again (outer hasn't added yet) →CreateCSharpTypeCore→ resolves →TypeCache.Add(QueryFilter, ...)succeeds - Outer call resumes: tries
TypeCache.Add(QueryFilter, ...)→ 💥 throws because the key was already added by the inner call
Affected Models (example from Azure CostManagement)
QueryFilter(properties:and: QueryFilter[],or: QueryFilter[])ForecastFilter(same pattern)ReportConfigFilter(same pattern)ErrorDetail(property:details: ErrorDetail[])
Any ARM/data-plane service with recursive models will trigger this crash.
Proposed Fix
One-line change in TypeFactory.cs line 54: replace TypeCache.Add(inputType, type) with TypeCache[inputType] = type.
The indexer assignment is idempotent — when the inner call adds the key first, the outer call simply overwrites with the same value. This is safe because both calls resolve the same InputType to the same CSharpType.
- TypeCache.Add(inputType, type);
+ // Use indexer instead of Add() to handle re-entrant calls for self-referencing models.
+ TypeCache[inputType] = type;File: packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/TypeFactory.cs
Regression Testing
Validated on commit 6c7036779 (main) + fix:
| Test Suite | Passed | Failed |
|---|---|---|
| Generator.Tests | 1,274 | 0 |
| ClientModel.Tests | 1,216 | 0 |
| Input.Tests | 88 | 0 |
| Local.Tests (Sample) | 37 | 0 |
| Spector.Tests | 15 (843 skipped) | 0 |
| Regen check (63 specs) | 63 | 0 |
| Git diff after regen | Zero diff | — |
Reproduction
- Create a TypeSpec model with a self-referencing array property:
model QueryFilter { and?: QueryFilter[]; or?: QueryFilter[]; }
- Run the C# emitter/generator against it
- Observe the
ArgumentExceptioncrash inTypeFactory.CreateCSharpType