Skip to content

[csharp] Derive ModelProvider.BaseModelProvider from BaseType#10486

Draft
ArcturusZhang wants to merge 1 commit intomicrosoft:mainfrom
ArcturusZhang:fix/model-provider-base-mismatch
Draft

[csharp] Derive ModelProvider.BaseModelProvider from BaseType#10486
ArcturusZhang wants to merge 1 commit intomicrosoft:mainfrom
ArcturusZhang:fix/model-provider-base-mismatch

Conversation

@ArcturusZhang
Copy link
Copy Markdown
Member

@ArcturusZhang ArcturusZhang commented Apr 24, 2026

Fixes #10485

Problem

ModelProvider.BuildBaseType is protected virtual, but BuildBaseModelProvider was private and computed the base independently from InputModelType.BaseModel. A derived ModelProvider that overrode BuildBaseType (for example, to replace the spec inheritance chain with a hand-written base class — as the Azure mgmt/provisioning generators do) ended up with BaseType and BaseModelProvider describing different parents.

Visitors that walk BaseModelProvider then saw properties from a model that wasn't actually in the C# inheritance chain — for example, the mgmt FlattenPropertyVisitor ended up flattening the same set of properties twice.

Fix

Make BaseType the single source of truth, and derive BaseModelProvider from it.

  • TypeProvider.BaseType is now virtual.
  • ModelProvider.BaseType overrides it to handle CustomCodeView precedence (custom base wins over spec base) and the namespace-less name resolution for custom bases that Roslyn could not resolve. ModelProvider.BuildBaseType is now spec-only and no longer needs to know about CustomCodeView.
  • ModelProvider.BuildBaseModelProvider simply looks up BaseType in the TypeFactory.CSharpTypeMap.

After this change, overriding BuildBaseType (or BaseType) on a derived ModelProvider automatically keeps BaseModelProvider in sync — they cannot diverge.

Test

Added OverridingBuildBaseTypeKeepsBaseModelProviderConsistent in ModelProviderTests.cs. It defines a derived ModelProvider that overrides BuildBaseType to return an external (hand-written) base type, mirroring what downstream emitters do, and asserts that BaseModelProvider follows BaseType (becomes null because the external type is not a generated ModelProvider). Under the old code, BaseModelProvider would still have resolved to the spec base, which is exactly the bug being fixed.

All 1434 Microsoft.TypeSpec.Generator.Tests and 1312 Microsoft.TypeSpec.Generator.ClientModel.Tests pass.

@ArcturusZhang ArcturusZhang added the emitter:client:csharp Issue for the C# client emitter: @typespec/http-client-csharp label Apr 24, 2026
@azure-sdk
Copy link
Copy Markdown
Collaborator

azure-sdk commented Apr 24, 2026

You can try these changes here

🛝 Playground 🌐 Website 🛝 VSCode Extension

@ArcturusZhang ArcturusZhang force-pushed the fix/model-provider-base-mismatch branch 3 times, most recently from 9d11c17 to 8c856a0 Compare April 27, 2026 06:24
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 27, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@typespec/http-client-csharp@10486

commit: ff57562

@github-actions
Copy link
Copy Markdown
Contributor

No changes needing a change description found.

@ArcturusZhang ArcturusZhang changed the title [csharp] Make ModelProvider.BuildBaseModelProvider virtual [csharp] Derive ModelProvider.BaseModelProvider from BaseType Apr 27, 2026
@ArcturusZhang ArcturusZhang force-pushed the fix/model-provider-base-mismatch branch 3 times, most recently from 28c5da6 to 6f63aa8 Compare April 27, 2026 08:31
Fixes microsoft#10485

ModelProvider exposed two parallel APIs for the parent type:
* BaseType (via BuildBaseType, virtual on TypeProvider)
* BaseModelProvider (via BuildBaseModelProvider, private)

Their resolution paths were independent: BaseModelProvider read
`_inputModel.BaseModel` directly while BuildBaseType was virtual.
Subclasses overriding BuildBaseType (e.g. emitters that replace the
spec inheritance with a hand-written base class) ended up with the
two APIs describing different parents, and visitors that walked
BaseModelProvider saw properties from a model that was not in the
C# inheritance chain.

Make BaseType the single source of truth:

* Move CustomCodeView precedence and the empty-namespace name
  resolution into ModelProvider.BuildBaseType, so BaseType already
  reflects the customized/spec/overridden parent as a CSharpType.
* Reduce BuildBaseModelProvider to a CSharpTypeMap lookup of BaseType.
  When BaseType is a non-generated type (subclass override, system
  type, hand-written external base) the lookup naturally misses and
  BaseModelProvider is null - which is what callers want, since they
  use it to walk generated parent properties/fields/etc.

Adds a regression test that overrides BuildBaseType to point at an
external type and asserts BaseModelProvider stays consistent.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ArcturusZhang ArcturusZhang force-pushed the fix/model-provider-base-mismatch branch from 6f63aa8 to ff57562 Compare April 27, 2026 09:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

emitter:client:csharp Issue for the C# client emitter: @typespec/http-client-csharp

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[csharp] ModelProvider.BaseType and BaseModelProvider can return mismatched values

2 participants