55using System ;
66using System . Collections . Immutable ;
77using System . Diagnostics ;
8+ using System . Diagnostics . CodeAnalysis ;
89using System . Linq ;
910using Microsoft . CodeAnalysis . CSharp . CodeGen ;
1011using Microsoft . CodeAnalysis . CSharp . Symbols ;
@@ -443,6 +444,44 @@ private BoundExpression CreateAndPopulateArray(BoundCollectionExpression node, A
443444 throw ExceptionUtilities . UnexpectedValue ( node ) ;
444445 }
445446
447+ // Collection-expr is of the form `[..spreadExpression]`, where 'spreadExpression' has same element type as the target collection.
448+ // Optimize to `spreadExpression.ToArray()` if possible.
449+ if ( node is { Elements : [ BoundCollectionExpressionSpreadElement { Expression : { } spreadExpression } spreadElement ] }
450+ && spreadElement . IteratorBody is BoundExpressionStatement expressionStatement
451+ && expressionStatement . Expression is not BoundConversion )
452+ {
453+ var spreadTypeOriginalDefinition = spreadExpression . Type ! . OriginalDefinition ;
454+ if ( tryGetToArrayMethod ( spreadTypeOriginalDefinition , WellKnownType . System_Collections_Generic_List_T , WellKnownMember . System_Collections_Generic_List_T__ToArray ) is { } listToArrayMethod )
455+ {
456+ var rewrittenSpreadExpression = VisitExpression ( spreadExpression ) ;
457+ return Factory . Call ( rewrittenSpreadExpression , listToArrayMethod . AsMember ( ( NamedTypeSymbol ) spreadExpression . Type ! ) ) ;
458+ }
459+
460+ if ( GetAsSpanMethod ( spreadExpression . Type ) is { } asSpanMethod )
461+ {
462+ var spanType = ( asSpanMethod is null ? spreadExpression : CallAsSpanMethod ( spreadExpression , asSpanMethod ) ) . Type ! . OriginalDefinition ;
463+ if ( ( tryGetToArrayMethod ( spanType , WellKnownType . System_Span_T , WellKnownMember . System_Span_T__ToArray )
464+ ?? tryGetToArrayMethod ( spanType , WellKnownType . System_ReadOnlySpan_T , WellKnownMember . System_ReadOnlySpan_T__ToArray ) )
465+ is { } toArrayMethod )
466+ {
467+ var rewrittenSpreadExpression = VisitExpression ( spreadExpression ) ;
468+ if ( asSpanMethod is not null )
469+ rewrittenSpreadExpression = CallAsSpanMethod ( rewrittenSpreadExpression , asSpanMethod ) ;
470+ return Factory . Call ( rewrittenSpreadExpression , toArrayMethod . AsMember ( ( NamedTypeSymbol ) rewrittenSpreadExpression . Type ! ) ) ;
471+ }
472+ }
473+
474+ MethodSymbol ? tryGetToArrayMethod ( TypeSymbol spreadTypeOriginalDefinition , WellKnownType wellKnownType , WellKnownMember wellKnownMember )
475+ {
476+ if ( spreadTypeOriginalDefinition . Equals ( this . _compilation . GetWellKnownType ( wellKnownType ) ) )
477+ {
478+ return _factory . WellKnownMethod ( wellKnownMember , isOptional : true ) ;
479+ }
480+
481+ return null ;
482+ }
483+ }
484+
446485 if ( numberIncludingLastSpread == 0 )
447486 {
448487 int knownLength = elements . Length ;
@@ -492,6 +531,7 @@ private BoundExpression CreateAndPopulateArray(BoundCollectionExpression node, A
492531 localsBuilder . Add ( arrayTemp ) ;
493532 sideEffects . Add ( assignmentToTemp ) ;
494533
534+ BoundLocal ? targetSpanTemp = null ;
495535 AddCollectionExpressionElements (
496536 elements ,
497537 arrayTemp ,
@@ -522,6 +562,25 @@ private BoundExpression CreateAndPopulateArray(BoundCollectionExpression node, A
522562 _factory . Binary ( BinaryOperatorKind . Addition , indexTemp . Type , indexTemp , _factory . Literal ( 1 ) ) ,
523563 isRef : false ,
524564 indexTemp . Type ) ) ;
565+ } ,
566+ ( sideEffects , targetArray , spreadElement , rewrittenSpreadOperand ) =>
567+ {
568+ if ( TryPrepareCopyToOptimization ( spreadElement , rewrittenSpreadOperand ) is not var ( spanSliceMethod , spreadElementAsSpan , getLengthMethod , copyToMethod ) )
569+ return false ;
570+
571+ if ( targetSpanTemp is null )
572+ {
573+ var targetSpan = TryConvertToSpanOrReadOnlySpan ( targetArray ) ;
574+ if ( targetSpan is null )
575+ return false ;
576+
577+ targetSpanTemp = _factory . StoreToTemp ( targetSpan , out var assignmentToTemp ) ;
578+ localsBuilder . Add ( targetSpanTemp ) ;
579+ sideEffects . Add ( assignmentToTemp ) ;
580+ }
581+
582+ PerformCopyToOptimization ( sideEffects , localsBuilder , indexTemp , targetSpanTemp , rewrittenSpreadOperand , spanSliceMethod , spreadElementAsSpan , getLengthMethod , copyToMethod ) ;
583+ return true ;
525584 } ) ;
526585
527586 var locals = localsBuilder . SelectAsArray ( l => l . LocalSymbol ) ;
@@ -535,6 +594,155 @@ private BoundExpression CreateAndPopulateArray(BoundCollectionExpression node, A
535594 arrayType ) ;
536595 }
537596
597+ private MethodSymbol ? GetAsSpanMethod ( TypeSymbol type )
598+ {
599+ if ( type is NamedTypeSymbol spanType
600+ && ( spanType . OriginalDefinition . Equals ( _compilation . GetWellKnownType ( WellKnownType . System_Span_T ) , TypeCompareKind . ConsiderEverything )
601+ || spanType . OriginalDefinition . Equals ( _compilation . GetWellKnownType ( WellKnownType . System_ReadOnlySpan_T ) , TypeCompareKind . ConsiderEverything ) ) )
602+ {
603+ return null ;
604+ }
605+
606+ if ( type is ArrayTypeSymbol { IsSZArray : true } arrayType
607+ && _factory . WellKnownMethod ( WellKnownMember . System_Span_T__ctor_Array , isOptional : true ) is { } spanCtorArray )
608+ {
609+ return spanCtorArray . AsMember ( spanCtorArray . ContainingType . Construct ( arrayType . ElementType ) ) ;
610+ }
611+
612+ if ( type is NamedTypeSymbol immutableArrayType
613+ && immutableArrayType . OriginalDefinition . Equals ( _compilation . GetWellKnownType ( WellKnownType . System_Collections_Immutable_ImmutableArray_T ) , TypeCompareKind . ConsiderEverything )
614+ && _factory . WellKnownMethod ( WellKnownMember . System_Collections_Immutable_ImmutableArray_T__AsSpan , isOptional : true ) is { } immutableArrayAsSpanMethod )
615+ {
616+ return immutableArrayAsSpanMethod ! . AsMember ( immutableArrayType ) ;
617+ }
618+
619+ if ( type is NamedTypeSymbol listType
620+ && listType . OriginalDefinition . Equals ( _compilation . GetWellKnownType ( WellKnownType . System_Collections_Generic_List_T ) , TypeCompareKind . ConsiderEverything )
621+ && _factory . WellKnownMethod ( WellKnownMember . System_Runtime_InteropServices_CollectionsMarshal__AsSpan_T , isOptional : true ) is { } collectionsMarshalAsSpanMethod )
622+ {
623+ return collectionsMarshalAsSpanMethod . Construct ( listType . TypeArgumentsWithAnnotationsNoUseSiteDiagnostics [ 0 ] . Type ) ;
624+ }
625+
626+ return null ;
627+ }
628+
629+ private BoundExpression ? TryConvertToSpanOrReadOnlySpan ( BoundExpression expression )
630+ {
631+ var type = expression . Type ;
632+ Debug . Assert ( type is not null ) ;
633+
634+ if ( GetAsSpanMethod ( type ) is not { } asSpanMethod )
635+ {
636+ return null ;
637+ }
638+
639+ if ( asSpanMethod is null )
640+ {
641+ return expression ;
642+ }
643+ else
644+ {
645+ return CallAsSpanMethod ( expression , asSpanMethod ) ;
646+ }
647+ }
648+
649+ private BoundExpression CallAsSpanMethod ( BoundExpression spreadExpression , MethodSymbol asSpanMethod )
650+ {
651+ if ( asSpanMethod is MethodSymbol { MethodKind : MethodKind . Constructor } constructor )
652+ {
653+ return _factory . New ( constructor , spreadExpression ) ;
654+ }
655+ else if ( asSpanMethod is MethodSymbol { IsStatic : true , ParameterCount : 1 } )
656+ {
657+ return _factory . Call ( receiver : null , asSpanMethod , spreadExpression ) ;
658+ }
659+ else
660+ {
661+ return _factory . Call ( spreadExpression , asSpanMethod ) ;
662+ }
663+ }
664+
665+ /// <summary>
666+ /// Verifies presence of methods necessary for the CopyTo optimization
667+ /// without performing mutating actions e.g. appending to side effects or locals builders.
668+ /// </summary>
669+ private ( MethodSymbol spanSliceMethod , BoundExpression spreadElementAsSpan , MethodSymbol getLengthMethod , MethodSymbol copyToMethod ) ? TryPrepareCopyToOptimization (
670+ BoundCollectionExpressionSpreadElement spreadElement ,
671+ BoundExpression rewrittenSpreadOperand )
672+ {
673+ // Cannot use CopyTo when spread element has non-identity conversion to target element type.
674+ // Could do a covariant conversion of ReadOnlySpan in future: https://github.com/dotnet/roslyn/issues/71106
675+ if ( spreadElement . IteratorBody is not BoundExpressionStatement expressionStatement || expressionStatement . Expression is BoundConversion )
676+ return null ;
677+
678+ if ( _factory . WellKnownMethod ( WellKnownMember . System_Span_T__Slice_Int_Int , isOptional : true ) is not { } spanSliceMethod )
679+ return null ;
680+
681+ if ( TryConvertToSpanOrReadOnlySpan ( rewrittenSpreadOperand ) is not { } spreadOperandAsSpan )
682+ return null ;
683+
684+ if ( ( tryGetSpanMethodsForSpread ( WellKnownType . System_ReadOnlySpan_T , WellKnownMember . System_ReadOnlySpan_T__get_Length , WellKnownMember . System_ReadOnlySpan_T__CopyTo_Span_T )
685+ ?? tryGetSpanMethodsForSpread ( WellKnownType . System_Span_T , WellKnownMember . System_Span_T__get_Length , WellKnownMember . System_Span_T__CopyTo_Span_T ) )
686+ is not ( var getLengthMethod , var copyToMethod ) )
687+ {
688+ return null ;
689+ }
690+
691+ return ( spanSliceMethod , spreadOperandAsSpan , getLengthMethod , copyToMethod ) ;
692+
693+ // gets either Span or ReadOnlySpan methods for operating on the source spread element.
694+ ( MethodSymbol getLengthMethod , MethodSymbol copyToMethod ) ? tryGetSpanMethodsForSpread (
695+ WellKnownType wellKnownSpanType ,
696+ WellKnownMember getLengthMember ,
697+ WellKnownMember copyToMember )
698+ {
699+ if ( spreadOperandAsSpan . Type ! . OriginalDefinition . Equals ( this . _compilation . GetWellKnownType ( wellKnownSpanType ) )
700+ && _factory . WellKnownMethod ( getLengthMember , isOptional : true ) is { } getLengthMethod
701+ && _factory . WellKnownMethod ( copyToMember , isOptional : true ) is { } copyToMethod )
702+ {
703+ return ( getLengthMethod ! , copyToMethod ! ) ;
704+ }
705+
706+ return null ;
707+ }
708+ }
709+
710+ private void PerformCopyToOptimization (
711+ ArrayBuilder < BoundExpression > sideEffects ,
712+ ArrayBuilder < BoundLocal > localsBuilder ,
713+ BoundLocal indexTemp ,
714+ BoundExpression spanTemp ,
715+ BoundExpression rewrittenSpreadOperand ,
716+ MethodSymbol spanSliceMethod ,
717+ BoundExpression spreadOperandAsSpan ,
718+ MethodSymbol getLengthMethod ,
719+ MethodSymbol copyToMethod )
720+ {
721+ // before:
722+ // ..e1 // in [e0, ..e1]
723+ //
724+ // after (roughly):
725+ // var e1Span = e1.AsSpan();
726+ // e1Span.CopyTo(destinationSpan.Slice(indexTemp, e1Span.Length);
727+ // indexTemp += e1Span.Length;
728+
729+ Debug . Assert ( ( object ) spreadOperandAsSpan != rewrittenSpreadOperand || spreadOperandAsSpan is BoundLocal { LocalSymbol . SynthesizedKind : SynthesizedLocalKind . LoweringTemp } ) ;
730+ if ( ( object ) spreadOperandAsSpan != rewrittenSpreadOperand )
731+ {
732+ spreadOperandAsSpan = _factory . StoreToTemp ( spreadOperandAsSpan , out var assignmentToTemp ) ;
733+ sideEffects . Add ( assignmentToTemp ) ;
734+ localsBuilder . Add ( ( BoundLocal ) spreadOperandAsSpan ) ;
735+ }
736+
737+ // e1Span.CopyTo(destinationSpan.Slice(indexTemp, e1Span.Length);
738+ var spreadLength = _factory . Call ( spreadOperandAsSpan , getLengthMethod . AsMember ( ( NamedTypeSymbol ) spreadOperandAsSpan . Type ! ) ) ;
739+ var targetSlice = _factory . Call ( spanTemp , spanSliceMethod . AsMember ( ( NamedTypeSymbol ) spanTemp . Type ! ) , indexTemp , spreadLength ) ;
740+ sideEffects . Add ( _factory . Call ( spreadOperandAsSpan , copyToMethod . AsMember ( ( NamedTypeSymbol ) spreadOperandAsSpan . Type ! ) , targetSlice ) ) ;
741+
742+ // indexTemp += e1Span.Length;
743+ sideEffects . Add ( new BoundAssignmentOperator ( rewrittenSpreadOperand . Syntax , indexTemp , _factory . Binary ( BinaryOperatorKind . Addition , indexTemp . Type , indexTemp , spreadLength ) , isRef : false , indexTemp . Type ) ) ;
744+ }
745+
538746 /// <summary>
539747 /// Create and populate an list from a collection expression.
540748 /// The collection may or may not have a known length.
@@ -648,11 +856,20 @@ private BoundExpression CreateAndPopulateList(BoundCollectionExpression node, Ty
648856 _factory . Binary ( BinaryOperatorKind . Addition , indexTemp . Type , indexTemp , _factory . Literal ( 1 ) ) ,
649857 isRef : false ,
650858 indexTemp . Type ) ) ;
859+ } ,
860+ ( sideEffects , spanTemp , spreadElement , rewrittenSpreadOperand ) =>
861+ {
862+ if ( TryPrepareCopyToOptimization ( spreadElement , rewrittenSpreadOperand ) is not var ( spanSliceMethod , spreadElementAsSpan , getLengthMethod , copyToMethod ) )
863+ return false ;
864+
865+ PerformCopyToOptimization ( sideEffects , localsBuilder , indexTemp , spanTemp , rewrittenSpreadOperand , spanSliceMethod , spreadElementAsSpan , getLengthMethod , copyToMethod ) ;
866+ return true ;
651867 } ) ;
652868 }
653869 else
654870 {
655- var addMethod = ( ( MethodSymbol ) _factory . WellKnownMember ( WellKnownMember . System_Collections_Generic_List_T__Add ) ) . AsMember ( collectionType ) ;
871+ var addMethod = _factory . WellKnownMethod ( WellKnownMember . System_Collections_Generic_List_T__Add ) . AsMember ( collectionType ) ;
872+ var addRangeMethod = _factory . WellKnownMethod ( WellKnownMember . System_Collections_Generic_List_T__AddRange , isOptional : true ) ? . AsMember ( collectionType ) ;
656873 AddCollectionExpressionElements (
657874 elements ,
658875 listTemp ,
@@ -664,6 +881,24 @@ private BoundExpression CreateAndPopulateList(BoundCollectionExpression node, Ty
664881 // list.Add(element);
665882 expressions . Add (
666883 _factory . Call ( listTemp , addMethod , rewrittenValue ) ) ;
884+ } ,
885+ ( sideEffects , spanTemp , spreadElement , rewrittenSpreadOperand ) =>
886+ {
887+ if ( addRangeMethod is null )
888+ return false ;
889+
890+ var type = rewrittenSpreadOperand . Type ! ;
891+
892+ var useSiteInfo = GetNewCompoundUseSiteInfo ( ) ;
893+ var conversion = _compilation . Conversions . ClassifyConversionFromType ( type , addRangeMethod . Parameters [ 0 ] . Type , isChecked : false , ref useSiteInfo ) ;
894+ _diagnostics . Add ( rewrittenSpreadOperand . Syntax , useSiteInfo ) ;
895+ if ( conversion . IsImplicit && conversion . IsReference )
896+ {
897+ sideEffects . Add ( _factory . Call ( listTemp , addRangeMethod , MakeConversionNode ( rewrittenSpreadOperand , type , @checked : false , markAsChecked : true ) ) ) ;
898+ return true ;
899+ }
900+
901+ return false ;
667902 } ) ;
668903 }
669904
@@ -708,7 +943,8 @@ private void AddCollectionExpressionElements(
708943 ArrayBuilder < BoundLocal > rewrittenExpressions ,
709944 int numberIncludingLastSpread ,
710945 ArrayBuilder < BoundExpression > sideEffects ,
711- Action < ArrayBuilder < BoundExpression > , BoundExpression , BoundExpression > addElement )
946+ Action < ArrayBuilder < BoundExpression > , BoundExpression , BoundExpression > addElement ,
947+ Func < ArrayBuilder < BoundExpression > , BoundExpression , BoundCollectionExpressionSpreadElement , BoundExpression , bool > ? tryOptimizeSpreadElement = null )
712948 {
713949 for ( int i = 0 ; i < elements . Length ; i ++ )
714950 {
@@ -719,6 +955,9 @@ private void AddCollectionExpressionElements(
719955
720956 if ( element is BoundCollectionExpressionSpreadElement spreadElement )
721957 {
958+ if ( tryOptimizeSpreadElement ? . Invoke ( sideEffects , rewrittenReceiver , spreadElement , rewrittenExpression ) == true )
959+ continue ;
960+
722961 var rewrittenElement = MakeCollectionExpressionSpreadElement (
723962 spreadElement ,
724963 rewrittenExpression ,
0 commit comments