Preserve model factory back-compat parameter optionality - #11703
Preserve model factory back-compat parameter optionality#11703Jorge Rangel (jorgerangel-msft) with Copilot wants to merge 18 commits into
Conversation
|
Azure Pipelines: Successfully started running 1 pipeline(s). 1 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
commit: |
There was a problem hiding this comment.
Pull request overview
This PR updates the http-client-csharp generator’s model-factory back-compat signature shaping so compatibility overloads preserve optional trailing parameters when doing so does not introduce ambiguous overload resolution, while still requiring the minimum positional prefix needed to disambiguate against surviving generated/custom overloads.
Changes:
- Extend
MethodSignatureHelper.BuildBackCompatMethodSignatureto compute and enforce a minimum “required parameter prefix” based on competing overload signatures. - Update
ModelFactoryProviderto pass the relevant competing overload set (including custom overloads) when constructing back-compat shims. - Add/adjust unit tests and golden test data to cover reordered overloads, fully-optional overloads, custom overload coexistence, and nullable value-type disambiguation.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs | Adds overload-aware logic to require only the minimum disambiguating parameter prefix while preserving trailing defaults when safe. |
| packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelFactoryProvider.cs | Computes and supplies the current competing overload set (including custom methods) when generating back-compat model factory methods. |
| packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Shared/MethodSignatureHelperTests.cs | Adds focused unit tests validating minimum-prefix computation across multiple overloads and value-type nullability distinctions. |
| packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/ModelFactoryProviderTests.cs | Adds integration tests asserting required/optional boundaries for generated back-compat factory methods across key scenarios. |
| packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_UnknownDiscriminatorReturnTypeOverloadIsGenerated.cs | Updates expected output to preserve optional parameters when no competing overload exists for the compatibility overload. |
| packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedRequiredParametersPreserveOptionalTrailingParameters/SampleNamespaceModelFactory.cs | New test asset representing a prior contract with required prefix + optional trailing defaults. |
| packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedFullyOptionalParametersRequireMinimumPrefix/SampleNamespaceModelFactory.cs | New test asset representing a fully-optional prior contract reorder scenario. |
| packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedCustomOverloadRequiresMinimumPrefix(Last)/SampleNamespaceModelFactory.cs | New “last contract” asset used to validate interplay with custom overloads. |
| packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedCustomOverloadRequiresMinimumPrefix(Custom)/SampleNamespaceModelFactory.cs | New “custom” asset used to validate coexistence/minimum-prefix computation against custom overload signatures. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
|
No changes needing a change description found. |
Jorge Rangel (jorgerangel-msft)
left a comment
There was a problem hiding this comment.
Copilot I'd like to see more unit tests with back compat + custom code where the resulting generated signature does not have all the parameters as required, only a subset of them. In addition, we should be ensuring we are keeping perf in mind for all changes. The back compat processing is getting quite complex and difficult to follow, so we should avoid overcomplicating things if possible
Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
Addressed in c4d1cde. Added custom-overload coverage that preserves trailing optional parameters, and cached the combined factory method list. |
Jorge Rangel (jorgerangel-msft)
left a comment
There was a problem hiding this comment.
Copilot It doesn't seem our fix is working as intended in the test azure library Azure/azure-sdk-for-net#62209. The pr shows that the SiteContainerData model factory back compat method is still being generated with all params as required, and another overload with all params optional. The previous GA signature is public static SiteContainerData SiteContainerData(ResourceIdentifier id = null, string name = null, ResourceType resourceType = null, SystemData systemData = null, string image = null, string targetPort = null, bool? isMain = null, string startUpCommand = null, SiteContainerAuthType? authType = null, string userName = null, string passwordSecret = null, string userManagedIdentityClientId = null, DateTimeOffset? createdOn = null, DateTimeOffset? lastModifiedOn = null, IEnumerable<SiteContainerVolumeMount> volumeMounts = null, bool? inheritAppSettingsAndConnectionStrings = null, IEnumerable<WebAppEnvironmentVariable> environmentVariables = null, string kind = null); . And the custom code that was removed in the pr shows what the generated shape should've been for the back compat method. Validate the pr diff, confirm if my analysis is correct, and work on correcting this in the generator as part of this pr
Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
Confirmed. The Azure diff showed the prior required prefix was being promoted through every trailing optional parameter. Fixed in 890dd3e: overlapping all-optional current overloads now retain an existing required prefix and preserve the prior trailing defaults. Added helper and model-factory snapshot regressions for that reordered shape. |
The model factory back-compat analysis promoted every parameter to required whenever the previous signature was fully optional, and it removed a current overload even when that exact signature was still part of the last contract. Regenerating Azure.ResourceManager.AppService reproduced both: SiteContainerData lost the GA parameter optionality and one of its overloads disappeared. Require only the parameter prefix up to and including the first position whose type differs from the competing overload, so trailing parameters keep the optionality they were published with, and never replace a current overload that still matches a previously shipped signature. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 098ae4f6-52c2-41dc-a7aa-88f561b153e5
Two last-contract overloads can compete with the same current method and with each other, and each needs its own required prefix. Pin both branches of the analysis: an overload that is a positional prefix of the current method becomes fully required because no argument count distinguishes it, while a reordered overload only requires the prefix up to its first differing parameter type and keeps its trailing optional parameters. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 098ae4f6-52c2-41dc-a7aa-88f561b153e5
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 23 out of 23 changed files in this pull request and generated no new comments.
Suppressed comments (2)
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs:82
- BuildBackCompatMethodSignature(previous, hideMethod, currentMethodSignatures, ...) currently calls RequireMinimumParameterPrefix(previousMethodSignature, ...), which mutates the last-contract signature in place (it nulls DefaultValue on its ParameterProvider instances). In ModelFactoryProvider those previous signatures are reused while processing other overloads, so this introduces order-dependent behavior and can affect later comparisons.
Consider applying the required-prefix adjustment only to the new back-compat signature (leaving the previous-contract signature unchanged).
internal static MethodSignature BuildBackCompatMethodSignature(
MethodSignature previousMethodSignature,
bool hideMethod,
IReadOnlyList<MethodSignature> currentMethodSignatures,
bool shouldNotBeAsync = false)
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs:89
- The XML doc for RequireMinimumParameterPrefix says that when no overloads are supplied "every parameter becomes required", but the implementation only forces all parameters required when currentMethodSignatures is null (an empty list results in requiredParameterCount = 0). Updating the wording would avoid misleading future maintainers.
/// Removes the default values from the leading parameters of <paramref name="signature"/> so it
/// can no longer be called with fewer arguments than the prefix that distinguishes it from
/// <paramref name="currentMethodSignatures"/>. When no overloads are supplied there is nothing to
/// compare against and every parameter becomes required.
/// </summary>
JoshLove-msft
left a comment
There was a problem hiding this comment.
Found a source-compatibility regression in the overload analysis.
| { | ||
| if (!ReferenceEquals(compatiblePreviousMethod, previousMethod)) | ||
| { | ||
| overloadSignatures.Add(compatiblePreviousMethod.Signature); |
There was a problem hiding this comment.
[P1] Do not treat last-contract overloads as new competitors
Adding every other compatiblePreviousMethod to the analysis set can remove defaults even when there is no surviving current/custom overload with that method name. Those overloads already coexisted in the published contract. For example, after a model/factory rename, prior overloads Old(string id, string description = default) and Old(string name = default) make the first overload fully required because the second is a positional prefix. That breaks the previously valid named call Old(id: "x"); reversing declaration order can additionally make Old() invalid because signatures are mutated in place. Only surviving current/custom overloads should drive optionality promotion. Please add a regression with multiple removed or renamed prior overloads and named calls.
--generated by Copilot
Review state corrected; PR reviews should be posted as comments, not changes requested.
Model-factory compatibility overloads either made every parameter required or retained fully optional reordered signatures, breaking callers or creating ambiguous overloads.
Changes