microsoft / microsoft/typespec
[http-client-csharp] Add last-contract backward compatibility for model base types
- Dominant language
- Java
- Stars
- 5.9k
- Forks
- 394
- Avg merge
- 1d 23h
- Merged PRs (30d)
- 104
Description
## Description
MTG has last-contract backward-compatibility handling for model names, declaration modifiers, properties, constructors, methods, and other published API details, but it does not currently preserve a model's previously shipped CLR base type.
A base-type change is itself a breaking API change. It can remove assignability to the previous base and can also remove inherited properties, constructors, serialization behavior, and polymorphic behavior. Today, preserving an existing hierarchy requires custom partial code, and MTG then has to reconcile the current TypeSpec base hierarchy with a potentially mismatched custom CLR hierarchy.
This gap surfaced while fixing #11727 in #11752. In that case, custom code retained a previously shipped, narrower CLR base while the current TypeSpec model had a broader base. Properties present only in the TypeSpec base were disconnected from generation and disappeared from the generated model, constructors, model factory, and serialization. PR #11752 restores those properties, but it is a targeted custom-base reconciliation fix rather than general base-model backward-compatibility support.
## Problem
`ModelProvider.BuildBaseType()` currently selects either:
1. `CustomCodeView.BaseType`, when custom code declares a base; or
2. the base represented by `_inputModel.BaseModel`.
It does not compare that result with `LastContractView.BaseType`. Consequently, MTG does not recognize or handle a change from a previously published base type.
The resulting compatibility problem is broader than missing properties:
- The generated model may no longer be assignable to its previously shipped base type.
- Public or protected members inherited from the previous base may disappear.
- Constructor chaining and constructor parameter propagation may change.
- Serialization/deserialization behavior inherited from the previous base may be lost.
- Discriminator and polymorphic model behavior may change.
- Preserving the old base through custom code can leave current TypeSpec-base properties without either an inherited or generated CLR member.
- Members represented by both hierarchies can be duplicated or incorrectly emitted as `new`/`override` unless reconciliation uses the effective CLR contract.
## Expected direction
Add explicit last-contract backward-compatibility support for model base types, consistent with MTG's existing compatibility processing for other API surface.
The design should:
1. Compare the current generated base type with the previously shipped base from `LastContractView`.
2. Determine when the previous base can and should be preserved, including type resolvability, accessibility, and accepted ApiCompat suppressions.
3. Preserve the previous CLR inheritance relationship when required for source/binary compatibility.
4. Reconcile the current TypeSpec model contract against the preserved effective CLR hierarchy before building model artifacts:
- keep members actually supplied by the effective base inherited;
- materialize current TypeSpec properties not supplied by that base;
- avoid duplicate or phantom `new`/`override` members;
- use the same reconciled contract for constructors, model factories, and serialization/deserialization.
5. Validate constructor chaining and discriminator/polymorphic behavior when the old and current base models differ.
6. Emit a clear compatibility diagnostic when the previous base cannot safely be retained.
Property identity and inherited-member compatibility should use the shared semantic matching work tracked by #11765 rather than introducing another independent matching policy.
## Suggested regression coverage
Add last-contract tests for at least:
- A model whose current TypeSpec base differs from its previously shipped CLR base.
- A previous base that is narrower than the current TypeSpec base: missing current properties are generated directly on the derived model and participate in constructors and serialization.
- A previous base that already supplies a property, including a renamed member: the property is not duplicated.
- A downstream model redeclaring a property when the effective base member is renamed, non-virtual, virtual, or filtered.
- Constructor chaining when the previous and current bases expose different constructors.
- Discriminated/polymorphic hierarchies.
- A previous base that is unavailable or intentionally suppressed in the ApiCompat baseline.
## Context
- #11727 tracks preserving TypeSpec-base properties when a custom CLR base is narrower.
- #11752 implements targeted reconciliation for that custom-base scenario.
- #11765 tracks shared semantic property matching across generated and custom model bases.
- by copilot
Contributor guide
Assessment
This issue has not been assessed yet.