Expand back-compat property type preservation to all public model properties#10413
Expand back-compat property type preservation to all public model properties#10413
Conversation
Agent-Logs-Url: https://github.com/microsoft/typespec/sessions/33d000a2-d811-4f6c-a72b-359b57cee1a2 Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
commit: |
|
|
||
| - **Collection wrapper change:** A property previously generated as a read-only collection (e.g. `IReadOnlyList<T>` / `IReadOnlyDictionary<TKey, TValue>`) that is now produced as a read-write collection (e.g. `IList<T>` / `IDictionary<TKey, TValue>`), or vice versa. The collection element/key/value type names must still match – the wrapper is preserved but genuine element-type changes are honoured. | ||
| - **Nullability change:** A scalar, enum, or model property that was previously generated as nullable (e.g. `int?`, `StatusEnum?`) and is now produced as non-nullable (or vice versa). The last contract's nullability is preserved when the top-level type name (and any generic argument names) still matches. | ||
| - **Same-name types from different sources:** Properties whose generated type is logically the same as the last contract's type, but sourced from different assemblies (e.g. a `TypeProvider`-produced type vs. a compiled-assembly type). Equality is evaluated by name rather than identity so these are treated as the same type. |
| - The generator compares property types against the `LastContractView` | ||
| - For read-write lists and dictionaries, if the previous type was different, the previous type is retained | ||
| - The generator compares property types against the `LastContractView`. | ||
| - For read-write lists and dictionaries, if the previous type was different but the element/key/value type names match, the previous type is retained. |
There was a problem hiding this comment.
@copilot this is no longer the case. Pull the latest main and remove mention of this.
There was a problem hiding this comment.
Pulled the latest backward-compatibility.md from main and rewrote the Implementation Details to describe the unified "same top-level name → retain previous type" rule, dropping the collection-only wording. Done in fd9ef6e.
| // Backcompat fix for property types: if a property existed in the last contract | ||
| // with a compatible but non-identical type, retain the previous type to avoid | ||
| // breaking consumers of the library. | ||
| if (TryGetLastContractPropertyTypeOverride(outputProperty, out var lastContractPropertyType)) |
There was a problem hiding this comment.
@copilot Since we are now handling all properties, we should extend our back compat APIs in TypeProvider to have a new BuildPropertiesForBackCompatibility method.
There was a problem hiding this comment.
Added protected internal virtual BuildPropertiesForBackCompatibility(IEnumerable<PropertyProvider>) to TypeProvider in fd9ef6e, matching the shape of BuildMethodsForBackCompatibility / BuildConstructorsForBackCompatibility. ModelProvider now overrides it to perform the type-override pass, and BuildProperties routes its output through the new hook so every property gets back-compat treatment. I kept the call site inside BuildProperties (rather than ProcessTypeForBackCompatibility) so the property type is settled before serializers/writers or visitors read Properties — moving it to ProcessTypeForBackCompatibility broke several existing tests that rely on the type being present at property-build time. Happy to revisit if you'd prefer a different trigger point.
Agent-Logs-Url: https://github.com/microsoft/typespec/sessions/e014a173-0713-44e4-a0b5-4ce8f896d407 Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
| - The generator compares property types against the `LastContractView` | ||
| - For read-write lists and dictionaries, if the previous type was different, the previous type is retained | ||
| - The generator compares property types against the `LastContractView`. | ||
| - If the previous type is logically compatible with the new type (same top-level name, with matching generic argument names), the previous type is retained. This covers read-write collection wrapper changes (e.g. `IList<T>` ↔ `IReadOnlyList<T>`) as well as nullability changes on scalars, enums, and models. |
There was a problem hiding this comment.
@copilot this is no longer the case - sync from main
There was a problem hiding this comment.
Could you clarify what you'd like changed here? Line 168 describes the unified rule introduced by this PR (collection wrapper changes + nullability), and main hasn't moved since I last synced it (ab5caa4 is still tip for this file). If you'd prefer I drop the "Implementation Details" bullet list entirely — keeping just the two scenario sections above — happy to do that.
There was a problem hiding this comment.
You're right, sorry — I missed 38a8c7a (the revert of #10319). Merged main in 6275b90 and dropped the AreNamesEqual generic-argument check from the collection branch so it matches main's behavior (override on any inequality). Updated the doc accordingly. Non-collection properties still use the conservative top-level-name-match rule for the new nullability case.
…n back-compat) Agent-Logs-Url: https://github.com/microsoft/typespec/sessions/1cb571b4-94fe-450a-bda0-73a4c98e8f9f Co-authored-by: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com>
|
@copilot sync from main and resolve all merge issues |
…ompat-support # Conflicts: # packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelProvider.cs Co-authored-by: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com>
Synced from main in 7c64485. Resolved the conflict in |
| // argument names) to match. This ensures we only override when the types are | ||
| // logically the same (e.g. differ only in nullability) and never when the | ||
| // underlying type has genuinely changed (e.g. string to int). | ||
| if (outputProperty.Type.AreNamesEqual(candidate)) |
There was a problem hiding this comment.
@copilot why wouldn't we just always enforce the existing type in the back compat scenario? Users can work around this with custom code if they need to.
There was a problem hiding this comment.
Good point — done in 86c40d0. TryGetLastContractPropertyTypeOverride now always returns the last contract's type whenever it differs from the new spec, with no collection-vs-scalar or AreNamesEqual branching. Updated the previously-negative test (BackCompat_ScalarPropertyTypeOverriddenWhenTypeNameDiffers) to assert the override now applies even when the top-level name changes (string → int is preserved as string), and rewrote the docs to describe the unconditional behavior. All 1317 generator tests pass.
Agent-Logs-Url: https://github.com/microsoft/typespec/sessions/111ae35d-3fde-4326-95a5-11485d35034c Co-authored-by: JoshLove-msft <54595583+JoshLove-msft@users.noreply.github.com>
BuildPropertiesForBackCompatibilityvirtual hook onTypeProviderModelProviderback-compat property logic into the new hookbackward-compatibility.mdModel Properties section to describe the unconditional preservation behaviorModelProvider.cs