@@ -1256,16 +1256,29 @@ protected override ScmMethodProvider[] BuildMethods()
12561256
12571257 protected sealed override IReadOnlyList < MethodProvider > BuildMethodsForBackCompatibility ( IEnumerable < MethodProvider > originalMethods )
12581258 {
1259+ List < MethodProvider > materializedMethods = [ .. originalMethods ] ;
1260+
12591261 if ( LastContractView ? . Methods == null || LastContractView . Methods . Count == 0 )
12601262 {
1261- return [ .. originalMethods ] ;
1263+ return materializedMethods ;
12621264 }
12631265
1264- var currentMethodSignatures = BuildCurrentMethodSignatures ( originalMethods ) ;
1266+ var currentMethodSignatures = BuildCurrentMethodSignatures ( materializedMethods ) ;
1267+
1268+ ProcessBackCompatForParameterReordering ( materializedMethods , currentMethodSignatures ) ;
1269+ ProcessBackCompatForNewOptionalParameters ( materializedMethods , currentMethodSignatures ) ;
1270+
1271+ return materializedMethods ;
1272+ }
1273+
1274+ private void ProcessBackCompatForParameterReordering (
1275+ IList < MethodProvider > materializedMethods ,
1276+ Dictionary < MethodSignature , MethodProvider > currentMethodSignatures )
1277+ {
12651278 var updatedSignatureToOriginal = new Dictionary < MethodSignature , MethodSignature > ( MethodSignature . MethodSignatureComparer ) ;
12661279 var methodsWithReorderedParams = new List < MethodProvider > ( ) ;
12671280
1268- foreach ( var previousMethod in LastContractView . Methods )
1281+ foreach ( var previousMethod in LastContractView ! . Methods )
12691282 {
12701283 if ( ! ShouldProcessMethodForBackCompat ( previousMethod . Signature , currentMethodSignatures ) )
12711284 {
@@ -1290,10 +1303,8 @@ protected sealed override IReadOnlyList<MethodProvider> BuildMethodsForBackCompa
12901303
12911304 if ( methodsWithReorderedParams . Count > 0 )
12921305 {
1293- UpdateConvenienceMethodsForBackCompat ( originalMethods , methodsWithReorderedParams , updatedSignatureToOriginal ) ;
1306+ UpdateConvenienceMethodsForBackCompat ( materializedMethods , methodsWithReorderedParams , updatedSignatureToOriginal ) ;
12941307 }
1295-
1296- return [ .. originalMethods ] ;
12971308 }
12981309
12991310 private Dictionary < MethodSignature , MethodProvider > BuildCurrentMethodSignatures ( IEnumerable < MethodProvider > originalMethods )
@@ -1748,5 +1759,151 @@ private static void UpdateXmlDocProviderForParamReorder(
17481759 xmlDocs . Update ( parameters : reorderedParamDocs ) ;
17491760 }
17501761 }
1762+
1763+ private void ProcessBackCompatForNewOptionalParameters (
1764+ List < MethodProvider > methods ,
1765+ Dictionary < MethodSignature , MethodProvider > currentMethodSignatures )
1766+ {
1767+ var currentMethodsByName = new Dictionary < string , List < MethodProvider > > ( ) ;
1768+ foreach ( var method in currentMethodSignatures . Values )
1769+ {
1770+ if ( method is ScmMethodProvider { Kind : ScmMethodKind . CreateRequest } )
1771+ {
1772+ continue ;
1773+ }
1774+
1775+ if ( ! currentMethodsByName . TryGetValue ( method . Signature . Name , out var list ) )
1776+ {
1777+ list = [ ] ;
1778+ currentMethodsByName [ method . Signature . Name ] = list ;
1779+ }
1780+ list . Add ( method ) ;
1781+ }
1782+
1783+ foreach ( var previousMethod in LastContractView ! . Methods )
1784+ {
1785+ var previousSignature = previousMethod . Signature ;
1786+
1787+ if ( ! previousSignature . Modifiers . HasFlag ( MethodSignatureModifiers . Public ) &&
1788+ ! previousSignature . Modifiers . HasFlag ( MethodSignatureModifiers . Protected ) )
1789+ {
1790+ continue ;
1791+ }
1792+
1793+ if ( currentMethodSignatures . ContainsKey ( previousSignature ) ||
1794+ ! currentMethodsByName . TryGetValue ( previousSignature . Name , out var candidates ) )
1795+ {
1796+ continue ;
1797+ }
1798+
1799+ ScmMethodProvider ? matchedCurrent = null ;
1800+ foreach ( var candidate in candidates )
1801+ {
1802+ if ( candidate is ScmMethodProvider { Kind : ScmMethodKind . Convenience or ScmMethodKind . Protocol } scmCandidate &&
1803+ HasNewOptionalNonBodyParametersOnly ( previousSignature , scmCandidate . Signature ) )
1804+ {
1805+ matchedCurrent = scmCandidate ;
1806+ break ;
1807+ }
1808+ }
1809+
1810+ if ( matchedCurrent is null )
1811+ {
1812+ continue ;
1813+ }
1814+
1815+ var overload = BuildBackCompatOverloadForNewOptionalParameters ( previousMethod , matchedCurrent ) ;
1816+ if ( overload == null || ! currentMethodSignatures . TryAdd ( overload . Signature , overload ) )
1817+ {
1818+ continue ;
1819+ }
1820+
1821+ methods . Add ( overload ) ;
1822+ CodeModelGenerator . Instance . Emitter . Debug (
1823+ $ "Added back-compat overload for '{ Name } .{ previousSignature . Name } ' to handle new optional parameter(s) introduced relative to the last contract.",
1824+ BackCompatibilityChangeCategory . SvcMethodNewOptionalParameterOverloadAdded ) ;
1825+ }
1826+ }
1827+
1828+ // Returns true when currentSignature contains all parameters of previousSignature in the same
1829+ // relative order, every "extra" parameter is optional, and none of the extras are body parameters.
1830+ private static bool HasNewOptionalNonBodyParametersOnly (
1831+ MethodSignature previousSignature ,
1832+ MethodSignature currentSignature )
1833+ {
1834+ if ( currentSignature . Parameters . Count <= previousSignature . Parameters . Count )
1835+ {
1836+ return false ;
1837+ }
1838+
1839+ if ( previousSignature . ReturnType is null
1840+ ? currentSignature . ReturnType is not null
1841+ : ! previousSignature . ReturnType . AreNamesEqual ( currentSignature . ReturnType ) )
1842+ {
1843+ return false ;
1844+ }
1845+
1846+ // Walk current parameters and ensure previous parameters appear in the same relative order
1847+ // (matched by variable name and type), with every "extra" parameter being optional and non-body.
1848+ int previousIndex = 0 ;
1849+ for ( int currentIndex = 0 ; currentIndex < currentSignature . Parameters . Count ; currentIndex ++ )
1850+ {
1851+ var currentParam = currentSignature . Parameters [ currentIndex ] ;
1852+
1853+ if ( previousIndex < previousSignature . Parameters . Count )
1854+ {
1855+ var previousParam = previousSignature . Parameters [ previousIndex ] ;
1856+ if ( currentParam . Name . ToVariableName ( ) == previousParam . Name . ToVariableName ( ) &&
1857+ currentParam . Type . AreNamesEqual ( previousParam . Type ) )
1858+ {
1859+ previousIndex ++ ;
1860+ continue ;
1861+ }
1862+ }
1863+
1864+ if ( currentParam . DefaultValue is null )
1865+ {
1866+ return false ;
1867+ }
1868+
1869+ if ( currentParam . Location == ParameterLocation . Body )
1870+ {
1871+ return false ;
1872+ }
1873+ }
1874+
1875+ return previousIndex == previousSignature . Parameters . Count ;
1876+ }
1877+
1878+ private ScmMethodProvider ? BuildBackCompatOverloadForNewOptionalParameters (
1879+ MethodProvider previousMethod ,
1880+ ScmMethodProvider currentMethod )
1881+ {
1882+ var previousSignature = previousMethod . Signature ;
1883+ var currentSignature = currentMethod . Signature ;
1884+
1885+ var previousParamsByName = new Dictionary < string , ParameterProvider > ( ) ;
1886+ foreach ( var p in previousSignature . Parameters )
1887+ {
1888+ previousParamsByName . TryAdd ( p . Name , p ) ;
1889+ }
1890+
1891+ var arguments = new List < ValueExpression > ( currentSignature . Parameters . Count ) ;
1892+ foreach ( var currentParam in currentSignature . Parameters )
1893+ {
1894+ ValueExpression value = previousParamsByName . TryGetValue ( currentParam . Name , out var prevParam )
1895+ ? prevParam
1896+ : ( currentParam . DefaultValue ?? Default ) ;
1897+ arguments . Add ( PositionalReference ( currentParam . Name , value ) ) ;
1898+ }
1899+
1900+ return new ScmMethodProvider (
1901+ signature : MethodSignatureHelper . BuildBackCompatMethodSignature ( previousSignature , hideMethod : true ) ,
1902+ bodyStatements : Return ( This . Invoke ( currentSignature . Name , arguments ) ) ,
1903+ enclosingType : this ,
1904+ methodKind : currentMethod . Kind ,
1905+ xmlDocProvider : previousMethod . XmlDocs ,
1906+ serviceMethod : currentMethod . ServiceMethod ) ;
1907+ }
17511908 }
17521909}
0 commit comments