Skip to content

TypeFactory.CreateCSharpType crashes on self-referencing models (re-entrant Dictionary.Add) #10031

@haiyuazhang

Description

@haiyuazhang

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:

  1. Outer call: CreateCSharpType(QueryFilter) → cache miss → calls CreateCSharpTypeCore
  2. CreateCSharpTypeCoreCreateModelBuildProperties → encounters and: QueryFilter[]
  3. Inner (re-entrant) call: CreateCSharpType(QueryFilter) → cache miss again (outer hasn't added yet) → CreateCSharpTypeCore → resolves → TypeCache.Add(QueryFilter, ...) succeeds
  4. 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

  1. Create a TypeSpec model with a self-referencing array property:
    model QueryFilter {
      and?: QueryFilter[];
      or?: QueryFilter[];
    }
  2. Run the C# emitter/generator against it
  3. Observe the ArgumentException crash in TypeFactory.CreateCSharpType

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions