Commit 72a95c2
Generate back-compat overload when a service method gains a new optional non-body parameter (microsoft#10532)
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](https://github.com/Azure/azure-sdk-for-net/blob/main/doc/DataPlaneCodeGeneration/ServiceDrivenEvolution.md#a-method-gets-a-new-optional-parameter)
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 via the `This.Invoke(string methodName,
IReadOnlyList<ValueExpression> arguments)` snippet, passing `default`
for each new parameter. Async overloads delegate without `await` so the
back-compat method itself remains non-async. The reordering and
new-optional-parameter passes are factored into private helpers
(`ProcessBackCompatForParameterReordering` and
`ProcessBackCompatForNewOptionalParameters`) which share a single
`currentMethodSignatures` dictionary and mutate the same
`materializedMethods` list. The new-optional-parameter pass skips
`ScmMethodProvider`s with `Kind == CreateRequest` and only generates
overloads for matched candidates whose `Kind` is `Convenience` or
`Protocol`, inheriting that `Kind`/`ServiceMethod` from the current
method. The candidate match collapses the `ScmMethodProvider` /
`Convenience|Protocol` filter into the candidates loop's pattern match,
and `currentMethodSignatures.TryAdd` is reused both for dedup and to
make new overloads visible to subsequent iterations (no auxiliary lists
or sets).
- **Body parameters are intentionally excluded** per the linked
guidance, since adding a body parameter typically reflects a schema
change handled elsewhere.
-
**`BackCompatibilityChangeCategory.SvcMethodNewOptionalParameterOverloadAdded`**
— new category surfaced in the emitter end-of-run summary.
- **Tests** — `BackCompatibility_NewOptionalNonBodyParameterAdded`,
`BackCompatibility_MultipleNewOptionalNonBodyParametersAdded`,
`BackCompatibility_NewOptionalNonBodyParameterAddedWithModelBody`
(covers an `InputModelType` request body),
`BackCompatibility_NewOptionalNonBodyParameterAddedWithPathAndHeaderParameters`
(covers a method whose previous contract mixes a path parameter with a
required query and a required header),
`BackCompatibility_NewOptionalBodyParameterDoesNotAddBackCompatOverload`,
`BackCompatibility_NewRequiredParameterDoesNotAddBackCompatOverload`.
All six tests run `TypeProviderWriter.Write()` on the affected client
wrapped in a `FilteredMethodsTypeProvider` so the expected `.cs`
fixtures under `TestData/ClientProviderTests/` only contain the affected
operation's protocol + convenience + back-compat methods.
- **Docs** — new "Client Methods" section in `backward-compatibility.md`
showing the full generated client (current sync/async then back-compat
sync/async) with one-sentence per-overload comments, the optional
default shown as `default`, the `async` modifier on the current async
method, and the back-compat delegating call using named arguments.
Includes a note verifying [Azure SDK parameter
ordering](https://azure.github.io/azure-sdk/dotnet_implementation.html#parameter-presence-and-ordering)
and a note explaining why the async back-compat overload returns the
`Task` directly without `await`.
### Example
Previous contract:
```csharp
public virtual ClientResult GetData(int p1, BinaryContent body, RequestOptions options = null);
public virtual Task<ClientResult> GetDataAsync(int p1, BinaryContent body, RequestOptions options = null);
```
Current TypeSpec adds an optional `@query p2?: boolean`. Generated
client:
```csharp
// Current sync method generated from the updated TypeSpec.
public virtual ClientResult GetData(int p1, BinaryContent body, bool? p2 = default, RequestOptions options = null) { /* ... */ }
// Current async method generated from the updated TypeSpec.
public virtual async Task<ClientResult> GetDataAsync(int p1, BinaryContent body, bool? p2 = default, RequestOptions options = null) { /* ... */ }
// Back-compat sync overload matching the previous contract's signature.
[EditorBrowsable(EditorBrowsableState.Never)]
public virtual ClientResult GetData(int p1, BinaryContent body, RequestOptions options)
=> this.GetData(p1: p1, body: body, p2: default, options: options);
// Back-compat async overload matching the previous contract's signature.
[EditorBrowsable(EditorBrowsableState.Never)]
public virtual Task<ClientResult> GetDataAsync(int p1, BinaryContent body, RequestOptions options)
=> this.GetDataAsync(p1: p1, body: body, p2: default, options: options);
```
Defaults are stripped from the back-compat signature to avoid ambiguous
call sites with the current method.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>1 parent cb8f053 commit 72a95c2
17 files changed
Lines changed: 952 additions & 6 deletions
File tree
- packages/http-client-csharp/generator
- Microsoft.TypeSpec.Generator.ClientModel
- src/Providers
- test/Providers/ClientProviders
- TestData/ClientProviderTests
- BackCompatibility_MultipleNewOptionalNonBodyParametersAdded
- BackCompatibility_NewOptionalBodyParameterDoesNotAddBackCompatOverload
- BackCompatibility_NewOptionalNonBodyParameterAddedWithModelBody
- BackCompatibility_NewOptionalNonBodyParameterAddedWithPathAndHeaderParameters
- BackCompatibility_NewOptionalNonBodyParameterAdded
- BackCompatibility_NewRequiredParameterDoesNotAddBackCompatOverload
- Microsoft.TypeSpec.Generator/src/EmitterRpc
- docs
Lines changed: 163 additions & 6 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1256 | 1256 | | |
1257 | 1257 | | |
1258 | 1258 | | |
| 1259 | + | |
| 1260 | + | |
1259 | 1261 | | |
1260 | 1262 | | |
1261 | | - | |
| 1263 | + | |
1262 | 1264 | | |
1263 | 1265 | | |
1264 | | - | |
| 1266 | + | |
| 1267 | + | |
| 1268 | + | |
| 1269 | + | |
| 1270 | + | |
| 1271 | + | |
| 1272 | + | |
| 1273 | + | |
| 1274 | + | |
| 1275 | + | |
| 1276 | + | |
| 1277 | + | |
1265 | 1278 | | |
1266 | 1279 | | |
1267 | 1280 | | |
1268 | | - | |
| 1281 | + | |
1269 | 1282 | | |
1270 | 1283 | | |
1271 | 1284 | | |
| |||
1290 | 1303 | | |
1291 | 1304 | | |
1292 | 1305 | | |
1293 | | - | |
| 1306 | + | |
1294 | 1307 | | |
1295 | | - | |
1296 | | - | |
1297 | 1308 | | |
1298 | 1309 | | |
1299 | 1310 | | |
| |||
1748 | 1759 | | |
1749 | 1760 | | |
1750 | 1761 | | |
| 1762 | + | |
| 1763 | + | |
| 1764 | + | |
| 1765 | + | |
| 1766 | + | |
| 1767 | + | |
| 1768 | + | |
| 1769 | + | |
| 1770 | + | |
| 1771 | + | |
| 1772 | + | |
| 1773 | + | |
| 1774 | + | |
| 1775 | + | |
| 1776 | + | |
| 1777 | + | |
| 1778 | + | |
| 1779 | + | |
| 1780 | + | |
| 1781 | + | |
| 1782 | + | |
| 1783 | + | |
| 1784 | + | |
| 1785 | + | |
| 1786 | + | |
| 1787 | + | |
| 1788 | + | |
| 1789 | + | |
| 1790 | + | |
| 1791 | + | |
| 1792 | + | |
| 1793 | + | |
| 1794 | + | |
| 1795 | + | |
| 1796 | + | |
| 1797 | + | |
| 1798 | + | |
| 1799 | + | |
| 1800 | + | |
| 1801 | + | |
| 1802 | + | |
| 1803 | + | |
| 1804 | + | |
| 1805 | + | |
| 1806 | + | |
| 1807 | + | |
| 1808 | + | |
| 1809 | + | |
| 1810 | + | |
| 1811 | + | |
| 1812 | + | |
| 1813 | + | |
| 1814 | + | |
| 1815 | + | |
| 1816 | + | |
| 1817 | + | |
| 1818 | + | |
| 1819 | + | |
| 1820 | + | |
| 1821 | + | |
| 1822 | + | |
| 1823 | + | |
| 1824 | + | |
| 1825 | + | |
| 1826 | + | |
| 1827 | + | |
| 1828 | + | |
| 1829 | + | |
| 1830 | + | |
| 1831 | + | |
| 1832 | + | |
| 1833 | + | |
| 1834 | + | |
| 1835 | + | |
| 1836 | + | |
| 1837 | + | |
| 1838 | + | |
| 1839 | + | |
| 1840 | + | |
| 1841 | + | |
| 1842 | + | |
| 1843 | + | |
| 1844 | + | |
| 1845 | + | |
| 1846 | + | |
| 1847 | + | |
| 1848 | + | |
| 1849 | + | |
| 1850 | + | |
| 1851 | + | |
| 1852 | + | |
| 1853 | + | |
| 1854 | + | |
| 1855 | + | |
| 1856 | + | |
| 1857 | + | |
| 1858 | + | |
| 1859 | + | |
| 1860 | + | |
| 1861 | + | |
| 1862 | + | |
| 1863 | + | |
| 1864 | + | |
| 1865 | + | |
| 1866 | + | |
| 1867 | + | |
| 1868 | + | |
| 1869 | + | |
| 1870 | + | |
| 1871 | + | |
| 1872 | + | |
| 1873 | + | |
| 1874 | + | |
| 1875 | + | |
| 1876 | + | |
| 1877 | + | |
| 1878 | + | |
| 1879 | + | |
| 1880 | + | |
| 1881 | + | |
| 1882 | + | |
| 1883 | + | |
| 1884 | + | |
| 1885 | + | |
| 1886 | + | |
| 1887 | + | |
| 1888 | + | |
| 1889 | + | |
| 1890 | + | |
| 1891 | + | |
| 1892 | + | |
| 1893 | + | |
| 1894 | + | |
| 1895 | + | |
| 1896 | + | |
| 1897 | + | |
| 1898 | + | |
| 1899 | + | |
| 1900 | + | |
| 1901 | + | |
| 1902 | + | |
| 1903 | + | |
| 1904 | + | |
| 1905 | + | |
| 1906 | + | |
| 1907 | + | |
1751 | 1908 | | |
1752 | 1909 | | |
0 commit comments