You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(http-client-csharp): restore historical bases over mapped ancestors - #12310
Fixes#12304 (the original BotData existing-mapped-base scenario).
Restore a compatible historical framework base over an existing mapped CLR ancestor, reconciling child-owned and ancestor-owned inherited properties without changing input ownership or shared providers.
Keep canonicalization and constructor traversal consistent with the model-local projection. Preserve source-owner vetoes, custom property/field authority, and type/accessor/collision/ambiguity safeguards.
Retain the historical BotData location constructor through compatible inherited-property assignment, preserving parameter types/defaults and rejecting unsafe conversions or inaccessible setters.
Preserve projected child properties during deserialization. Getter-only collection population requires an explicit downstream mutability contract; unknown interfaces and read-only wrappers retain the current base.
Scope after the split
This PR does not introduce a framework constructor-default approval API. The separate Network proposal #12311 is withdrawn. The latest plan is an SDK-owned legacy constructor with existing CodeGenSuppress, plus a narrowly scoped relaxation of the custom-constructor restoration guard here if it remains small. That relaxation is not implemented yet; the flat-base and real-SDK validation prerequisites still apply.
BotData’s inherited-property constructor repair is included: restoring its base alone does not retain its historical public location overload.
Change-specific validation
The original BotData regression remains intact: historical base, inherited Location/Tags ownership, and the public non-nullable location constructor.
Controls cover nonempty-base canonicalization, duplicate ancestor initialization, input/shared-provider isolation, source-owner vetoes in both cache orders, custom property/field aliases, and inherited parameter/default/setter safety.
Added compiled scalar/dictionary/list JSON round trips, read-only-wrapper and unapproved-interface fallback controls, virtual-event override/hiding controls, and an emitted constructor baseline including inherited backing-field exclusion.
Latest local validation: 148 core + 75 ClientModel focused checks, 3,058/3,058 core Generator tests, and 2,648/2,648 ClientModel tests passed, with no skipped tests. CI for 4cb8aac5e passed, including Windows/Linux C# tests and regeneration checks (build 7013300).
No real SDK regeneration, GA ApiCompat run, or inherited ARM-envelope JSON-roundtrip validation is claimed. The separate flat-base feature remains #12303.
Wei Hu (live1206)
changed the title
fix(http-client-csharp): preserve mapped bases and historical constructors
fix(http-client-csharp): restore historical bases over mapped ancestors
Oct 20, 2026
This PR addresses #12304 by extending C# client-generator compatibility handling to restore historical framework bases over existing mapped ancestors, including BotData’s inherited properties and public location constructor. Flat-model restoration and framework-default constructor approval remain outside this PR’s scope.
Changes:
Adds model-local inherited-property projections while preserving input ownership and shared providers.
Updates base resolution, canonical properties, and constructor traversal to use those projections.
Adds regression coverage for compatibility safeguards, customization authority, and historical constructor metadata.
Paths are relative to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/.
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Unconsumed ancestor-owned properties can be omitted from deserialization, silently losing JSON values, while getter-only collections with unknown mutability can throw during restoration.
Checking CLR nullability does not reject nullable wire collections: ApplyCurrentInputMetadata replaces Type with the historical non-nullable type while retaining WireInfo.IsNullable. With an approved getter-only Tags and nullable input, {"tags":null} therefore passes restoration, but ModelProvider.cs:1996 skips population and leaves an empty dictionary; reject nullable wire collections here and add a null-payload fallback control.
The reason will be displayed to describe this comment to others. Learn more.
imo this is still confusing. Can we guarantee that inputProperties will originate from the inputType ? Or are we allowing inputProperties to possibly originate from another input type ? Previously, we knew the properties always came from the inputmodel..
The reason will be displayed to describe this comment to others. Learn more.
Clarified in ac1cc78. The normal constructor still takes properties from inputType. Mapped views now use a separate CreateForPropertyProjection factory whose contract explicitly allows properties from a descendant or multiple models in its hierarchy, retaining their original input owners. The model argument supplies model-level metadata, not property provenance. Canonicalization and input-ownership regressions pass.
The reason will be displayed to describe this comment to others. Learn more.
Simplified in ac1cc78. Historical matching is now a shared named helper, and source-owner/cache/customization validation is isolated in PreservesSourceOwnerProperty. The hierarchy loop now handles only traversal, matching, and duplicate tracking. Existing mapped-base regressions pass.
The reason will be displayed to describe this comment to others. Learn more.
Updated in ac1cc78 to use DictionarySnippets.Add and ListSnippets.Add on the typed inherited-property expression. There is no existing Clear snippet, so that invocation uses nameof(ICollection<object>.Clear) rather than a string literal. The compiled dictionary/list payload round-trip regressions pass.
The reason will be displayed to describe this comment to others. Learn more.
Changed in ac1cc78. Removed the protected core hook and made IsLastContractModelBaseCollectionMutable internal virtual, updating the test setups accordingly. No production override was found in this checkout. The collection round-trip and unsafe-collection fallback regressions pass.
The reason will be displayed to describe this comment to others. Learn more.
as I'm reviewing this, I'm wondering why do we need CreateUncachedProperty and this method? we could just make PropertyCache internal since we are the only users. Mutating it should still only happen in the typefactory, but I don't see an issue with reading the dictionary directly in mtg
The reason will be displayed to describe this comment to others. Learn more.
Updated in ac1cc78: PropertyCache is exposed internally as an IReadOnlyDictionary, with writes remaining inside TypeFactory, and the redundant GetProperty wrapper is removed. CreateUncachedProperty remains necessary to dispatch through protected factory overrides and visitors for a specific owner without reading/populating the input-keyed cache during projection or reentrant validation. Both cache-order regressions pass.
The reason will be displayed to describe this comment to others. Learn more.
The earlier ancestor-deserialization and nullable-collection findings are addressed. One remaining correctness concern and one nonblocking simplification below.
The reason will be displayed to describe this comment to others. Learn more.
[P2] Preserve source-owner visitor changes
PreVisitProperty can modify the property (LibraryVisitor.cs:121–128), but this only validates its name: a source-owner-specific change to Location’s type or setter still passes, then disappears when ApplyCurrentInputMetadata recreates it under the framework owner. Reject source-owner customizations the projection cannot preserve, with regression coverage for both cache orders.
The reason will be displayed to describe this comment to others. Learn more.
Fixed in ac1cc78. Restoration now rejects source-owner CLR-shape changes that framework projection would discard, comparing the visited property against an unvisited source-owner property. Added ten cases covering type, setter presence/accessibility, member accessibility, and init-only changes in both cache orders; all ten failed before the fix and pass afterward. Both full unit suites pass: 3,069 core + 2,651 ClientModel tests, with no skips. CI for the new commit is still outstanding, so this thread remains unresolved.
The property matching, ambiguity detection, and historical wire-metadata checks duplicate BackCompatModelBaseResolver.cs:111–125, leaving two implementations of the same compatibility rules to maintain. Extract those shared checks into a small helper, keeping hierarchy traversal and source-owner validation specific to each path.
The reason will be displayed to describe this comment to others. Learn more.
Consolidated in ac1cc78. Both paths now use TryMatchHistoricalProperty for compatibility matching, ambiguity/identity detection, and known historical wire-metadata conflicts. Hierarchy traversal, duplicate ownership, and source-owner validation remain specific to each path. Both restoration paths' existing regressions pass.
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
The internal opt-in cannot be overridden by downstream TypeFactory subclasses in other assemblies, preventing approval of getter-only collection mappings and causing their base upgrades to be rejected.
The reason will be displayed to describe this comment to others. Learn more.
This recommendation conflicts with the earlier request to make this hook internal when there is no usage outside MTG: #12310 (comment) . The latest change followed that request; no production override was found in this checkout. A protected Core hook would allow non-friend downstream assemblies to approve known getter-only collection contracts, but whether we need that extension point is still an API-scope decision. Leaving the visibility decision open for now; no code change is being made in response to this comment yet.
- by copilot
This branch has not been deployed
No deployments
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #12304 (the original BotData existing-mapped-base scenario).
Scope after the split
This PR does not introduce a framework constructor-default approval API. The separate Network proposal #12311 is withdrawn. The latest plan is an SDK-owned legacy constructor with existing
CodeGenSuppress, plus a narrowly scoped relaxation of the custom-constructor restoration guard here if it remains small. That relaxation is not implemented yet; the flat-base and real-SDK validation prerequisites still apply.BotData’s inherited-property constructor repair is included: restoring its base alone does not retain its historical public location overload.
Change-specific validation
4cb8aac5epassed, including Windows/Linux C# tests and regeneration checks (build 7013300).No real SDK regeneration, GA ApiCompat run, or inherited ARM-envelope JSON-roundtrip validation is claimed. The separate flat-base feature remains #12303.
- by copilot