Skip to content

[http-client-csharp] Preserve last-contract model base types - #11818

Open
Wei Hu (live1206) wants to merge 18 commits into
microsoft:mainfrom
live1206:test/11816-last-contract-base-type
Open

[http-client-csharp] Preserve last-contract model base types#11818
Wei Hu (live1206) wants to merge 18 commits into
microsoft:mainfrom
live1206:test/11816-last-contract-base-type

Conversation

@live1206

@live1206 Wei Hu (live1206) commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Summary

  • preserve a model's previously shipped CLR base when the current base hierarchy no longer contains it
  • retain a newer immediate base when it already derives from the last-contract base
  • resolve generated, referenced, nested, and constructed-generic preserved bases against the current build using complete CLR metadata identity
  • continue resolution past unusable cached placeholders to find a usable current-compilation base
  • keep properties shipped on the previous GA base inherited without duplicating them on the derived model
  • keep an explicit custom base authoritative when restoring the GA base would conflict across partial declarations
  • skip restoration when constructor chaining, direct property collisions, or the current discriminator hierarchy would make the generated C# invalid

This intentionally does not materialize properties introduced only by a displaced, newer TypeSpec base. That broader reconciliation can be added when an SDK use case requires it.

Fixes #11816

Validation

  • Generated, referenced, multi-level nested, and constructed-generic base regressions verify the previous CLR inheritance relationship is restored and the correct metadata identity is selected.
  • The promoted-base regression verifies a current base that already derives from the previous base is retained.
  • The inherited-property regression verifies GA base properties remain available and are not duplicated on the derived model.
  • Custom-base and constructor regressions verify MTG retains the current base and emits a diagnostic when restoring the previous base would produce invalid C#.
  • Generated and symbol-backed property-collision regressions verify final back-compatible CLR names and compile the resulting model hierarchies.
  • The discriminator regression verifies restoration does not detach a model from its current polymorphic hierarchy and compiles the generated deserializer.

@microsoft-github-policy-service microsoft-github-policy-service Bot added the emitter:client:csharp Issue for the C# client emitter: @typespec/http-client-csharp label Sep 2, 2026
@pkg-pr-new

pkg-pr-new Bot commented Sep 2, 2026

Copy link
Copy Markdown

Open in StackBlitz

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

commit: c0d3c88

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

No changes needing a change description found.

@live1206

Copy link
Copy Markdown
Contributor Author

Regen: Azure/azure-sdk-for-net#62636
The diff of model factory is from #11703

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.

🔵 Needs a closer look

It changes core model inheritance resolution and constructor-chaining constraints in the generator, which is high-impact even with good regression coverage.

Pull request overview

This PR extends the http-client-csharp generator’s last-contract back-compat logic to preserve a model’s previously shipped CLR base type when the current TypeSpec base hierarchy no longer contains it, while guarding against cases that would produce invalid C# (e.g., conflicting partial bases or inaccessible constructor chaining).

Changes:

  • Update ModelProvider base-type selection to restore last-contract bases when safe, retain promoted bases when already derived, and resolve preserved bases against the current build.
  • Add constructor-accessibility checks for symbol-backed base types and emit a new diagnostic for incompatible custom-base conflicts.
  • Add targeted regression tests and test data for generated, referenced, and framework base-type preservation scenarios.
File summaries
File Description
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_ReferencedLastContractBaseWithInternalParameterlessConstructorIsNotRestored(LastContract)/Models.cs Adds last-contract baseline source for an external base with an internal parameterless ctor (should not be restored).
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_NonGeneratedLastContractBaseWithoutParameterlessConstructorIsNotRestored(LastContract)/Models.cs Adds last-contract baseline source for a non-generated base lacking a parameterless ctor (should not be restored).
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_NonGeneratedLastContractBaseWithoutParameterlessConstructorIsNotRestored(Current)/ExternalBase.cs Adds current-build external base type used to validate constructor-chaining constraints.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_LastContractBaseRestoresInheritedProperties/Models.cs Adds baseline types to validate inherited properties remain inherited and are not duplicated.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_CustomBaseTakesPrecedenceOverDifferentLastContractBase(LastContract)/Models.cs Adds last-contract baseline for a previous base used in the custom-base precedence scenario.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_CustomBaseTakesPrecedenceOverDifferentLastContractBase(Current)/Models.cs Adds current-build custom base partial to validate partial-base conflict handling.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_BaseTypeChangePreservesNonGeneratedLastContractBaseType/DerivedModel.cs Adds baseline derived model inheriting from a framework type (e.g., System.Exception).
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/TestData/ModelProviderTests/BackCompat_BaseTypeChangePreservesLastContractBaseType/Models.cs Adds baseline types to validate restoring a previously shipped generated base.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelProviderTests.cs Adds regression tests covering base restoration, promoted bases, inherited properties, custom-base conflicts, and ctor-accessibility constraints.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Utilities/DiagnosticCodes.cs Introduces incompatible-backcompat-base-type diagnostic code.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/NamedTypeSymbolProvider.cs Adds HasAccessibleParameterlessConstructor to validate ctor accessibility across assembly boundaries.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs Implements last-contract base-type preservation logic and resolves preserved bases against the current compilation/providers.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/EmitterRpc/Emitter.cs Adds display label for the new back-compat change category.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/EmitterRpc/BackCompatibilityChangeCategory.cs Adds ModelBaseTypePreserved category for back-compat reporting.
Review details
  • Files reviewed: 14/14 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Copilot AI review requested due to automatic review settings September 3, 2026 03:14

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.

🔵 Needs a closer look

It changes core back-compat inheritance selection logic with broad API-surface implications that warrants final human verification beyond the added regressions.

Review details
  • Files reviewed: 14/14 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

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.

🟡 Changes recommended

There are correctness issues in base-type resolution/traversal that can incorrectly block restoring an available last-contract base or mis-detect base hierarchies in nested/generic scenarios.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (2)

Previously missed (2) — in code that hasn't changed since the last review.

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmModelProvider.cs:95

  • IsInBaseTypeHierarchy uses type.FullyQualifiedName for cycle detection, but CSharpType.FullyQualifiedName omits the full declaring-type chain (and generic arity), so unrelated nested/generic types can collide and prematurely stop traversal. Prefer a unique key such as namespace + ClrMetadataName.
    packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs:366
  • IsInBaseTypeHierarchy uses type.FullyQualifiedName to detect cycles, but CSharpType.FullyQualifiedName only includes the immediate declaring type (and omits generic arity/arguments), so different nested/generic types can collide and prematurely stop traversal. Use a stable unique key such as namespace + ClrMetadataName instead.
  • Files reviewed: 21/21 changed files
  • Comments generated: 1
  • Review effort level: Lite

Copilot AI review requested due to automatic review settings September 3, 2026 08:48

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.

🟡 Changes recommended

The new base-type hierarchy/resolution logic relies on CSharpType.AreNamesEqual in ways that can misidentify multi-level nested types, risking incorrect base restoration decisions unless metadata-identity matching is used consistently.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (2)

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs:435

  • Same nested-type resolution issue in the second CSharpTypeMap scan: using AreNamesEqual can select the wrong provider for multi-level nested types. Use metadata identity matching here as well to keep the two scans consistent and correctly disambiguate nested declaring-type chains.
                if (provider is not null &&
                    provider.Type.AreNamesEqual(type) &&
                    TryUseProviderAsBase(provider, out resolvedProvider))

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs:418

  • TryResolveTypeInCurrentBuild matches candidate providers using CSharpType.AreNamesEqual. For multi-level nested types, AreNamesEqual can conflate types because FullyQualifiedName drops outer declaring types. This can cause resolving the wrong provider from CSharpTypeMap when nested types share the same immediate declaring type name. Match on metadata identity (outermost namespace + ClrMetadataName) instead.
                if (provider is not null &&
                    provider.Type.AreNamesEqual(type) &&
                    TryUseProviderAsBase(provider, out resolvedProvider))
  • Files reviewed: 21/21 changed files
  • Comments generated: 1
  • Review effort level: Lite

Copilot AI review requested due to automatic review settings September 3, 2026 09:01

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.

🔵 Needs a closer look

It changes core model base-type resolution/back-compat behavior in the generator with potentially broad API/compat impact, so it warrants final human validation beyond automated review.

Review details

Suppressed comments (3)

Previously missed (2) — in code that hasn't changed since the last review.

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmModelProvider/ScmModelProviderTests.cs:333

  • This Roslyn compilation assertion only checks DiagnosticSeverity.Error. Since generator projects build with TreatWarningsAsErrors=true, consider failing the test on warnings as well so issues like member hiding (CS0108) are caught here before they become build breaks.
    packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelProviderTests.cs:613
  • The Roslyn compilation assertion only checks DiagnosticSeverity.Error. This can miss warnings like CS0108 (member hiding) that will fail real builds because generator projects treat warnings as errors (TreatWarningsAsErrors=true). Consider asserting that there are no warnings as well so the regression better matches actual build behavior.

This issue also appears on line 652 of the same file.

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelProviders/ModelProviderTests.cs:655

  • This compilation check filters only DiagnosticSeverity.Error, so warnings (which are treated as errors in the generator build) can slip through and make the regression less representative. Consider including DiagnosticSeverity.Warning in the assertion.
            Assert.That(
                compilation.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error),
                Is.Empty,
                "The generated model hierarchy should compile after the incompatible symbol-backed base restoration is skipped");
  • Files reviewed: 21/21 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI review requested due to automatic review settings September 3, 2026 09:11

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.

🔵 Needs a closer look

The base-restoration property-collision guard currently treats private base properties as collisions, which can incorrectly block restoring a last-contract base type.

Review details

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs:391

  • HasDirectPropertyNameCollision treats private properties on a symbol-backed/base type as collisions. In C#, private base members are not inherited, so they cannot cause the CS0108 hiding warning/error and shouldn’t block restoring the last-contract base; this can incorrectly skip base restoration and break assignability compatibility.
  • Files reviewed: 21/21 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI review requested due to automatic review settings September 3, 2026 09:22

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.

🔵 Needs a closer look

The change alters core model inheritance/back-compat behavior with many edge-case guards (resolution, constructors, collisions, discriminator hierarchies) that warrants final human review despite strong regression coverage.

Review details
  • Files reviewed: 21/21 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@live1206

Copy link
Copy Markdown
Contributor Author

latest regen: Azure/azure-sdk-for-net#62687

Copilot AI review requested due to automatic review settings September 4, 2026 02:09

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.

🟡 Changes recommended

There is a C# compile issue in ScmModelProvider where an override reduces method accessibility, which must be corrected before merge.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 21/21 changed files
  • Comments generated: 1
  • Review effort level: Lite

Copilot AI review requested due to automatic review settings September 4, 2026 05:29

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.

🔵 Needs a closer look

It introduces non-trivial base-type reconciliation logic that can affect broad model/serialization behaviors, so it warrants final human review despite strong regression coverage.

Review details
  • Files reviewed: 21/21 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

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.

[http-client-csharp] Add last-contract backward compatibility for model base types

5 participants