-
Notifications
You must be signed in to change notification settings - Fork 4.1k
/
Copy pathLocalRewriter_CollectionExpression.cs
1314 lines (1161 loc) · 68.7 KB
/
LocalRewriter_CollectionExpression.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.CodeGen;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Collections;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp
{
internal sealed partial class LocalRewriter
{
public override BoundNode? VisitCollectionExpression(BoundCollectionExpression node)
{
// BoundCollectionExpression should be handled in VisitConversion().
throw ExceptionUtilities.Unreachable();
}
public override BoundNode? VisitUnconvertedCollectionExpression(BoundUnconvertedCollectionExpression node)
{
throw ExceptionUtilities.Unreachable();
}
private BoundExpression RewriteCollectionExpressionConversion(Conversion conversion, BoundCollectionExpression node)
{
Debug.Assert(conversion.Kind == ConversionKind.CollectionExpression);
Debug.Assert(!_inExpressionLambda);
Debug.Assert(_additionalLocals is { });
Debug.Assert(node.Type is { });
var previousSyntax = _factory.Syntax;
_factory.Syntax = node.Syntax;
try
{
var collectionTypeKind = conversion.GetCollectionExpressionTypeKind(out var elementType, out _, out _);
switch (collectionTypeKind)
{
case CollectionExpressionTypeKind.ImplementsIEnumerable:
if (useListOptimization(_compilation, node, out var listElementType))
{
return CreateAndPopulateList(node, listElementType, node.Elements.SelectAsArray(static (element, node) => unwrapListElement(node, element), node));
}
return VisitCollectionInitializerCollectionExpression(node, node.Type);
case CollectionExpressionTypeKind.Array:
case CollectionExpressionTypeKind.Span:
case CollectionExpressionTypeKind.ReadOnlySpan:
Debug.Assert(elementType is { });
return VisitArrayOrSpanCollectionExpression(node, collectionTypeKind, node.Type, TypeWithAnnotations.Create(elementType));
case CollectionExpressionTypeKind.CollectionBuilder:
// A few special cases when a collection type is an ImmutableArray<T>
if (ConversionsBase.IsSpanOrListType(_compilation, node.Type, WellKnownType.System_Collections_Immutable_ImmutableArray_T, out var arrayElementType))
{
// For `[]` try to use `ImmutableArray<T>.Empty` singleton if available
if (node.Elements.IsEmpty &&
_compilation.GetWellKnownTypeMember(WellKnownMember.System_Collections_Immutable_ImmutableArray_T__Empty) is FieldSymbol immutableArrayOfTEmpty)
{
var immutableArrayOfTargetCollectionTypeEmpty = immutableArrayOfTEmpty.AsMember((NamedTypeSymbol)node.Type);
return _factory.Field(receiver: null, immutableArrayOfTargetCollectionTypeEmpty);
}
// Otherwise try to optimize construction using `ImmutableCollectionsMarshal.AsImmutableArray`.
// Note, that we skip that path if collection expression is just `[.. readOnlySpan]` of the same element type,
// in such cases it is more efficient to emit a direct call of `ImmutableArray.Create`
if (_compilation.GetWellKnownTypeMember(WellKnownMember.System_Runtime_InteropServices_ImmutableCollectionsMarshal__AsImmutableArray_T) is MethodSymbol asImmutableArray &&
!CanOptimizeSingleSpreadAsCollectionBuilderArgument(node, out _))
{
return VisitImmutableArrayCollectionExpression(node, arrayElementType, asImmutableArray);
}
}
return VisitCollectionBuilderCollectionExpression(node);
case CollectionExpressionTypeKind.ArrayInterface:
return VisitListInterfaceCollectionExpression(node);
default:
throw ExceptionUtilities.UnexpectedValue(collectionTypeKind);
}
}
finally
{
_factory.Syntax = previousSyntax;
}
// If the collection type is List<T> and items are added using the expected List<T>.Add(T) method,
// then construction can be optimized to use CollectionsMarshal methods.
static bool useListOptimization(CSharpCompilation compilation, BoundCollectionExpression node, out TypeWithAnnotations elementType)
{
if (!ConversionsBase.IsSpanOrListType(compilation, node.Type, WellKnownType.System_Collections_Generic_List_T, out elementType))
{
return false;
}
var elements = node.Elements;
if (elements.Length == 0)
{
return true;
}
var addMethod = (MethodSymbol?)compilation.GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_List_T__Add);
if (addMethod is null)
{
return false;
}
return elements.All(canOptimizeListElement, addMethod);
}
static bool canOptimizeListElement(BoundNode element, MethodSymbol addMethod)
{
BoundExpression expr;
if (element is BoundCollectionExpressionSpreadElement spreadElement)
{
Debug.Assert(spreadElement.IteratorBody is { });
expr = ((BoundExpressionStatement)spreadElement.IteratorBody).Expression;
}
else
{
expr = (BoundExpression)element;
}
if (expr is BoundCollectionElementInitializer collectionInitializer)
{
return addMethod.Equals(collectionInitializer.AddMethod.OriginalDefinition);
}
return false;
}
static BoundNode unwrapListElement(BoundCollectionExpression node, BoundNode element)
{
if (element is BoundCollectionExpressionSpreadElement spreadElement)
{
Debug.Assert(spreadElement.IteratorBody is { });
var iteratorBody = Binder.GetUnderlyingCollectionExpressionElement(node, ((BoundExpressionStatement)spreadElement.IteratorBody).Expression, throwOnErrors: true);
Debug.Assert(iteratorBody is { });
return spreadElement.Update(
spreadElement.Expression,
spreadElement.ExpressionPlaceholder,
spreadElement.Conversion,
spreadElement.EnumeratorInfoOpt,
spreadElement.LengthOrCount,
spreadElement.ElementPlaceholder,
new BoundExpressionStatement(iteratorBody.Syntax, iteratorBody));
}
else
{
var result = Binder.GetUnderlyingCollectionExpressionElement(node, (BoundExpression)element, throwOnErrors: true);
Debug.Assert(result is { });
return result;
}
}
}
private static bool CanOptimizeSingleSpreadAsCollectionBuilderArgument(BoundCollectionExpression node, [NotNullWhen(true)] out BoundExpression? spreadExpression)
{
spreadExpression = null;
if (node is
{
CollectionBuilderMethod: { } builder,
Elements: [BoundCollectionExpressionSpreadElement { Expression: { Type: NamedTypeSymbol spreadType } expr }],
} &&
ConversionsBase.HasIdentityConversion(builder.Parameters[0].Type, spreadType) &&
(!builder.ReturnType.IsRefLikeType || builder.Parameters[0].EffectiveScope == ScopedKind.ScopedValue))
{
spreadExpression = expr;
}
return spreadExpression is not null;
}
private BoundExpression VisitImmutableArrayCollectionExpression(BoundCollectionExpression node, TypeWithAnnotations elementType, MethodSymbol asImmutableArray)
{
var arrayCreation = VisitArrayOrSpanCollectionExpression(
node,
CollectionExpressionTypeKind.Array,
ArrayTypeSymbol.CreateSZArray(_compilation.Assembly, elementType),
elementType);
// ImmutableCollectionsMarshal.AsImmutableArray(arrayCreation)
return _factory.StaticCall(asImmutableArray.Construct(ImmutableArray.Create(elementType)), ImmutableArray.Create(arrayCreation));
}
private BoundExpression VisitArrayOrSpanCollectionExpression(BoundCollectionExpression node, CollectionExpressionTypeKind collectionTypeKind, TypeSymbol collectionType, TypeWithAnnotations elementType)
{
Debug.Assert(!_inExpressionLambda);
Debug.Assert(_additionalLocals is { });
Debug.Assert(node.CollectionCreation is null); // shouldn't have generated a constructor call
Debug.Assert(node.Placeholder is null);
var syntax = node.Syntax;
var elements = node.Elements;
MethodSymbol? spanConstructor = null;
var arrayType = collectionType as ArrayTypeSymbol;
if (arrayType is null)
{
// We're constructing a Span<T> or ReadOnlySpan<T> rather than T[].
var spanType = (NamedTypeSymbol)collectionType;
Debug.Assert(collectionTypeKind is CollectionExpressionTypeKind.Span or CollectionExpressionTypeKind.ReadOnlySpan);
Debug.Assert(spanType.OriginalDefinition.Equals(_compilation.GetWellKnownType(
collectionTypeKind == CollectionExpressionTypeKind.Span ? WellKnownType.System_Span_T : WellKnownType.System_ReadOnlySpan_T), TypeCompareKind.AllIgnoreOptions));
Debug.Assert(elementType.Equals(spanType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0], TypeCompareKind.AllIgnoreOptions));
if (elements.Length == 0)
{
// `default(Span<T>)` is the best way to make empty Spans
return _factory.Default(collectionType);
}
if (collectionTypeKind == CollectionExpressionTypeKind.ReadOnlySpan &&
ShouldUseRuntimeHelpersCreateSpan(node, elementType.Type))
{
// Assert that binding layer agrees with lowering layer about whether this collection-expr will allocate.
Debug.Assert(!IsAllocatingRefStructCollectionExpression(node, collectionTypeKind, elementType.Type, _compilation));
var constructor = ((MethodSymbol)_factory.WellKnownMember(WellKnownMember.System_ReadOnlySpan_T__ctor_Array)).AsMember(spanType);
var rewrittenElements = elements.SelectAsArray(static (element, rewriter) => rewriter.VisitExpression((BoundExpression)element), this);
return _factory.New(constructor, _factory.Array(elementType.Type, rewrittenElements));
}
if (ShouldUseInlineArray(node, _compilation) &&
_additionalLocals is { })
{
Debug.Assert(!IsAllocatingRefStructCollectionExpression(node, collectionTypeKind, elementType.Type, _compilation));
return CreateAndPopulateSpanFromInlineArray(
syntax,
elementType,
elements,
asReadOnlySpan: collectionTypeKind == CollectionExpressionTypeKind.ReadOnlySpan);
}
Debug.Assert(IsAllocatingRefStructCollectionExpression(node, collectionTypeKind, elementType.Type, _compilation));
arrayType = ArrayTypeSymbol.CreateSZArray(_compilation.Assembly, elementType);
spanConstructor = ((MethodSymbol)_factory.WellKnownMember(
collectionTypeKind == CollectionExpressionTypeKind.Span ? WellKnownMember.System_Span_T__ctor_Array : WellKnownMember.System_ReadOnlySpan_T__ctor_Array)!).AsMember(spanType);
}
BoundExpression array;
if (ShouldUseKnownLength(node, out _))
{
array = CreateAndPopulateArray(node, arrayType);
}
else
{
// The array initializer has an unknown length, so we'll create an intermediate List<T> instance.
// https://github.com/dotnet/roslyn/issues/68785: Emit Enumerable.TryGetNonEnumeratedCount() and avoid intermediate List<T> at runtime.
var list = CreateAndPopulateList(node, elementType, elements);
Debug.Assert(list.Type is { });
Debug.Assert(list.Type.OriginalDefinition.Equals(_compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_List_T), TypeCompareKind.AllIgnoreOptions));
var listToArray = ((MethodSymbol)_factory.WellKnownMember(WellKnownMember.System_Collections_Generic_List_T__ToArray)).AsMember((NamedTypeSymbol)list.Type);
array = _factory.Call(list, listToArray);
}
if (spanConstructor is null)
{
return array;
}
Debug.Assert(TypeSymbol.Equals(array.Type, spanConstructor.Parameters[0].Type, TypeCompareKind.AllIgnoreOptions));
return new BoundObjectCreationExpression(syntax, spanConstructor, array);
}
private BoundExpression VisitCollectionInitializerCollectionExpression(BoundCollectionExpression node, TypeSymbol collectionType)
{
Debug.Assert(!_inExpressionLambda);
var elements = node.Elements;
var rewrittenReceiver = VisitExpression(node.CollectionCreation);
Debug.Assert(rewrittenReceiver is { });
// Create a temp for the collection.
BoundAssignmentOperator assignmentToTemp;
BoundLocal temp = _factory.StoreToTemp(rewrittenReceiver, out assignmentToTemp);
var sideEffects = ArrayBuilder<BoundExpression>.GetInstance(elements.Length + 1);
sideEffects.Add(assignmentToTemp);
var placeholder = node.Placeholder;
Debug.Assert(placeholder is { });
AddPlaceholderReplacement(placeholder, temp);
foreach (var element in elements)
{
var rewrittenElement = element is BoundCollectionExpressionSpreadElement spreadElement ?
MakeCollectionExpressionSpreadElement(
spreadElement,
VisitExpression(spreadElement.Expression),
iteratorBody =>
{
var syntax = iteratorBody.Syntax;
var rewrittenValue = rewriteCollectionInitializer(temp, ((BoundExpressionStatement)iteratorBody).Expression);
// MakeCollectionInitializer() may return null if Add() is marked [Conditional].
return rewrittenValue is { } ?
new BoundExpressionStatement(syntax, rewrittenValue) :
new BoundNoOpStatement(syntax, NoOpStatementFlavor.Default);
}) :
rewriteCollectionInitializer(temp, (BoundExpression)element);
if (rewrittenElement != null)
{
sideEffects.Add(rewrittenElement);
}
}
RemovePlaceholderReplacement(placeholder);
return new BoundSequence(
node.Syntax,
ImmutableArray.Create(temp.LocalSymbol),
sideEffects.ToImmutableAndFree(),
temp,
collectionType);
BoundExpression? rewriteCollectionInitializer(BoundLocal rewrittenReceiver, BoundExpression expressionElement)
{
return expressionElement switch
{
BoundCollectionElementInitializer collectionInitializer => MakeCollectionInitializer(rewrittenReceiver, collectionInitializer),
BoundDynamicCollectionElementInitializer dynamicInitializer => MakeDynamicCollectionInitializer(rewrittenReceiver, dynamicInitializer),
var e => throw ExceptionUtilities.UnexpectedValue(e)
};
}
}
private BoundExpression VisitListInterfaceCollectionExpression(BoundCollectionExpression node)
{
Debug.Assert(!_inExpressionLambda);
Debug.Assert(_factory.ModuleBuilderOpt is { });
Debug.Assert(_diagnostics.DiagnosticBag is { });
Debug.Assert(node.Type is NamedTypeSymbol);
Debug.Assert(node.CollectionCreation is null);
Debug.Assert(node.Placeholder is null);
var syntax = node.Syntax;
var collectionType = (NamedTypeSymbol)node.Type;
var elementType = collectionType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics.Single();
var elements = node.Elements;
BoundExpression arrayOrList;
if (collectionType.OriginalDefinition.SpecialType is
SpecialType.System_Collections_Generic_IEnumerable_T or
SpecialType.System_Collections_Generic_IReadOnlyCollection_T or
SpecialType.System_Collections_Generic_IReadOnlyList_T)
{
int numberIncludingLastSpread;
bool useKnownLength = ShouldUseKnownLength(node, out numberIncludingLastSpread);
if (elements.Length == 0)
{
Debug.Assert(numberIncludingLastSpread == 0);
// arrayOrList = Array.Empty<ElementType>();
arrayOrList = CreateEmptyArray(syntax, ArrayTypeSymbol.CreateSZArray(_compilation.Assembly, elementType));
}
else
{
var typeArgs = ImmutableArray.Create(elementType);
var kind = useKnownLength
? numberIncludingLastSpread == 0 && elements.Length == 1 && SynthesizedReadOnlyListTypeSymbol.CanCreateSingleElement(_compilation)
? SynthesizedReadOnlyListKind.SingleElement
: SynthesizedReadOnlyListKind.Array
: SynthesizedReadOnlyListKind.List;
var synthesizedType = _factory.ModuleBuilderOpt.EnsureReadOnlyListTypeExists(syntax, kind: kind, _diagnostics.DiagnosticBag).Construct(typeArgs);
if (synthesizedType.IsErrorType())
{
return BadExpression(node);
}
BoundExpression fieldValue = kind switch
{
// fieldValue = e1;
SynthesizedReadOnlyListKind.SingleElement => this.VisitExpression((BoundExpression)elements.Single()),
// fieldValue = new ElementType[] { e1, ..., eN };
SynthesizedReadOnlyListKind.Array => CreateAndPopulateArray(node, ArrayTypeSymbol.CreateSZArray(_compilation.Assembly, elementType)),
// fieldValue = new List<ElementType> { e1, ..., eN };
SynthesizedReadOnlyListKind.List => CreateAndPopulateList(node, elementType, elements),
var v => throw ExceptionUtilities.UnexpectedValue(v)
};
// arrayOrList = new <>z__ReadOnlyList<ElementType>(fieldValue);
arrayOrList = new BoundObjectCreationExpression(syntax, synthesizedType.Constructors.Single(), fieldValue) { WasCompilerGenerated = true };
}
}
else
{
arrayOrList = CreateAndPopulateList(node, elementType, elements);
}
return _factory.Convert(collectionType, arrayOrList);
}
private BoundExpression VisitCollectionBuilderCollectionExpression(BoundCollectionExpression node)
{
Debug.Assert(!_inExpressionLambda);
Debug.Assert(node.Type is { });
Debug.Assert(node.CollectionCreation is null);
Debug.Assert(node.Placeholder is null);
Debug.Assert(node.CollectionBuilderMethod is { });
Debug.Assert(node.CollectionBuilderInvocationPlaceholder is { });
Debug.Assert(node.CollectionBuilderInvocationConversion is { });
var constructMethod = node.CollectionBuilderMethod;
var spanType = (NamedTypeSymbol)constructMethod.Parameters[0].Type;
Debug.Assert(spanType.OriginalDefinition.Equals(_compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T), TypeCompareKind.AllIgnoreOptions));
var elementType = spanType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0];
// If collection expression is of form `[.. anotherReadOnlySpan]`
// with `anotherReadOnlySpan` being a ReadOnlySpan of the same type as target collection type
// and that span cannot be captured in a returned ref struct
// we can directly use `anotherReadOnlySpan` as collection builder argument and skip the copying assignment.
BoundExpression span = CanOptimizeSingleSpreadAsCollectionBuilderArgument(node, out var spreadExpression)
? spreadExpression
: VisitArrayOrSpanCollectionExpression(node, CollectionExpressionTypeKind.ReadOnlySpan, spanType, elementType);
var invocation = new BoundCall(
node.Syntax,
receiverOpt: null,
initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown,
method: constructMethod,
arguments: ImmutableArray.Create(span),
argumentNamesOpt: default,
argumentRefKindsOpt: default,
isDelegateCall: false,
expanded: false,
invokedAsExtensionMethod: false,
argsToParamsOpt: default,
defaultArguments: default,
resultKind: LookupResultKind.Viable,
type: constructMethod.ReturnType);
var invocationPlaceholder = node.CollectionBuilderInvocationPlaceholder;
AddPlaceholderReplacement(invocationPlaceholder, invocation);
var result = VisitExpression(node.CollectionBuilderInvocationConversion);
RemovePlaceholderReplacement(invocationPlaceholder);
return result;
}
internal static bool IsAllocatingRefStructCollectionExpression(BoundCollectionExpressionBase node, CollectionExpressionTypeKind collectionKind, TypeSymbol? elementType, CSharpCompilation compilation)
{
return collectionKind is CollectionExpressionTypeKind.Span or CollectionExpressionTypeKind.ReadOnlySpan
&& node.Elements.Length > 0
&& elementType is not null
&& !(collectionKind == CollectionExpressionTypeKind.ReadOnlySpan && ShouldUseRuntimeHelpersCreateSpan(node, elementType))
&& !ShouldUseInlineArray(node, compilation);
}
internal static bool ShouldUseRuntimeHelpersCreateSpan(BoundCollectionExpressionBase node, TypeSymbol elementType)
{
return !node.HasSpreadElements(out _, out _) &&
node.Elements.Length > 0 &&
CodeGenerator.IsTypeAllowedInBlobWrapper(elementType.EnumUnderlyingTypeOrSelf().SpecialType) &&
node.Elements.All(e => ((BoundExpression)e).ConstantValueOpt is { });
}
private static bool ShouldUseInlineArray(BoundCollectionExpressionBase node, CSharpCompilation compilation)
{
return !node.HasSpreadElements(out _, out _) &&
node.Elements.Length > 0 &&
compilation.Assembly.RuntimeSupportsInlineArrayTypes;
}
private BoundExpression CreateAndPopulateSpanFromInlineArray(
SyntaxNode syntax,
TypeWithAnnotations elementType,
ImmutableArray<BoundNode> elements,
bool asReadOnlySpan)
{
Debug.Assert(elements.Length > 0);
Debug.Assert(elements.All(e => e is BoundExpression));
Debug.Assert(_factory.ModuleBuilderOpt is { });
Debug.Assert(_diagnostics.DiagnosticBag is { });
Debug.Assert(_compilation.Assembly.RuntimeSupportsInlineArrayTypes);
Debug.Assert(_additionalLocals is { });
int arrayLength = elements.Length;
if (arrayLength == 1
&& _factory.WellKnownMember(asReadOnlySpan
? WellKnownMember.System_ReadOnlySpan_T__ctor_ref_readonly_T
: WellKnownMember.System_Span_T__ctor_ref_T, isOptional: true) is MethodSymbol spanRefConstructor)
{
// Special case: no need to create an InlineArray1 type. Just use a temp of the element type.
var spanType = _factory
.WellKnownType(asReadOnlySpan ? WellKnownType.System_ReadOnlySpan_T : WellKnownType.System_Span_T)
.Construct([elementType]);
var constructor = spanRefConstructor.AsMember(spanType);
var element = VisitExpression((BoundExpression)elements[0]);
var temp = _factory.StoreToTemp(element, out var assignment);
_additionalLocals.Add(temp.LocalSymbol);
var call = _factory.New(constructor, arguments: [temp], argumentRefKinds: [asReadOnlySpan ? RefKindExtensions.StrictIn : RefKind.Ref]);
return _factory.Sequence([assignment], call);
}
var inlineArrayType = _factory.ModuleBuilderOpt.EnsureInlineArrayTypeExists(syntax, _factory, arrayLength, _diagnostics.DiagnosticBag).Construct(ImmutableArray.Create(elementType));
Debug.Assert(inlineArrayType.HasInlineArrayAttribute(out int inlineArrayLength) && inlineArrayLength == arrayLength);
var intType = _factory.SpecialType(SpecialType.System_Int32);
MethodSymbol elementRef = _factory.ModuleBuilderOpt.EnsureInlineArrayElementRefExists(syntax, intType, _diagnostics.DiagnosticBag).
Construct(ImmutableArray.Create(TypeWithAnnotations.Create(inlineArrayType), elementType));
// Create an inline array and assign to a local.
// var tmp = new <>y__InlineArrayN<ElementType>();
BoundAssignmentOperator assignmentToTemp;
BoundLocal inlineArrayLocal = _factory.StoreToTemp(new BoundDefaultExpression(syntax, inlineArrayType), out assignmentToTemp);
var sideEffects = ArrayBuilder<BoundExpression>.GetInstance();
sideEffects.Add(assignmentToTemp);
_additionalLocals.Add(inlineArrayLocal.LocalSymbol);
// Populate the inline array.
// InlineArrayElementRef<<>y__InlineArrayN<ElementType>, ElementType>(ref tmp, 0) = element0;
// InlineArrayElementRef<<>y__InlineArrayN<ElementType>, ElementType>(ref tmp, 1) = element1;
// ...
for (int i = 0; i < arrayLength; i++)
{
var element = VisitExpression((BoundExpression)elements[i]);
var call = _factory.Call(null, elementRef, inlineArrayLocal, _factory.Literal(i), useStrictArgumentRefKinds: true);
var assignment = new BoundAssignmentOperator(syntax, call, element, type: call.Type) { WasCompilerGenerated = true };
sideEffects.Add(assignment);
}
// Get a span to the inline array.
// ... InlineArrayAsReadOnlySpan<<>y__InlineArrayN<ElementType>, ElementType>(in tmp, N)
// or
// ... InlineArrayAsSpan<<>y__InlineArrayN<ElementType>, ElementType>(ref tmp, N)
MethodSymbol inlineArrayAsSpan = asReadOnlySpan ?
_factory.ModuleBuilderOpt.EnsureInlineArrayAsReadOnlySpanExists(syntax, _factory.WellKnownType(WellKnownType.System_ReadOnlySpan_T), intType, _diagnostics.DiagnosticBag) :
_factory.ModuleBuilderOpt.EnsureInlineArrayAsSpanExists(syntax, _factory.WellKnownType(WellKnownType.System_Span_T), intType, _diagnostics.DiagnosticBag);
inlineArrayAsSpan = inlineArrayAsSpan.Construct(ImmutableArray.Create(TypeWithAnnotations.Create(inlineArrayType), elementType));
var span = _factory.Call(
receiver: null,
inlineArrayAsSpan,
inlineArrayLocal,
_factory.Literal(arrayLength),
useStrictArgumentRefKinds: true);
Debug.Assert(span.Type is { });
return new BoundSequence(
syntax,
locals: ImmutableArray<LocalSymbol>.Empty,
sideEffects.ToImmutableAndFree(),
span,
span.Type);
}
/// <summary>
/// Returns true if the collection expression has a known length and that length should be used
/// in the lowered code to avoid resizing the collection instance, or allocating intermediate storage,
/// during construction. If the collection expression includes spreads, the spreads must be countable.
/// The caller will need to delay adding elements and iterating spreads until the last spread has been
/// evaluated, to determine the overall length of the collection. Therefore, this method only returns
/// true if the number of preceding elements is below a maximum.
/// </summary>
private static bool ShouldUseKnownLength(BoundCollectionExpression node, out int numberIncludingLastSpread)
{
// The maximum number of collection expression elements that will be rewritten into temporaries.
// The value is arbitrary but small to avoid significant stack size for the containing method
// while also allowing using the known length for common cases. In particular, this allows
// using the known length for simple concatenation of two elements [e, ..y] or [..x, ..y].
// Temporaries are only needed up to the last spread, so this also allows [..x, e1, e2, ...].
const int maxTemporaries = 3;
int n;
bool hasKnownLength;
node.HasSpreadElements(out n, out hasKnownLength);
if (hasKnownLength && n <= maxTemporaries)
{
numberIncludingLastSpread = n;
return true;
}
numberIncludingLastSpread = 0;
return false;
}
/// <summary>
/// Create and populate an array from a collection expression where the
/// collection has a known length, although possibly including spreads.
/// </summary>
private BoundExpression CreateAndPopulateArray(BoundCollectionExpression node, ArrayTypeSymbol arrayType)
{
var syntax = node.Syntax;
var elements = node.Elements;
int numberIncludingLastSpread;
if (!ShouldUseKnownLength(node, out numberIncludingLastSpread))
{
// Should have been handled by the caller.
throw ExceptionUtilities.UnexpectedValue(node);
}
// Collection-expr is of the form `[..spreadExpression]`, where 'spreadExpression' has same element type as the target collection.
// Optimize to `spreadExpression.ToArray()` if possible.
if (node is { Elements: [BoundCollectionExpressionSpreadElement { Expression: { } spreadExpression } spreadElement] }
&& spreadElement.IteratorBody is BoundExpressionStatement expressionStatement
&& expressionStatement.Expression is not BoundConversion)
{
var spreadTypeOriginalDefinition = spreadExpression.Type!.OriginalDefinition;
if (tryGetToArrayMethod(spreadTypeOriginalDefinition, WellKnownType.System_Collections_Generic_List_T, WellKnownMember.System_Collections_Generic_List_T__ToArray, out MethodSymbol? listToArrayMethod))
{
var rewrittenSpreadExpression = VisitExpression(spreadExpression);
return _factory.Call(rewrittenSpreadExpression, listToArrayMethod.AsMember((NamedTypeSymbol)spreadExpression.Type!));
}
if (TryGetSpanConversion(spreadExpression.Type, writableOnly: false, out var asSpanMethod))
{
var spanType = CallAsSpanMethod(spreadExpression, asSpanMethod).Type!.OriginalDefinition;
if (tryGetToArrayMethod(spanType, WellKnownType.System_ReadOnlySpan_T, WellKnownMember.System_ReadOnlySpan_T__ToArray, out var toArrayMethod)
|| tryGetToArrayMethod(spanType, WellKnownType.System_Span_T, WellKnownMember.System_Span_T__ToArray, out toArrayMethod))
{
var rewrittenSpreadExpression = CallAsSpanMethod(VisitExpression(spreadExpression), asSpanMethod);
return _factory.Call(rewrittenSpreadExpression, toArrayMethod.AsMember((NamedTypeSymbol)rewrittenSpreadExpression.Type!));
}
}
bool tryGetToArrayMethod(TypeSymbol spreadTypeOriginalDefinition, WellKnownType wellKnownType, WellKnownMember wellKnownMember, [NotNullWhen(true)] out MethodSymbol? toArrayMethod)
{
if (TypeSymbol.Equals(spreadTypeOriginalDefinition, this._compilation.GetWellKnownType(wellKnownType), TypeCompareKind.AllIgnoreOptions))
{
toArrayMethod = _factory.WellKnownMethod(wellKnownMember, isOptional: true);
return toArrayMethod is { };
}
toArrayMethod = null;
return false;
}
}
if (numberIncludingLastSpread == 0)
{
int knownLength = elements.Length;
if (knownLength == 0)
{
return CreateEmptyArray(syntax, arrayType);
}
var initialization = new BoundArrayInitialization(
syntax,
isInferred: false,
elements.SelectAsArray(static (element, rewriter) => rewriter.VisitExpression((BoundExpression)element), this));
return new BoundArrayCreation(
syntax,
ImmutableArray.Create<BoundExpression>(
new BoundLiteral(
syntax,
ConstantValue.Create(knownLength),
_compilation.GetSpecialType(SpecialType.System_Int32))),
initialization,
arrayType)
{ WasCompilerGenerated = true };
}
BoundAssignmentOperator assignmentToTemp;
var localsBuilder = ArrayBuilder<BoundLocal>.GetInstance();
var sideEffects = ArrayBuilder<BoundExpression>.GetInstance();
RewriteCollectionExpressionElementsIntoTemporaries(elements, numberIncludingLastSpread, localsBuilder, sideEffects);
// int index = 0;
BoundLocal indexTemp = _factory.StoreToTemp(
_factory.Literal(0),
out assignmentToTemp);
localsBuilder.Add(indexTemp);
sideEffects.Add(assignmentToTemp);
// ElementType[] array = new ElementType[N + s1.Length + ...];
BoundLocal arrayTemp = _factory.StoreToTemp(
new BoundArrayCreation(syntax,
ImmutableArray.Create(GetKnownLengthExpression(elements, numberIncludingLastSpread, localsBuilder)),
initializerOpt: null,
arrayType),
out assignmentToTemp);
localsBuilder.Add(arrayTemp);
sideEffects.Add(assignmentToTemp);
AddCollectionExpressionElements(
elements,
arrayTemp,
localsBuilder,
numberIncludingLastSpread,
sideEffects,
addElement: (ArrayBuilder<BoundExpression> expressions, BoundExpression arrayTemp, BoundExpression rewrittenValue) =>
{
Debug.Assert(arrayTemp.Type is ArrayTypeSymbol);
Debug.Assert(indexTemp.Type is { SpecialType: SpecialType.System_Int32 });
var expressionSyntax = rewrittenValue.Syntax;
var elementType = ((ArrayTypeSymbol)arrayTemp.Type).ElementType;
// array[index] = element;
expressions.Add(
new BoundAssignmentOperator(
expressionSyntax,
_factory.ArrayAccess(arrayTemp, indexTemp),
rewrittenValue,
isRef: false,
elementType));
// index = index + 1;
expressions.Add(
new BoundAssignmentOperator(
expressionSyntax,
indexTemp,
_factory.Binary(BinaryOperatorKind.Addition, indexTemp.Type, indexTemp, _factory.Literal(1)),
isRef: false,
indexTemp.Type));
},
tryOptimizeSpreadElement: (ArrayBuilder<BoundExpression> sideEffects, BoundExpression arrayTemp, BoundCollectionExpressionSpreadElement spreadElement, BoundExpression rewrittenSpreadOperand) =>
{
if (PrepareCopyToOptimization(spreadElement, rewrittenSpreadOperand) is not var (spanSliceMethod, spreadElementAsSpan, getLengthMethod, copyToMethod))
return false;
// https://github.com/dotnet/roslyn/issues/71270
// Could save the targetSpan to temp in the enclosing scope, but need to make sure we are async-safe etc.
if (!TryConvertToSpan(arrayTemp, writableOnly: true, out var targetSpan))
return false;
PerformCopyToOptimization(sideEffects, localsBuilder, indexTemp, targetSpan, rewrittenSpreadOperand, spanSliceMethod, spreadElementAsSpan, getLengthMethod, copyToMethod);
return true;
});
var locals = localsBuilder.SelectAsArray(l => l.LocalSymbol);
localsBuilder.Free();
return new BoundSequence(
syntax,
locals,
sideEffects.ToImmutableAndFree(),
arrayTemp,
arrayType);
}
/// <summary>
/// For the purpose of optimization, conversions to ReadOnlySpan and/or Span are known on the following types:
/// System.Array, System.Span, System.ReadOnlySpan, System.Collections.Immutable.ImmutableArray, and System.Collections.Generic.List.
/// </summary>
/// <param name="asSpanMethod">Not-null if non-identity conversion was found.</param>
/// <returns>
/// If <paramref name="writableOnly"/> is 'true', will only return 'true' with a conversion to Span.
/// If <paramref name="writableOnly"/> is 'false', may return either a conversion to ReadOnlySpan or to Span, depending on the source type.
/// For System.Array and 'false' argument for <paramref name="writableOnly"/>, only a conversion to ReadOnlySpan may be returned.
/// For System.Array and 'true' argument for <paramref name="writableOnly"/>, only a conversion to Span may be returned.
/// For System.Span, only a conversion to System.Span is may be returned.
/// For System.ReadOnlySpan, only a conversion to System.ReadOnlySpan may be returned.
/// For System.Collections.Immutable.ImmutableArray, only a conversion to System.ReadOnlySpan may be returned.
/// For System.Collections.Generic.List, only a conversion to System.Span may be returned.
/// </returns>
/// <remarks>We are assuming that the well-known types we are converting to/from do not have constraints on their type parameters.</remarks>
private bool TryGetSpanConversion(TypeSymbol type, bool writableOnly, out MethodSymbol? asSpanMethod)
{
if (type is ArrayTypeSymbol { IsSZArray: true } arrayType
&& _factory.WellKnownMethod(writableOnly ? WellKnownMember.System_Span_T__ctor_Array : WellKnownMember.System_ReadOnlySpan_T__ctor_Array, isOptional: true) is { } spanCtorArray)
{
// conversion to 'object' will fail if, for example, 'arrayType.ElementType' is a pointer.
var useSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
if (_compilation.Conversions.ClassifyConversionFromType(source: arrayType.ElementType, destination: _compilation.GetSpecialType(SpecialType.System_Object), isChecked: false, ref useSiteInfo).IsImplicit)
{
asSpanMethod = spanCtorArray.AsMember(spanCtorArray.ContainingType.Construct(arrayType.ElementType));
return true;
}
}
if (type is not NamedTypeSymbol namedType)
{
asSpanMethod = null;
return false;
}
if ((!writableOnly && namedType.OriginalDefinition.Equals(_compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T), TypeCompareKind.ConsiderEverything))
|| namedType.OriginalDefinition.Equals(_compilation.GetWellKnownType(WellKnownType.System_Span_T), TypeCompareKind.ConsiderEverything))
{
asSpanMethod = null;
return true;
}
if (!writableOnly
&& namedType.OriginalDefinition.Equals(_compilation.GetWellKnownType(WellKnownType.System_Collections_Immutable_ImmutableArray_T), TypeCompareKind.ConsiderEverything)
&& _factory.WellKnownMethod(WellKnownMember.System_Collections_Immutable_ImmutableArray_T__AsSpan, isOptional: true) is { } immutableArrayAsSpanMethod)
{
asSpanMethod = immutableArrayAsSpanMethod.AsMember(namedType);
return true;
}
if (namedType.OriginalDefinition.Equals(_compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_List_T), TypeCompareKind.ConsiderEverything)
&& _factory.WellKnownMethod(WellKnownMember.System_Runtime_InteropServices_CollectionsMarshal__AsSpan_T, isOptional: true) is { } collectionsMarshalAsSpanMethod)
{
asSpanMethod = collectionsMarshalAsSpanMethod.Construct(namedType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type);
return true;
}
asSpanMethod = null;
return false;
}
private bool TryConvertToSpan(BoundExpression expression, bool writableOnly, [NotNullWhen(true)] out BoundExpression? span)
{
var type = expression.Type;
Debug.Assert(type is not null);
if (!TryGetSpanConversion(type, writableOnly, out var asSpanMethod))
{
span = null;
return false;
}
span = CallAsSpanMethod(expression, asSpanMethod);
return true;
}
private BoundExpression CallAsSpanMethod(BoundExpression spreadExpression, MethodSymbol? asSpanMethod)
{
if (asSpanMethod is null)
{
return spreadExpression;
}
if (asSpanMethod is MethodSymbol { MethodKind: MethodKind.Constructor } constructor)
{
return _factory.New(constructor, spreadExpression);
}
else if (asSpanMethod is MethodSymbol { IsStatic: true, ParameterCount: 1 })
{
return _factory.Call(receiver: null, asSpanMethod, spreadExpression);
}
else
{
return _factory.Call(spreadExpression, asSpanMethod);
}
}
/// <summary>
/// Verifies presence of methods necessary for the CopyTo optimization
/// without performing mutating actions e.g. appending to side effects or locals builders.
/// </summary>
private (MethodSymbol spanSliceMethod, BoundExpression spreadElementAsSpan, MethodSymbol getLengthMethod, MethodSymbol copyToMethod)? PrepareCopyToOptimization(
BoundCollectionExpressionSpreadElement spreadElement,
BoundExpression rewrittenSpreadOperand)
{
// Cannot use CopyTo when spread element has non-identity conversion to target element type.
// Could do a covariant conversion of ReadOnlySpan in future: https://github.com/dotnet/roslyn/issues/71106
if (spreadElement.IteratorBody is not BoundExpressionStatement expressionStatement || expressionStatement.Expression is BoundConversion)
return null;
if (_factory.WellKnownMethod(WellKnownMember.System_Span_T__Slice_Int_Int, isOptional: true) is not { } spanSliceMethod)
return null;
if (!TryConvertToSpan(rewrittenSpreadOperand, writableOnly: false, out var spreadOperandAsSpan))
return null;
if ((getSpanMethodsForSpread(WellKnownType.System_ReadOnlySpan_T, WellKnownMember.System_ReadOnlySpan_T__get_Length, WellKnownMember.System_ReadOnlySpan_T__CopyTo_Span_T)
?? getSpanMethodsForSpread(WellKnownType.System_Span_T, WellKnownMember.System_Span_T__get_Length, WellKnownMember.System_Span_T__CopyTo_Span_T))
is not (var getLengthMethod, var copyToMethod))
{
return null;
}
return (spanSliceMethod, spreadOperandAsSpan, getLengthMethod, copyToMethod);
// gets either Span or ReadOnlySpan methods for operating on the source spread element.
(MethodSymbol getLengthMethod, MethodSymbol copyToMethod)? getSpanMethodsForSpread(
WellKnownType wellKnownSpanType,
WellKnownMember getLengthMember,
WellKnownMember copyToMember)
{
if (spreadOperandAsSpan.Type!.OriginalDefinition.Equals(this._compilation.GetWellKnownType(wellKnownSpanType))
&& _factory.WellKnownMethod(getLengthMember, isOptional: true) is { } getLengthMethod
&& _factory.WellKnownMethod(copyToMember, isOptional: true) is { } copyToMethod)
{
return (getLengthMethod, copyToMethod);
}
return null;
}
}
private void PerformCopyToOptimization(
ArrayBuilder<BoundExpression> sideEffects,
ArrayBuilder<BoundLocal> localsBuilder,
BoundLocal indexTemp,
BoundExpression spanTemp,
BoundExpression rewrittenSpreadOperand,
MethodSymbol spanSliceMethod,
BoundExpression spreadOperandAsSpan,
MethodSymbol getLengthMethod,
MethodSymbol copyToMethod)
{
// before:
// ..e1 // in [e0, ..e1]
//
// after (roughly):
// var e1Span = e1.AsSpan();
// e1Span.CopyTo(destinationSpan.Slice(indexTemp, e1Span.Length);
// indexTemp += e1Span.Length;
Debug.Assert((object)spreadOperandAsSpan != rewrittenSpreadOperand || spreadOperandAsSpan is BoundLocal { LocalSymbol.SynthesizedKind: SynthesizedLocalKind.LoweringTemp });
if ((object)spreadOperandAsSpan != rewrittenSpreadOperand)
{
spreadOperandAsSpan = _factory.StoreToTemp(spreadOperandAsSpan, out var assignmentToTemp);
sideEffects.Add(assignmentToTemp);
localsBuilder.Add((BoundLocal)spreadOperandAsSpan);
}
// e1Span.CopyTo(destinationSpan.Slice(indexTemp, e1Span.Length);
var spreadLength = _factory.Call(spreadOperandAsSpan, getLengthMethod.AsMember((NamedTypeSymbol)spreadOperandAsSpan.Type!));
var targetSlice = _factory.Call(spanTemp, spanSliceMethod.AsMember((NamedTypeSymbol)spanTemp.Type!), indexTemp, spreadLength);
sideEffects.Add(_factory.Call(spreadOperandAsSpan, copyToMethod.AsMember((NamedTypeSymbol)spreadOperandAsSpan.Type!), targetSlice));
// indexTemp += e1Span.Length;
sideEffects.Add(new BoundAssignmentOperator(rewrittenSpreadOperand.Syntax, indexTemp, _factory.Binary(BinaryOperatorKind.Addition, indexTemp.Type, indexTemp, spreadLength), isRef: false, indexTemp.Type));
}
/// <summary>
/// Create and populate an list from a collection expression.
/// The collection may or may not have a known length.
/// </summary>
private BoundExpression CreateAndPopulateList(BoundCollectionExpression node, TypeWithAnnotations elementType, ImmutableArray<BoundNode> elements)
{
Debug.Assert(!_inExpressionLambda);
var typeArguments = ImmutableArray.Create(elementType);
var collectionType = _factory.WellKnownType(WellKnownType.System_Collections_Generic_List_T).Construct(typeArguments);
var localsBuilder = ArrayBuilder<BoundLocal>.GetInstance();
var sideEffects = ArrayBuilder<BoundExpression>.GetInstance(elements.Length + 1);
int numberIncludingLastSpread;
bool useKnownLength = ShouldUseKnownLength(node, out numberIncludingLastSpread);
RewriteCollectionExpressionElementsIntoTemporaries(elements, numberIncludingLastSpread, localsBuilder, sideEffects);
bool useOptimizations = false;
MethodSymbol? setCount = null;
MethodSymbol? asSpan = null;
// Do not use optimizations in async method since the optimizations require Span<T>.
if (useKnownLength && elements.Length > 0 && _factory.CurrentFunction?.IsAsync == false)
{
setCount = ((MethodSymbol?)_compilation.GetWellKnownTypeMember(WellKnownMember.System_Runtime_InteropServices_CollectionsMarshal__SetCount_T))?.Construct(typeArguments);
asSpan = ((MethodSymbol?)_compilation.GetWellKnownTypeMember(WellKnownMember.System_Runtime_InteropServices_CollectionsMarshal__AsSpan_T))?.Construct(typeArguments);
if (setCount is { } && asSpan is { })
{
useOptimizations = true;
}
}
// Create a temp for the knownLength
BoundAssignmentOperator assignmentToTemp;
BoundLocal? knownLengthTemp = null;
BoundObjectCreationExpression rewrittenReceiver;
if (useKnownLength && elements.Length > 0)
{
var constructor = ((MethodSymbol)_factory.WellKnownMember(WellKnownMember.System_Collections_Generic_List_T__ctorInt32)).AsMember(collectionType);
var knownLengthExpression = GetKnownLengthExpression(elements, numberIncludingLastSpread, localsBuilder);
if (useOptimizations)
{
// If we use optimizations, we know the length of the resulting list, and we store it in a temp so we can pass it to List.ctor(int32) and to CollectionsMarshal.SetCount
// int knownLengthTemp = N + s1.Length + ...;
knownLengthTemp = _factory.StoreToTemp(knownLengthExpression, out assignmentToTemp);
localsBuilder.Add(knownLengthTemp);
sideEffects.Add(assignmentToTemp);
// List<ElementType> list = new(knownLengthTemp);
rewrittenReceiver = _factory.New(constructor, ImmutableArray.Create<BoundExpression>(knownLengthTemp));
}
else
{
// List<ElementType> list = new(N + s1.Length + ...)
rewrittenReceiver = _factory.New(constructor, ImmutableArray.Create(knownLengthExpression));
}
}
else
{
// List<ElementType> list = new();
var constructor = ((MethodSymbol)_factory.WellKnownMember(WellKnownMember.System_Collections_Generic_List_T__ctor)).AsMember(collectionType);
rewrittenReceiver = _factory.New(constructor, ImmutableArray<BoundExpression>.Empty);
}
// Create a temp for the list.
BoundLocal listTemp = _factory.StoreToTemp(rewrittenReceiver, out assignmentToTemp);
localsBuilder.Add(listTemp);
sideEffects.Add(assignmentToTemp);
// Use Span<T> if CollectionsMarshal methods are available, otherwise use List<T>.Add().
if (useOptimizations)
{
Debug.Assert(useKnownLength);
Debug.Assert(setCount is { });
Debug.Assert(asSpan is { });
Debug.Assert(knownLengthTemp is { });
// CollectionsMarshal.SetCount<ElementType>(list, knownLengthTemp);
sideEffects.Add(_factory.Call(receiver: null, setCount, listTemp, knownLengthTemp));
// var span = CollectionsMarshal.AsSpan<ElementType(list);
BoundLocal spanTemp = _factory.StoreToTemp(_factory.Call(receiver: null, asSpan, listTemp), out assignmentToTemp);
localsBuilder.Add(spanTemp);
sideEffects.Add(assignmentToTemp);
// Populate the span.
var spanGetItem = ((MethodSymbol)_factory.WellKnownMember(WellKnownMember.System_Span_T__get_Item)).AsMember((NamedTypeSymbol)spanTemp.Type);