@@ -236,12 +236,20 @@ private BoundForEachStatement BindForEachPartsWorker(BindingDiagnosticBag diagno
236236 {
237237 originalBinder . CheckImplicitThisCopyInReadOnlyMember ( collectionExpr , getEnumeratorMethod , diagnostics ) ;
238238
239- if ( getEnumeratorMethod . IsExtensionMethod && ! hasErrors )
239+ if ( ! hasErrors )
240240 {
241- var messageId = IsAsync ? MessageID . IDS_FeatureExtensionGetAsyncEnumerator : MessageID . IDS_FeatureExtensionGetEnumerator ;
242- messageId . CheckFeatureAvailability ( diagnostics , Compilation , collectionExpr . Syntax . Location ) ;
241+ if ( getEnumeratorMethod . IsExtensionMethod )
242+ {
243+ var messageId = IsAsync ? MessageID . IDS_FeatureExtensionGetAsyncEnumerator : MessageID . IDS_FeatureExtensionGetEnumerator ;
244+ messageId . CheckFeatureAvailability ( diagnostics , Compilation , collectionExpr . Syntax . Location ) ;
243245
244- if ( getEnumeratorMethod . ParameterRefKinds is { IsDefault : false } refKinds && refKinds [ 0 ] == RefKind . Ref )
246+ if ( getEnumeratorMethod . ParameterRefKinds is { IsDefault : false } refKinds && refKinds [ 0 ] == RefKind . Ref )
247+ {
248+ Error ( diagnostics , ErrorCode . ERR_RefLvalueExpected , collectionExpr . Syntax ) ;
249+ hasErrors = true ;
250+ }
251+ }
252+ else if ( getEnumeratorMethod . GetIsNewExtensionMember ( ) && getEnumeratorMethod . ContainingType . ExtensionParameter . RefKind == RefKind . Ref ) // PROTOTYPE: add test coverage for 'ref readonly' and 'in'
245253 {
246254 Error ( diagnostics , ErrorCode . ERR_RefLvalueExpected , collectionExpr . Syntax ) ;
247255 hasErrors = true ;
@@ -570,7 +578,8 @@ private BoundForEachStatement BindForEachPartsWorker(BindingDiagnosticBag diagno
570578 ( collectionConversionClassification . IsImplicit &&
571579 ( IsIEnumerable ( builder . CollectionType ) ||
572580 IsIEnumerableT ( builder . CollectionType . OriginalDefinition , IsAsync , Compilation ) ||
573- builder . GetEnumeratorInfo . Method . IsExtensionMethod ) ) ||
581+ builder . GetEnumeratorInfo . Method . IsExtensionMethod ||
582+ builder . GetEnumeratorInfo . Method . GetIsNewExtensionMember ( ) ) ) ||
574583 // For compat behavior, we can enumerate over System.String even if it's not IEnumerable. That will
575584 // result in an explicit reference conversion in the bound nodes, but that conversion won't be emitted.
576585 ( collectionConversionClassification . Kind == ConversionKind . ExplicitReference && collectionExpr . Type . SpecialType == SpecialType . System_String ) ) ;
@@ -861,9 +870,9 @@ private EnumeratorResult GetEnumeratorInfoCore(SyntaxNode syntax, SyntaxNode col
861870
862871#if DEBUG
863872 Debug . Assert ( span == originalSpan ) ;
864- Debug . Assert ( ! builder . ViaExtensionMethod || builder . GetEnumeratorInfo . Method . IsExtensionMethod ) ;
873+ Debug . Assert ( ! builder . ViaExtensionMethod || builder . GetEnumeratorInfo . Method . IsExtensionMethod || builder . GetEnumeratorInfo . Method . GetIsNewExtensionMember ( ) ) ;
865874#endif
866- if ( ! builder . ViaExtensionMethod &&
875+ if ( ! builder . ViaExtensionMethod && // PROTOTYPE: Add test coverage for new extensions
867876 ( ( result is EnumeratorResult . Succeeded && builder . ElementTypeWithAnnotations . Equals ( elementField . TypeWithAnnotations , TypeCompareKind . AllIgnoreOptions ) &&
868877 builder . CurrentPropertyGetter ? . RefKind == ( wellKnownSpan == WellKnownType . System_ReadOnlySpan_T ? RefKind . RefReadOnly : RefKind . Ref ) ) ||
869878 result is EnumeratorResult . FailedAndReported ) )
@@ -908,7 +917,7 @@ private EnumeratorResult GetEnumeratorInfoCore(SyntaxNode syntax, SyntaxNode col
908917#if DEBUG
909918 Debug . Assert ( collectionExpr == originalCollectionExpr ||
910919 ( originalCollectionExpr . Type ? . IsNullableType ( ) == true && originalCollectionExpr . Type . StrippedType ( ) . Equals ( collectionExpr . Type , TypeCompareKind . AllIgnoreOptions ) ) ) ;
911- Debug . Assert ( ! builder . ViaExtensionMethod || builder . GetEnumeratorInfo . Method . IsExtensionMethod ) ;
920+ Debug . Assert ( ! builder . ViaExtensionMethod || builder . GetEnumeratorInfo . Method . IsExtensionMethod || builder . GetEnumeratorInfo . Method . GetIsNewExtensionMember ( ) ) ;
912921#endif
913922
914923 return result ;
@@ -1019,12 +1028,26 @@ EnumeratorResult createPatternBasedEnumeratorResult(ref ForEachEnumeratorInfo.Bu
10191028 {
10201029 Debug . Assert ( ( object ) builder . GetEnumeratorInfo != null ) ;
10211030
1022- Debug . Assert ( ! ( viaExtensionMethod && builder . GetEnumeratorInfo . Method . Parameters . IsDefaultOrEmpty ) ) ;
1031+ Debug . Assert ( ! ( viaExtensionMethod && builder . GetEnumeratorInfo . Method . IsExtensionMethod && builder . GetEnumeratorInfo . Method . Parameters . IsDefaultOrEmpty ) ) ;
1032+ Debug . Assert ( ! ( viaExtensionMethod && ! builder . GetEnumeratorInfo . Method . IsExtensionMethod && ! builder . GetEnumeratorInfo . Method . GetIsNewExtensionMember ( ) ) ) ;
10231033
10241034 builder . ViaExtensionMethod = viaExtensionMethod ;
1025- builder . CollectionType = viaExtensionMethod
1026- ? builder . GetEnumeratorInfo . Method . Parameters [ 0 ] . Type
1027- : collectionExpr . Type ;
1035+
1036+ if ( viaExtensionMethod )
1037+ {
1038+ if ( builder . GetEnumeratorInfo . Method . IsExtensionMethod )
1039+ {
1040+ builder . CollectionType = builder . GetEnumeratorInfo . Method . Parameters [ 0 ] . Type ;
1041+ }
1042+ else
1043+ {
1044+ builder . CollectionType = builder . GetEnumeratorInfo . Method . ContainingType . ExtensionParameter . Type ;
1045+ }
1046+ }
1047+ else
1048+ {
1049+ builder . CollectionType = collectionExpr . Type ;
1050+ }
10281051
10291052 if ( SatisfiesForEachPattern ( syntax , collectionSyntax , ref builder , isAsync , diagnostics ) )
10301053 {
@@ -1200,7 +1223,7 @@ private void GetDisposalInfoForEnumerator(SyntaxNode syntax, ref ForEachEnumerat
12001223 MethodSymbol patternDisposeMethod = TryFindDisposePatternMethod ( receiver , syntax , isAsync , patternDiagnostics , out bool expanded ) ;
12011224 if ( patternDisposeMethod is object )
12021225 {
1203- Debug . Assert ( ! patternDisposeMethod . IsExtensionMethod ) ;
1226+ Debug . Assert ( ! patternDisposeMethod . IsExtensionMethod && ! patternDisposeMethod . GetIsNewExtensionMember ( ) ) ;
12041227 Debug . Assert ( patternDisposeMethod . ParameterRefKinds . IsDefaultOrEmpty ||
12051228 patternDisposeMethod . ParameterRefKinds . All ( static refKind => refKind is RefKind . None or RefKind . In or RefKind . RefReadOnlyParameter ) ) ;
12061229
@@ -1522,6 +1545,8 @@ private MethodArgumentInfo FindForEachPatternMethodViaExtension(SyntaxNode synta
15221545 {
15231546 var result = overloadResolutionResult . ValidResult . Member ;
15241547
1548+ Debug . Assert ( result . IsExtensionMethod || result . GetIsNewExtensionMember ( ) ) ;
1549+
15251550 if ( result . CallsAreOmitted ( syntax . SyntaxTree ) )
15261551 {
15271552 // Calls to this method are omitted in the current syntax tree, i.e it is either a partial method with no implementation part OR a conditional method whose condition is not true in this source file.
@@ -1531,28 +1556,44 @@ private MethodArgumentInfo FindForEachPatternMethodViaExtension(SyntaxNode synta
15311556 return null ;
15321557 }
15331558
1534- CompoundUseSiteInfo < AssemblySymbol > useSiteInfo = GetNewCompoundUseSiteInfo ( diagnostics ) ;
1535- var collectionConversion = this . Conversions . ClassifyConversionFromExpression ( collectionExpr , result . Parameters [ 0 ] . Type , isChecked : CheckOverflowAtRuntime , ref useSiteInfo ) ;
1536- diagnostics . Add ( syntax , useSiteInfo ) ;
1559+ MethodArgumentInfo info ;
1560+ bool expanded = overloadResolutionResult . ValidResult . Result . Kind == MemberResolutionKind . ApplicableInExpandedForm ;
15371561
1538- // Unconditionally convert here, to match what we set the ConvertedExpression to in the main BoundForEachStatement node.
1539- Debug . Assert ( ! collectionConversion . IsUserDefined ) ;
1540- collectionExpr = new BoundConversion (
1541- collectionExpr . Syntax ,
1542- collectionExpr ,
1543- collectionConversion ,
1544- @checked : CheckOverflowAtRuntime ,
1545- explicitCastInCode : false ,
1546- conversionGroupOpt : null ,
1547- ConstantValue . NotAvailable ,
1548- result . Parameters [ 0 ] . Type ) ;
1562+ if ( result . IsExtensionMethod )
1563+ {
1564+ CompoundUseSiteInfo < AssemblySymbol > useSiteInfo = GetNewCompoundUseSiteInfo ( diagnostics ) ;
1565+ var collectionConversion = this . Conversions . ClassifyConversionFromExpression ( collectionExpr , result . Parameters [ 0 ] . Type , isChecked : CheckOverflowAtRuntime , ref useSiteInfo ) ;
1566+ diagnostics . Add ( syntax , useSiteInfo ) ;
1567+
1568+ // Unconditionally convert here, to match what we set the ConvertedExpression to in the main BoundForEachStatement node.
1569+ Debug . Assert ( ! collectionConversion . IsUserDefined ) ;
1570+ collectionExpr = new BoundConversion (
1571+ collectionExpr . Syntax ,
1572+ collectionExpr ,
1573+ collectionConversion ,
1574+ @checked : CheckOverflowAtRuntime ,
1575+ explicitCastInCode : false ,
1576+ conversionGroupOpt : null ,
1577+ ConstantValue . NotAvailable ,
1578+ result . Parameters [ 0 ] . Type ) ;
1579+
1580+ info = BindDefaultArguments (
1581+ result ,
1582+ collectionExpr ,
1583+ expanded : expanded ,
1584+ collectionExpr . Syntax ,
1585+ diagnostics ) ;
1586+ }
1587+ else
1588+ {
1589+ info = BindDefaultArguments (
1590+ result ,
1591+ extensionReceiverOpt : null ,
1592+ expanded : expanded ,
1593+ collectionExpr . Syntax ,
1594+ diagnostics ) ;
1595+ }
15491596
1550- var info = BindDefaultArguments (
1551- result ,
1552- collectionExpr ,
1553- expanded : overloadResolutionResult . ValidResult . Result . Kind == MemberResolutionKind . ApplicableInExpandedForm ,
1554- collectionExpr . Syntax ,
1555- diagnostics ) ;
15561597 methodGroupResolutionResult . Free ( ) ;
15571598 analyzedArguments . Free ( ) ;
15581599 return info ;
0 commit comments