@@ -43,9 +43,17 @@ private BoundExpression RewriteCollectionExpressionConversion(Conversion convers
43
43
switch ( collectionTypeKind )
44
44
{
45
45
case CollectionExpressionTypeKind . ImplementsIEnumerable :
46
- if ( useListOptimization ( _compilation , node , out var listElementType ) )
46
+ if ( ConversionsBase . IsSpanOrListType ( _compilation , node . Type , WellKnownType . System_Collections_Generic_List_T , out var listElementType ) )
47
47
{
48
- return CreateAndPopulateList ( node , listElementType , node . Elements . SelectAsArray ( static ( element , node ) => unwrapListElement ( node , element ) , node ) ) ;
48
+ if ( TryRewriteSingleElementSpreadToList ( node , listElementType , out var result ) )
49
+ {
50
+ return result ;
51
+ }
52
+
53
+ if ( useListOptimization ( _compilation , node ) )
54
+ {
55
+ return CreateAndPopulateList ( node , listElementType , node . Elements . SelectAsArray ( static ( element , node ) => unwrapListElement ( node , element ) , node ) ) ;
56
+ }
49
57
}
50
58
return VisitCollectionInitializerCollectionExpression ( node , node . Type ) ;
51
59
case CollectionExpressionTypeKind . Array :
@@ -88,12 +96,8 @@ private BoundExpression RewriteCollectionExpressionConversion(Conversion convers
88
96
89
97
// If the collection type is List<T> and items are added using the expected List<T>.Add(T) method,
90
98
// then construction can be optimized to use CollectionsMarshal methods.
91
- static bool useListOptimization ( CSharpCompilation compilation , BoundCollectionExpression node , out TypeWithAnnotations elementType )
99
+ static bool useListOptimization ( CSharpCompilation compilation , BoundCollectionExpression node )
92
100
{
93
- if ( ! ConversionsBase . IsSpanOrListType ( compilation , node . Type , WellKnownType . System_Collections_Generic_List_T , out elementType ) )
94
- {
95
- return false ;
96
- }
97
101
var elements = node . Elements ;
98
102
if ( elements . Length == 0 )
99
103
{
@@ -151,6 +155,62 @@ static BoundNode unwrapListElement(BoundCollectionExpression node, BoundNode ele
151
155
}
152
156
}
153
157
158
+ // If we have something like `List<int> l = [.. someEnumerable]`
159
+ // try rewrite it using `Enumerable.ToList` member if possible
160
+ private bool TryRewriteSingleElementSpreadToList ( BoundCollectionExpression node , TypeWithAnnotations listElementType , [ NotNullWhen ( true ) ] out BoundExpression ? result )
161
+ {
162
+ result = null ;
163
+
164
+ if ( node . Elements is not [ BoundCollectionExpressionSpreadElement singleSpread ] )
165
+ {
166
+ return false ;
167
+ }
168
+
169
+ if ( ! TryGetWellKnownTypeMember ( node . Syntax , WellKnownMember . System_Linq_Enumerable__ToList , out MethodSymbol ? toListGeneric , isOptional : true ) )
170
+ {
171
+ return false ;
172
+ }
173
+
174
+ var toListOfElementType = toListGeneric . Construct ( [ listElementType ] ) ;
175
+
176
+ Debug . Assert ( singleSpread . Expression . Type is not null ) ;
177
+
178
+ if ( ! ShouldUseAddRangeOrToListMethod ( singleSpread . Expression . Type , toListOfElementType . Parameters [ 0 ] . Type , singleSpread . EnumeratorInfoOpt ? . GetEnumeratorInfo . Method ) )
179
+ {
180
+ return false ;
181
+ }
182
+
183
+ var rewrittenSpreadExpression = VisitExpression ( singleSpread . Expression ) ;
184
+ result = _factory . Call ( receiver : null , toListOfElementType , rewrittenSpreadExpression ) ;
185
+ return true ;
186
+ }
187
+
188
+ private bool ShouldUseAddRangeOrToListMethod ( TypeSymbol spreadType , TypeSymbol targetEnumerableType , MethodSymbol ? getEnumeratorMethod )
189
+ {
190
+ Debug . Assert ( targetEnumerableType . OriginalDefinition == ( object ) _compilation . GetSpecialType ( SpecialType . System_Collections_Generic_IEnumerable_T ) ) ;
191
+
192
+ var discardedUseSiteInfo = CompoundUseSiteInfo < AssemblySymbol > . Discarded ;
193
+
194
+ Conversion conversion ;
195
+
196
+ // If collection has a struct enumerator but doesn't implement ICollection<T>
197
+ // then manual `foreach` is always more efficient then using `ToList` or `AddRange` methods
198
+ if ( getEnumeratorMethod ? . ReturnType . IsValueType == true )
199
+ {
200
+ var iCollectionOfTType = _compilation . GetSpecialType ( SpecialType . System_Collections_Generic_ICollection_T ) ;
201
+ var iCollectionOfElementType = iCollectionOfTType . Construct ( ( ( NamedTypeSymbol ) targetEnumerableType ) . TypeArgumentsWithAnnotationsNoUseSiteDiagnostics ) ;
202
+
203
+ conversion = _compilation . Conversions . ClassifyBuiltInConversion ( spreadType , iCollectionOfElementType , isChecked : false , ref discardedUseSiteInfo ) ;
204
+ if ( conversion . Kind is not ( ConversionKind . Identity or ConversionKind . ImplicitReference ) )
205
+ {
206
+ return false ;
207
+ }
208
+ }
209
+
210
+ conversion = _compilation . Conversions . ClassifyImplicitConversionFromType ( spreadType , targetEnumerableType , ref discardedUseSiteInfo ) ;
211
+ return conversion . Kind is ConversionKind . Identity or ConversionKind . ImplicitReference ;
212
+ }
213
+
154
214
private static bool CanOptimizeSingleSpreadAsCollectionBuilderArgument ( BoundCollectionExpression node , [ NotNullWhen ( true ) ] out BoundExpression ? spreadExpression )
155
215
{
156
216
spreadExpression = null ;
@@ -1064,42 +1124,18 @@ private BoundExpression CreateAndPopulateList(BoundCollectionExpression node, Ty
1064
1124
} ,
1065
1125
tryOptimizeSpreadElement : ( ArrayBuilder < BoundExpression > sideEffects , BoundExpression listTemp , BoundCollectionExpressionSpreadElement spreadElement , BoundExpression rewrittenSpreadOperand ) =>
1066
1126
{
1127
+ Debug . Assert ( rewrittenSpreadOperand . Type is not null ) ;
1128
+
1067
1129
if ( addRangeMethod is null )
1068
1130
return false ;
1069
1131
1070
- Conversion conversion ;
1071
-
1072
- if ( spreadElement . EnumeratorInfoOpt is { } enumeratorInfo )
1132
+ if ( ! ShouldUseAddRangeOrToListMethod ( rewrittenSpreadOperand . Type , addRangeMethod . Parameters [ 0 ] . Type , spreadElement . EnumeratorInfoOpt ? . GetEnumeratorInfo . Method ) )
1073
1133
{
1074
- var iCollectionOfTType = _compilation . GetSpecialType ( SpecialType . System_Collections_Generic_ICollection_T ) ;
1075
- var iCollectionOfElementType = iCollectionOfTType . Construct ( enumeratorInfo . ElementType ) ;
1076
- var discardedUseSiteInfo = CompoundUseSiteInfo < AssemblySymbol > . Discarded ;
1077
-
1078
- // If collection has a struct enumerator but doesn't implement ICollection<T>
1079
- // then manual `foreach` is always more efficient then using `AddRange` method
1080
- if ( enumeratorInfo . GetEnumeratorInfo . Method . ReturnType . IsValueType )
1081
- {
1082
- conversion = _compilation . Conversions . ClassifyBuiltInConversion ( enumeratorInfo . CollectionType , iCollectionOfElementType , isChecked : false , ref discardedUseSiteInfo ) ;
1083
- if ( ! ( conversion . Kind is ConversionKind . Identity or ConversionKind . ImplicitReference ) )
1084
- {
1085
- return false ;
1086
- }
1087
- }
1088
- }
1089
-
1090
- var type = rewrittenSpreadOperand . Type ! ;
1091
-
1092
- var useSiteInfo = GetNewCompoundUseSiteInfo ( ) ;
1093
- conversion = _compilation . Conversions . ClassifyConversionFromType ( type , addRangeMethod . Parameters [ 0 ] . Type , isChecked : false , ref useSiteInfo ) ;
1094
- _diagnostics . Add ( rewrittenSpreadOperand . Syntax , useSiteInfo ) ;
1095
- if ( conversion . Kind is ConversionKind . Identity or ConversionKind . ImplicitReference )
1096
- {
1097
- conversion . MarkUnderlyingConversionsCheckedRecursive ( ) ;
1098
- sideEffects . Add ( _factory . Call ( listTemp , addRangeMethod , rewrittenSpreadOperand ) ) ;
1099
- return true ;
1134
+ return false ;
1100
1135
}
1101
1136
1102
- return false ;
1137
+ sideEffects . Add ( _factory . Call ( listTemp , addRangeMethod , rewrittenSpreadOperand ) ) ;
1138
+ return true ;
1103
1139
} ) ;
1104
1140
}
1105
1141
0 commit comments