Skip to content

Fix system model base provider resolution - #11585

Merged
Wei Hu (live1206) merged 1 commit into
microsoft:mainfrom
live1206:fix/system-object-base-provider
Aug 11, 2026
Merged

Fix system model base provider resolution#11585
Wei Hu (live1206) merged 1 commit into
microsoft:mainfrom
live1206:fix/system-object-base-provider

Conversation

@live1206

@live1206 Wei Hu (live1206) commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Ensure MTG creates and resolves the corresponding input base model provider when a SystemObjectModelProvider wraps a framework type with an existing CLR base type.

SystemObjectModelProvider.BuildBaseType() prefers SystemType.BaseType, so the normal ModelProvider.BuildBaseType() path does not create the declared TypeSpec base model. BuildBaseModelProvider() previously only checked CSharpTypeMap, which could leave the base provider null depending on model creation order.

The fallback now:

  • Creates the declared input base model when no mapped provider exists.
  • Verifies that its generated type matches the resolved base type by name.
  • Registers the resolved CSharpType alias for subsequent lookups.

Added a regression test using ArgumentException and SystemException to cover both automatic system base-provider creation and framework/non-framework CSharpType equivalence.

Contributes to #10787.

Validation

  • SystemObjectModelProviderTests: 27 passed
  • Full npm run test:generator
  • Azure management generator with local MTG project references and its base-provider visitor workaround removed:
    • 269 management generator tests passed
    • Full management test-project regeneration passed, including Mgmt-TypeSpec-MultiService

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@pkg-pr-new

pkg-pr-new Bot commented Aug 7, 2026

Copy link
Copy Markdown

Open in StackBlitz

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

commit: 723f8df

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

No changes needing a change description found.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes base model provider resolution for SystemObjectModelProvider when the wrapped framework type has an existing CLR base type, ensuring the corresponding declared TypeSpec base model provider is created/resolved and registered for subsequent lookups.

Changes:

  • Updates ModelProvider.BuildBaseModelProvider() to fall back to creating the declared input base model provider when no mapped provider exists for the resolved CLR base type, and to register a CSharpTypeMap alias when the types match by name.
  • Adds a regression test covering automatic system base-provider creation across an input model hierarchy (ArgumentException / SystemException).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs Adds a fallback that creates the declared input base model provider and aliases the resolved CLR base CSharpType to it when they match by name.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/SystemObjectModelProviderTests.cs Adds a regression test verifying base system providers are created/resolved from the input model inheritance chain.

@live1206

Copy link
Copy Markdown
Contributor Author

regen: Azure/azure-sdk-for-net#61816

@live1206
Wei Hu (live1206) added this pull request to the merge queue Aug 11, 2026
Merged via the queue into microsoft:main with commit 825454a Aug 11, 2026
29 checks passed
@live1206
Wei Hu (live1206) deleted the fix/system-object-base-provider branch August 11, 2026 02:25
Wei Hu (live1206) added a commit to live1206/azure-sdk-for-net that referenced this pull request Aug 11, 2026
Use the base generator build containing microsoft/typespec#11585 and remove the management generator fallback.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4e218381-df47-465e-bef4-9580c3c08bbd
pull Bot pushed a commit to jrcribb/cadl that referenced this pull request Aug 14, 2026
…k types (microsoft#11678)

Fixes microsoft#11676

## Root cause

Not PR microsoft#11585 (the auto-triage hypothesis in the issue). The real cause
is framework-type resolution.

`TypeFactory.CreateFrameworkType` ultimately falls back to
`Type.GetType(fullyQualifiedTypeName)`. An unqualified `Type.GetType`
only probes **corlib and the assembly that declares the calling method**
— here `Microsoft.TypeSpec.Generator.dll`, which does not reference
`System.ClientModel`. So `"System.ClientModel.FileBinaryContent"` always
resolved to `null`. (This is the same reason `System.BinaryData`,
`System.Uri`, `System.Text.Json.JsonElement` and `System.Net.IPAddress`
already needed hard-coded special cases there.)

Verified that this is not fixable by moving the call or by adding
metadata references:

```
compile-time reference resolves: System.ClientModel.FileBinaryContent
assembly loaded: System.ClientModel
Type.GetType from referencing assembly: <null>
Assembly.GetType on SCM assembly: System.ClientModel.FileBinaryContent
```

Even from an assembly that directly references *and has loaded*
`System.ClientModel`, `Type.GetType` returns `null`.
`CodeModelGenerator.AdditionalMetadataReferences` does not help either —
that is a Roslyn/symbol-binding concern and has no effect on CLR
reflection.

### How that produced the reported symptoms

1. `TypeSymbolExtensions.GetCSharpType` calls `CreateFrameworkType`,
gets `null`, and builds a symbol-backed `CSharpType { Name =
"FileBinaryContent", Namespace = "System.ClientModel", IsFrameworkType =
false }`.
2. `ModelProvider.BuildProperties` back-compat handling sees
`!lastContractPropertyType.Equals(outputProperty.Type)`
(framework-backed vs symbol-backed), concludes the contract changed, and
overwrites the property type with the symbol-backed look-alike.
3. `ScmModel.IsFileBinaryContentType` then returns `false`, so:
- `BuildMultipartFileConstructors` returns `null` → the `string` /
`Stream` / `BinaryData` convenience constructors disappear
- the `[Experimental("SCME0004")]` attributes on the constructor,
property and model-factory method are dropped, along with `using
System.Diagnostics.CodeAnalysis;`
- `MultipartFormDataSerializationDefinition.BuildScalarAdd` picks the
model `Add<T>` overload instead of the `FileBinaryContent` one

### Why it looked intermittent

The bad path only runs when a last contract is actually resolved
(`SourceInputModel.FindForTypeInLastContract`). Runs without a
last-contract assembly available never hit it, which is why
byte-identical inputs produced different output across CI runs. The
`dotnet msbuild` version delta noted in the issue is a proxy for that,
not the cause.

## Fix

Override `CreateFrameworkType` in `ScmTypeFactory` and fall back to
`Assembly.GetType` scoped to the `System.ClientModel` assembly:

```csharp
protected override Type? CreateFrameworkType(string fullyQualifiedTypeName)
    => base.CreateFrameworkType(fullyQualifiedTypeName)
        ?? typeof(BinaryContent).Assembly.GetType(fullyQualifiedTypeName);
```

This is deterministic (the assembly is a compile-time reference of
`Microsoft.TypeSpec.Generator.ClientModel`) and fixes every
`System.ClientModel` type, not just `FileBinaryContent`. Deliberately
avoided broad probing such as `AppDomain.CurrentDomain.GetAssemblies()`,
which would reintroduce load-order nondeterminism — exactly the class of
problem this issue is about.

## Tests

Added
`TestMultipartFormDataModel_LastContractFileType_KeepsFileBinaryContentFrameworkType`,
which loads a last contract declaring `public FileBinaryContent
ProfileImage { get; }` and asserts the property stays a framework type,
that `IsFileBinaryContentType` recognizes it, and that the emitted model
is byte-identical to the no-last-contract output. It fails on `main`
with `Expected: True But was: False` and passes with this change.

## Validation

- `Microsoft.TypeSpec.Generator.Tests` — 1895/1895 passed
- `Microsoft.TypeSpec.Generator.ClientModel.Tests` — 1571/1571 passed
- `eng/scripts/Generate.ps1` — regenerated all libraries, no output
drift
- `npm run cop` — `cop checks passed.`

## Follow-up

`Azure.Generator` in `Azure/azure-sdk-for-net` derives from
`ScmTypeFactory`, so it picks up `System.ClientModel` resolution from
this change automatically. It will still need the equivalent one-line
fallback for its own `Azure.Core` / `Azure.ResourceManager` types.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 33840ead-d95e-4c5d-91eb-8765e25089bc
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.

4 participants