File: Lowering\LocalRewriter\LocalRewriter_CollectionExpression.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// 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 (ConversionsBase.IsSpanOrListType(_compilation, node.Type, WellKnownType.System_Collections_Generic_List_T, out var listElementType))
                        {
                            if (TryRewriteSingleElementSpreadToList(node, listElementType, out var result))
                            {
                                return result;
                            }
 
                            if (useListOptimization(_compilation, node))
                            {
                                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)
            {
                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;
                }
            }
        }
 
        // If we have something like `List<int> l = [.. someEnumerable]`
        // try rewrite it using `Enumerable.ToList` member if possible
        private bool TryRewriteSingleElementSpreadToList(BoundCollectionExpression node, TypeWithAnnotations listElementType, [NotNullWhen(true)] out BoundExpression? result)
        {
            result = null;
 
            if (node.Elements is not [BoundCollectionExpressionSpreadElement singleSpread])
            {
                return false;
            }
 
            if (!TryGetWellKnownTypeMember(node.Syntax, WellKnownMember.System_Linq_Enumerable__ToList, out MethodSymbol? toListGeneric, isOptional: true))
            {
                return false;
            }
 
            var toListOfElementType = toListGeneric.Construct([listElementType]);
 
            Debug.Assert(singleSpread.Expression.Type is not null);
 
            if (!ShouldUseIEnumerableBulkAddMethod(singleSpread.Expression.Type, toListOfElementType.Parameters[0].Type, singleSpread.EnumeratorInfoOpt?.GetEnumeratorInfo.Method))
            {
                return false;
            }
 
            var rewrittenSpreadExpression = VisitExpression(singleSpread.Expression);
            result = _factory.Call(receiver: null, toListOfElementType, rewrittenSpreadExpression);
            return true;
        }
 
        /// <summary>
        /// Decides if a bulk-add method such as AddRange, ToList, ToArray, etc. is suitable for copying a spread value with type 'spreadType' to the destination collection.
        /// </summary>
        private bool ShouldUseIEnumerableBulkAddMethod(TypeSymbol spreadType, TypeSymbol targetEnumerableType, MethodSymbol? getEnumeratorMethod)
        {
            Debug.Assert(targetEnumerableType.OriginalDefinition == (object)_compilation.GetSpecialType(SpecialType.System_Collections_Generic_IEnumerable_T));
 
            var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
 
            Conversion conversion;
 
            // If collection has a struct enumerator but doesn't implement ICollection<T>
            // then manual `foreach` is always more efficient then using `ToList` or `AddRange` methods
            if (getEnumeratorMethod?.ReturnType.IsValueType == true)
            {
                var iCollectionOfTType = _compilation.GetSpecialType(SpecialType.System_Collections_Generic_ICollection_T);
                var iCollectionOfElementType = iCollectionOfTType.Construct(((NamedTypeSymbol)targetEnumerableType).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics);
 
                conversion = _compilation.Conversions.ClassifyBuiltInConversion(spreadType, iCollectionOfElementType, isChecked: false, ref discardedUseSiteInfo);
                if (conversion.Kind is not (ConversionKind.Identity or ConversionKind.ImplicitReference))
                {
                    return false;
                }
            }
 
            conversion = _compilation.Conversions.ClassifyImplicitConversionFromType(spreadType, targetEnumerableType, ref discardedUseSiteInfo);
            return conversion.Kind is ConversionKind.Identity or ConversionKind.ImplicitReference;
        }
 
        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 (TryOptimizeSingleSpreadToArray(node, arrayType) is { } optimizedArray)
            {
                array = optimizedArray;
            }
            else 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 => createArray(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);
 
            BoundExpression createArray(BoundCollectionExpression node, ArrayTypeSymbol arrayType)
            {
                if (TryOptimizeSingleSpreadToArray(node, arrayType) is { } optimizedArray)
                    return optimizedArray;
 
                return CreateAndPopulateArray(node, arrayType);
            }
        }
 
        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)
                ? VisitExpression(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>Attempt to optimize conversion of a single-spread collection expr to array, even if the spread length is not known.</summary>
        /// <remarks>
        /// The following optimizations are tried, in order:
        /// 1. 'List.ToArray' if the spread value is a list
        /// 2. 'Enumerable.ToArray' if we can convert the spread value to IEnumerable and additional conditions are met
        /// 3. 'Span/ReadOnlySpan.ToArray' if we can convert the spread value to Span or ReadOnlySpan
        /// </remarks>
        private BoundExpression? TryOptimizeSingleSpreadToArray(BoundCollectionExpression node, ArrayTypeSymbol arrayType)
        {
            // Collection-expr is of the form `[..spreadExpression]`.
            // Optimize to `spreadExpression.ToArray()` if possible.
            if (node is { Elements: [BoundCollectionExpressionSpreadElement { Expression: { } spreadExpression } spreadElement] }
                && spreadElement.IteratorBody is BoundExpressionStatement expressionStatement)
            {
                var spreadElementHasIdentityConversion = expressionStatement.Expression is not BoundConversion;
                var spreadTypeOriginalDefinition = spreadExpression.Type!.OriginalDefinition;
 
                if (spreadElementHasIdentityConversion
                    && 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!));
                }
 
                // See if 'Enumerable.ToArray<T>(IEnumerable<T>)' will work, possibly due to a covariant conversion on the spread value.
                if (_factory.WellKnownMethod(WellKnownMember.System_Linq_Enumerable__ToArray, isOptional: true) is { } linqToArrayMethodGeneric)
                {
                    // Note that in general, we expect well-known collection types and methods to lack constraints on their type parameter(s).
                    // Because an array element type may not be a valid type argument for unconstrained type parameter, we still check constraints here regardless.
                    var linqToArrayMethod = linqToArrayMethodGeneric.Construct([arrayType.ElementTypeWithAnnotations]);
                    if (linqToArrayMethod.CheckConstraints(new ConstraintsHelper.CheckConstraintsArgs(_compilation, _compilation.Conversions, Location.None, BindingDiagnosticBag.Discarded))
                        && ShouldUseIEnumerableBulkAddMethod(spreadExpression.Type!, linqToArrayMethod.Parameters[0].Type, spreadElement.EnumeratorInfoOpt?.GetEnumeratorInfo.Method))
                    {
                        return _factory.Call(receiver: null, linqToArrayMethod, VisitExpression(spreadExpression));
                    }
                }
 
                if (spreadElementHasIdentityConversion
                    && 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;
                }
            }
 
            return null;
        }
 
        /// <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);
            }
 
            // Shouldn't call this method if the single spread optimization would work.
            Debug.Assert(TryOptimizeSingleSpreadToArray(node, arrayType) is null);
 
            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, bool isLastElement) =>
                {
                    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));
                    if (!isLastElement)
                    {
                        // 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)
            {
                var spanOfElementType = spanCtorArray.ContainingType.Construct(arrayType.ElementType);
                if (spanOfElementType.CheckConstraints(new ConstraintsHelper.CheckConstraintsArgs(_compilation, _compilation.Conversions, Location.None, BindingDiagnosticBag.Discarded)))
                {
                    asSpanMethod = spanCtorArray.AsMember(spanOfElementType);
                    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);
 
                // int index = 0;
                BoundLocal indexTemp = _factory.StoreToTemp(
                    _factory.Literal(0),
                    out assignmentToTemp);
                localsBuilder.Add(indexTemp);
                sideEffects.Add(assignmentToTemp);
 
                AddCollectionExpressionElements(
                    elements,
                    spanTemp,
                    localsBuilder,
                    numberIncludingLastSpread,
                    sideEffects,
                    addElement: (ArrayBuilder<BoundExpression> expressions, BoundExpression spanTemp, BoundExpression rewrittenValue, bool isLastElement) =>
                    {
                        Debug.Assert(spanTemp.Type is NamedTypeSymbol);
                        Debug.Assert(indexTemp.Type is { SpecialType: SpecialType.System_Int32 });
 
                        var expressionSyntax = rewrittenValue.Syntax;
                        var elementType = ((NamedTypeSymbol)spanTemp.Type).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type;
 
                        // span[index] = element;
                        expressions.Add(
                            new BoundAssignmentOperator(
                                expressionSyntax,
                                _factory.Call(spanTemp, spanGetItem, indexTemp),
                                rewrittenValue,
                                isRef: false,
                                elementType));
                        if (!isLastElement)
                        {
                            // 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 spanTemp, BoundCollectionExpressionSpreadElement spreadElement, BoundExpression rewrittenSpreadOperand) =>
                    {
                        if (PrepareCopyToOptimization(spreadElement, rewrittenSpreadOperand) is not var (spanSliceMethod, spreadElementAsSpan, getLengthMethod, copyToMethod))
                            return false;
 
                        PerformCopyToOptimization(sideEffects, localsBuilder, indexTemp, spanTemp, rewrittenSpreadOperand, spanSliceMethod, spreadElementAsSpan, getLengthMethod, copyToMethod);
                        return true;
                    });
            }
            else
            {
                var addMethod = _factory.WellKnownMethod(WellKnownMember.System_Collections_Generic_List_T__Add).AsMember(collectionType);
                var addRangeMethod = _factory.WellKnownMethod(WellKnownMember.System_Collections_Generic_List_T__AddRange, isOptional: true)?.AsMember(collectionType);
                AddCollectionExpressionElements(
                    elements,
                    listTemp,
                    localsBuilder,
                    numberIncludingLastSpread,
                    sideEffects,
                    addElement: (ArrayBuilder<BoundExpression> expressions, BoundExpression listTemp, BoundExpression rewrittenValue, bool isLastElement) =>
                    {
                        // list.Add(element);
                        expressions.Add(
                            _factory.Call(listTemp, addMethod, rewrittenValue));
                    },
                    tryOptimizeSpreadElement: (ArrayBuilder<BoundExpression> sideEffects, BoundExpression listTemp, BoundCollectionExpressionSpreadElement spreadElement, BoundExpression rewrittenSpreadOperand) =>
                    {
                        Debug.Assert(rewrittenSpreadOperand.Type is not null);
 
                        if (addRangeMethod is null)
                            return false;
 
                        if (!ShouldUseIEnumerableBulkAddMethod(rewrittenSpreadOperand.Type, addRangeMethod.Parameters[0].Type, spreadElement.EnumeratorInfoOpt?.GetEnumeratorInfo.Method))
                        {
                            return false;
                        }
 
                        sideEffects.Add(_factory.Call(listTemp, addRangeMethod, rewrittenSpreadOperand));
                        return true;
                    });
            }
 
            var locals = localsBuilder.SelectAsArray(l => l.LocalSymbol);
            localsBuilder.Free();
 
            return new BoundSequence(
                node.Syntax,
                locals,
                sideEffects.ToImmutableAndFree(),
                listTemp,
                collectionType);
        }
 
        private BoundExpression RewriteCollectionExpressionElementExpression(BoundNode element)
        {
            var expression = element is BoundCollectionExpressionSpreadElement spreadElement ?
                spreadElement.Expression :
                (BoundExpression)element;
            return VisitExpression(expression);
        }
 
        private void RewriteCollectionExpressionElementsIntoTemporaries(
            ImmutableArray<BoundNode> elements,
            int numberIncludingLastSpread,
            ArrayBuilder<BoundLocal> locals,
            ArrayBuilder<BoundExpression> sideEffects)
        {
            for (int i = 0; i < numberIncludingLastSpread; i++)
            {
                var rewrittenExpression = RewriteCollectionExpressionElementExpression(elements[i]);
                BoundAssignmentOperator assignmentToTemp;
                BoundLocal temp = _factory.StoreToTemp(rewrittenExpression, out assignmentToTemp);
                locals.Add(temp);
                sideEffects.Add(assignmentToTemp);
            }
        }
 
        private void AddCollectionExpressionElements(
            ImmutableArray<BoundNode> elements,
            BoundExpression rewrittenReceiver,
            ArrayBuilder<BoundLocal> rewrittenExpressions,
            int numberIncludingLastSpread,
            ArrayBuilder<BoundExpression> sideEffects,
            Action<ArrayBuilder<BoundExpression>, BoundExpression, BoundExpression, bool> addElement,
            Func<ArrayBuilder<BoundExpression>, BoundExpression, BoundCollectionExpressionSpreadElement, BoundExpression, bool> tryOptimizeSpreadElement)
        {
            for (int i = 0; i < elements.Length; i++)
            {
                var element = elements[i];
                var rewrittenExpression = i < numberIncludingLastSpread ?
                    rewrittenExpressions[i] :
                    RewriteCollectionExpressionElementExpression(element);
 
                if (element is BoundCollectionExpressionSpreadElement spreadElement)
                {
                    if (tryOptimizeSpreadElement(sideEffects, rewrittenReceiver, spreadElement, rewrittenExpression))
                        continue;
 
                    var rewrittenElement = MakeCollectionExpressionSpreadElement(
                        spreadElement,
                        rewrittenExpression,
                        iteratorBody =>
                        {
                            var rewrittenValue = VisitExpression(((BoundExpressionStatement)iteratorBody).Expression);
                            var builder = ArrayBuilder<BoundExpression>.GetInstance();
                            addElement(builder, rewrittenReceiver, rewrittenValue, false);
                            var statements = builder.SelectAsArray(expr => (BoundStatement)new BoundExpressionStatement(expr.Syntax, expr));
                            builder.Free();
                            Debug.Assert(statements.Length > 0);
                            return statements.Length == 1 ?
                                statements[0] :
                                new BoundBlock(iteratorBody.Syntax, locals: ImmutableArray<LocalSymbol>.Empty, statements);
                        });
                    sideEffects.Add(rewrittenElement);
                }
                else
                {
                    var isLastElement = i == (elements.Length - 1);
                    addElement(sideEffects, rewrittenReceiver, rewrittenExpression, isLastElement);
                }
            }
        }
 
        private BoundExpression GetKnownLengthExpression(ImmutableArray<BoundNode> elements, int numberIncludingLastSpread, ArrayBuilder<BoundLocal> rewrittenExpressions)
        {
            Debug.Assert(rewrittenExpressions.Count >= numberIncludingLastSpread);
 
            int initialLength = 0;
            BoundExpression? sum = null;
 
            for (int i = 0; i < numberIncludingLastSpread; i++)
            {
                var element = elements[i];
                if (element is BoundCollectionExpressionSpreadElement spreadElement)
                {
                    var collectionPlaceholder = spreadElement.ExpressionPlaceholder;
                    Debug.Assert(collectionPlaceholder is { });
                    AddPlaceholderReplacement(collectionPlaceholder, rewrittenExpressions[i]);
                    var lengthAccess = VisitExpression(spreadElement.LengthOrCount);
                    RemovePlaceholderReplacement(collectionPlaceholder);
 
                    Debug.Assert(lengthAccess is { });
                    sum = add(sum, lengthAccess);
                }
                else
                {
                    initialLength++;
                }
            }
 
            initialLength += elements.Length - numberIncludingLastSpread;
 
            if (initialLength > 0)
            {
                var otherElements = _factory.Literal(initialLength);
                sum = sum is null ?
                    otherElements :
                    add(otherElements, sum);
            }
 
            Debug.Assert(sum is { });
            return sum;
 
            BoundExpression add(BoundExpression? sum, BoundExpression value)
            {
                return sum is null ?
                    value :
                    _factory.Binary(BinaryOperatorKind.Addition, sum.Type!, sum, value);
            }
        }
 
        private BoundExpression MakeCollectionExpressionSpreadElement(
            BoundCollectionExpressionSpreadElement node,
            BoundExpression rewrittenExpression,
            Func<BoundStatement, BoundStatement> rewriteBody)
        {
            var enumeratorInfo = node.EnumeratorInfoOpt;
            var convertedExpression = (BoundConversion?)node.Conversion;
            var expressionPlaceholder = node.ExpressionPlaceholder;
            var elementPlaceholder = node.ElementPlaceholder;
            var iteratorBody = node.IteratorBody;
 
            Debug.Assert(enumeratorInfo is { });
            Debug.Assert(convertedExpression is { });
            Debug.Assert(expressionPlaceholder is { });
            Debug.Assert(elementPlaceholder is { });
            Debug.Assert(iteratorBody is { });
 
            AddPlaceholderReplacement(expressionPlaceholder, rewrittenExpression);
 
            var iterationVariable = _factory.SynthesizedLocal(enumeratorInfo.ElementType, node.Syntax);
            var iterationLocal = _factory.Local(iterationVariable);
 
            AddPlaceholderReplacement(elementPlaceholder, iterationLocal);
            var rewrittenBody = rewriteBody(iteratorBody);
            RemovePlaceholderReplacement(elementPlaceholder);
 
            var iterationVariables = ImmutableArray.Create(iterationVariable);
            var breakLabel = new GeneratedLabelSymbol("break");
            var continueLabel = new GeneratedLabelSymbol("continue");
 
            BoundStatement statement;
            if (convertedExpression.Operand.Type is ArrayTypeSymbol arrayType)
            {
                if (arrayType.IsSZArray)
                {
                    statement = RewriteSingleDimensionalArrayForEachEnumerator(
                        node,
                        convertedExpression.Operand,
                        elementPlaceholder: null,
                        elementConversion: null,
                        iterationVariables,
                        deconstruction: null,
                        breakLabel,
                        continueLabel,
                        rewrittenBody);
                }
                else
                {
                    statement = RewriteMultiDimensionalArrayForEachEnumerator(
                        node,
                        convertedExpression.Operand,
                        elementPlaceholder: null,
                        elementConversion: null,
                        iterationVariables,
                        deconstruction: null,
                        breakLabel,
                        continueLabel,
                        rewrittenBody);
                }
            }
            else if (enumeratorInfo is { InlineArraySpanType: not WellKnownType.Unknown })
            {
                statement = RewriteForEachStatementAsFor(
                    node,
                    getPreamble: GetInlineArrayForEachStatementPreambleDelegate(),
                    getItem: GetInlineArrayForEachStatementGetItemDelegate(),
                    getLength: GetInlineArrayForEachStatementGetLengthDelegate(),
                    arg: null,
                    convertedExpression.Operand,
                    enumeratorInfo,
                    elementPlaceholder: null,
                    elementConversion: null,
                    iterationVariables,
                    deconstructionOpt: null,
                    breakLabel,
                    continueLabel,
                    rewrittenBody);
            }
            else
            {
                statement = RewriteForEachEnumerator(
                    node,
                    convertedExpression,
                    enumeratorInfo,
                    elementPlaceholder: null,
                    elementConversion: null,
                    iterationVariables,
                    deconstruction: null,
                    awaitableInfo: null,
                    breakLabel,
                    continueLabel,
                    rewrittenBody);
            }
 
            RemovePlaceholderReplacement(expressionPlaceholder);
 
            _needsSpilling = true;
            return _factory.SpillSequence(
                ImmutableArray<LocalSymbol>.Empty,
                ImmutableArray.Create(statement),
                result: _factory.Literal(0)); // result is unused
        }
    }
}