Skip to content

Generate back-compat overload when a service method gains a new optional non-body parameter#10532

Draft
Copilot wants to merge 4 commits intomainfrom
copilot/handle-new-optional-parameter
Draft

Generate back-compat overload when a service method gains a new optional non-body parameter#10532
Copilot wants to merge 4 commits intomainfrom
copilot/handle-new-optional-parameter

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 28, 2026

Adding an optional parameter to a service method is non-breaking in TypeSpec but breaks generated C# clients (callers binding to the old method signature fail to resolve). The generator now mirrors the Service-Driven Evolution guidance and emits a hidden overload matching the previous contract's signature.

Changes

  • ClientProvider.BuildMethodsForBackCompatibility — after parameter reordering, scans LastContractView.Methods for previous signatures whose parameters are a same-order subset of a current method, where every extra current parameter is optional and non-body. Emits a hidden [EditorBrowsable(Never)] ScmMethodProvider overload (same ScmMethodKind as the current method) that delegates to the current method, passing default for each new parameter. Async overloads delegate without await so the back-compat method itself remains non-async.
  • Body parameters are intentionally excluded per the linked guidance, since adding a body parameter typically reflects a schema change handled elsewhere.
  • BackCompatibilityChangeCategory.MethodNewOptionalParameterOverloadAdded — new category surfaced in the emitter end-of-run summary.
  • TestsBackCompatibility_NewOptionalNonBodyParameterAdded, BackCompatibility_MultipleNewOptionalNonBodyParametersAdded, BackCompatibility_NewOptionalBodyParameterDoesNotAddBackCompatOverload, BackCompatibility_NewRequiredParameterDoesNotAddBackCompatOverload.
  • Docs — new "Service Method Evolution" section in backward-compatibility.md.

Example

Previous contract:

public virtual ClientResult<string> GetData(int param2, string param1, CancellationToken cancellationToken = default);
public virtual Task<ClientResult<string>> GetDataAsync(int param2, string param1, CancellationToken cancellationToken = default);

Current TypeSpec adds an optional @header param3?: boolean. Generated overloads:

[EditorBrowsable(EditorBrowsableState.Never)]
public virtual ClientResult<string> GetData(int param2, string param1, CancellationToken cancellationToken)
    => this.GetData(param2, param1, default, cancellationToken);

[EditorBrowsable(EditorBrowsableState.Never)]
public virtual Task<ClientResult<string>> GetDataAsync(int param2, string param1, CancellationToken cancellationToken)
    => this.GetDataAsync(param2, param1, default, cancellationToken);

Defaults are stripped from the back-compat signature to avoid ambiguous call sites with the current method.

Agent-Logs-Url: https://github.com/microsoft/typespec/sessions/bb773994-13fe-4a5a-971a-b594a6d82388

Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
@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 Apr 28, 2026
Copilot AI changed the title [WIP] Add handling for new optional parameter in LastContractView service method [ClientModel] Generate back-compat overload when a service method gains a new optional non-body parameter Apr 28, 2026
Copilot AI requested a review from jorgerangel-msft April 28, 2026 21:23
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 28, 2026

Open in StackBlitz

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

commit: c20d2ae

@jorgerangel-msft jorgerangel-msft changed the title [ClientModel] Generate back-compat overload when a service method gains a new optional non-body parameter Generate back-compat overload when a service method gains a new optional non-body parameter Apr 28, 2026
}

var currentMethodSignatures = BuildCurrentMethodSignatures(originalMethods);
var materializedMethods = originalMethods as IList<MethodProvider> ?? [.. originalMethods];
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.

@copilot lets move the existing parameter ordering back compat support into a separate private helper.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted into ApplyParameterReorderingForBackCompat in 81e56f3.


return [.. originalMethods];
// Add back-compat overloads for methods that have gained one or more new optional non-body parameter(s)
// relative to the last contract. See https://github.com/Azure/azure-sdk-for-net/blob/main/doc/DataPlaneCodeGeneration/ServiceDrivenEvolution.md#a-method-gets-a-new-optional-parameter
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.

@copilot simply this comment and remove the link

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplified and removed the link in 81e56f3.

Comment on lines +1758 to +1767
/// For each public/protected method on the last contract that does not have an exact match in the
/// current contract, attempts to find a corresponding current method whose parameter list is the
/// previous method's parameter list (in the same order) plus one or more additional optional
/// non-body parameters. When such a current method is found, a hidden back-compat overload that
/// matches the previous signature is added; its body simply delegates to the current method,
/// passing default values for the new parameters.
///
/// Per https://github.com/Azure/azure-sdk-for-net/blob/main/doc/DataPlaneCodeGeneration/ServiceDrivenEvolution.md#a-method-gets-a-new-optional-parameter
/// this back-compat behavior is intentionally restricted to non-body parameters.
/// </summary>
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.

@copilot no need for the summary. Remove it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in 81e56f3.

Comment on lines +1775 to +1786
var existingSignatures = new HashSet<MethodSignature>(MethodSignature.MethodSignatureComparer);
foreach (var m in methods)
{
existingSignatures.Add(m.Signature);
}
if (CustomCodeView?.Methods != null)
{
foreach (var m in CustomCodeView.Methods)
{
existingSignatures.Add(m.Signature);
}
}
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.

@copilot it seems there is some overlapping logic between this and BuildCurrentMethodSignatures. CAn we consolidate this and only calculate the signatures once to improve perf?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consolidated in 81e56f3currentMethodSignatures is now built once in BuildMethodsForBackCompatibility and passed into both ApplyParameterReorderingForBackCompat and AddBackCompatOverloadsForNewOptionalParameters. The latter reuses it for the existing-signature check and derives the by-name lookup from its values, so no duplicate signature collection happens.

…dict

Agent-Logs-Url: https://github.com/microsoft/typespec/sessions/98e4708e-374c-4348-8b5b-30e4c877bd56

Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
}
```

### Service Method Evolution
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.

@copilot lets rename this section to "Client Methods"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to "Client Methods" in 779d663.

Previous version of the client:

```csharp
public virtual ClientResult<string> GetData(int param2, string param1, CancellationToken cancellationToken = default);
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.

@copilot we need to fix this example. The tsp example you have below has a body param. We should fix it to be
op getData(@query p1: int32, @body body: SampleModel, @query p2?: boolean): string;
And the generated methods should accept a BinaryContent param type for the body.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 779d663 — TSP example now uses @body body: SampleModel with @query p1 / @query p2?, and the generated methods accept BinaryContent for the body.

Agent-Logs-Url: https://github.com/microsoft/typespec/sessions/eaa4071b-877d-4406-a1ca-63af4e8c07c5

Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
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.

LastContractView - Handle A New Optional Parameter Being Added to a Service Method

2 participants