diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs index 6cbecefe3f188..f8ec243af6ba1 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs @@ -56,8 +56,11 @@ private BoundExpression RewriteCollectionExpressionConversion(Conversion convers case CollectionExpressionTypeKind.CollectionBuilder: // If the collection type is ImmutableArray, then construction is optimized to use // ImmutableCollectionsMarshal.AsImmutableArray. + // The only exception is when 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 (ConversionsBase.IsSpanOrListType(_compilation, node.Type, WellKnownType.System_Collections_Immutable_ImmutableArray_T, out var arrayElementType) && - _compilation.GetWellKnownTypeMember(WellKnownMember.System_Runtime_InteropServices_ImmutableCollectionsMarshal__AsImmutableArray_T) is MethodSymbol asImmutableArray) + _compilation.GetWellKnownTypeMember(WellKnownMember.System_Runtime_InteropServices_ImmutableCollectionsMarshal__AsImmutableArray_T) is MethodSymbol asImmutableArray && + !CanOptimizeSingleSpreadAsCollectionBuilderArgument(node, out _)) { return VisitImmutableArrayCollectionExpression(node, arrayElementType, asImmutableArray); } @@ -138,6 +141,24 @@ static BoundNode unwrapListElement(BoundCollectionExpression node, BoundNode ele } } + 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( @@ -375,7 +396,14 @@ private BoundExpression VisitCollectionBuilderCollectionExpression(BoundCollecti Debug.Assert(spanType.OriginalDefinition.Equals(_compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T), TypeCompareKind.AllIgnoreOptions)); var elementType = spanType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0]; - BoundExpression span = VisitArrayOrSpanCollectionExpression(node, CollectionExpressionTypeKind.ReadOnlySpan, spanType, elementType); + + // 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, diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs index 7306ecda575a2..b532300c6b72c 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs @@ -9295,6 +9295,411 @@ static void Main() Diagnostic(ErrorCode.ERR_AnonMethGrpInForEach, "Main").WithArguments("method group").WithLocation(5, 22)); } + [Fact] + public void SpreadElement_15() + { + // Optimization: pass ReadOnlySpan directly to collection builder method without copying + string source = """ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Runtime.CompilerServices; + + [CollectionBuilder(typeof(MyCollectionBuilder), nameof(MyCollectionBuilder.Create))] + class MyCollection : IEnumerable + { + private readonly T[] _arr; + + public IEnumerator GetEnumerator() => ((IEnumerable)_arr).GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public MyCollection(T[] arr) + { + _arr = arr; + } + } + + class MyCollectionBuilder + { + public static MyCollection Create(ReadOnlySpan span) => new(span.ToArray()); + } + + class Program + { + static void Main() + { + M([1, 2, 3]).Report(); + } + + static MyCollection M(ReadOnlySpan span) => [.. span]; + } + """; + + var verifier = CompileAndVerify([source, s_collectionExtensions], targetFramework: TargetFramework.Net80, expectedOutput: IncludeExpectedOutput("[1, 2, 3], "), verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Program.M", """ + { + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: call "MyCollection MyCollectionBuilder.Create(System.ReadOnlySpan)" + IL_0006: ret + } + """); + } + + [Fact] + public void SpreadElement_16() + { + // Spread operand element type differs from result collection element type by CLR signature + string source = """ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Runtime.CompilerServices; + + [CollectionBuilder(typeof(MyCollectionBuilder), nameof(MyCollectionBuilder.Create))] + class MyCollection : IEnumerable + { + private readonly T[] _arr; + + public IEnumerator GetEnumerator() => ((IEnumerable)_arr).GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public MyCollection(T[] arr) + { + _arr = arr; + } + } + + class MyCollectionBuilder + { + public static MyCollection Create(ReadOnlySpan span) => new(span.ToArray()); + } + + class Program + { + static void Main() + { + M([1, 2, 3]).Report(); + } + + static MyCollection M(ReadOnlySpan span) => [.. span]; + } + """; + + var verifier = CompileAndVerify([source, s_collectionExtensions], targetFramework: TargetFramework.Net80, expectedOutput: IncludeExpectedOutput("[1, 2, 3], "), verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Program.M", """ + { + // Code size 72 (0x48) + .maxstack 3 + .locals init (System.ReadOnlySpan V_0, + int V_1, + object[] V_2, + System.ReadOnlySpan.Enumerator V_3, + int V_4) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldc.i4.0 + IL_0003: stloc.1 + IL_0004: ldloca.s V_0 + IL_0006: call "int System.ReadOnlySpan.Length.get" + IL_000b: newarr "object" + IL_0010: stloc.2 + IL_0011: ldloca.s V_0 + IL_0013: call "System.ReadOnlySpan.Enumerator System.ReadOnlySpan.GetEnumerator()" + IL_0018: stloc.3 + IL_0019: br.s IL_0033 + IL_001b: ldloca.s V_3 + IL_001d: call "ref readonly int System.ReadOnlySpan.Enumerator.Current.get" + IL_0022: ldind.i4 + IL_0023: stloc.s V_4 + IL_0025: ldloc.2 + IL_0026: ldloc.1 + IL_0027: ldloc.s V_4 + IL_0029: box "int" + IL_002e: stelem.ref + IL_002f: ldloc.1 + IL_0030: ldc.i4.1 + IL_0031: add + IL_0032: stloc.1 + IL_0033: ldloca.s V_3 + IL_0035: call "bool System.ReadOnlySpan.Enumerator.MoveNext()" + IL_003a: brtrue.s IL_001b + IL_003c: ldloc.2 + IL_003d: newobj "System.ReadOnlySpan..ctor(object[])" + IL_0042: call "MyCollection MyCollectionBuilder.Create(System.ReadOnlySpan)" + IL_0047: ret + } + """); + } + + [Fact] + public void SpreadElement_17() + { + // 'dynamic' and 'object' are the same things during runtime, so apply 'no-copy' optimization + string source = """ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Runtime.CompilerServices; + + [CollectionBuilder(typeof(MyCollectionBuilder), nameof(MyCollectionBuilder.Create))] + class MyCollection : IEnumerable + { + private readonly T[] _arr; + + public IEnumerator GetEnumerator() => ((IEnumerable)_arr).GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public MyCollection(T[] arr) + { + _arr = arr; + } + } + + class MyCollectionBuilder + { + public static MyCollection Create(ReadOnlySpan span) => new(span.ToArray()); + } + + class Program + { + static void Main() + { + M([1, 2, 3]).Report(); + } + + static MyCollection M(ReadOnlySpan span) => [.. span]; + } + """; + + var verifier = CompileAndVerify([source, s_collectionExtensions], targetFramework: TargetFramework.Net80, expectedOutput: IncludeExpectedOutput("[1, 2, 3], "), verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Program.M", """ + { + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: call "MyCollection MyCollectionBuilder.Create(System.ReadOnlySpan)" + IL_0006: ret + } + """); + } + + [Fact] + public void SpreadElement_18() + { + // Tuple element names only have effect in the code, + // thus '(int A, int B)' and just '(int, int)' are the same thing, + // so apply 'no-copy' optimization here as well + string source = """ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Runtime.CompilerServices; + + [CollectionBuilder(typeof(MyCollectionBuilder), nameof(MyCollectionBuilder.Create))] + class MyCollection : IEnumerable + { + private readonly T[] _arr; + + public IEnumerator GetEnumerator() => ((IEnumerable)_arr).GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public MyCollection(T[] arr) + { + _arr = arr; + } + } + + class MyCollectionBuilder + { + public static MyCollection Create(ReadOnlySpan span) => new(span.ToArray()); + } + + class Program + { + static void Main() + { + M([(1, 1), (2, 2), (3, 3)]).Report(); + } + + static MyCollection<(int A, int B)> M(ReadOnlySpan<(int, int)> span) => [.. span]; + } + """; + + var verifier = CompileAndVerify([source, s_collectionExtensions], targetFramework: TargetFramework.Net80, expectedOutput: IncludeExpectedOutput("[(1, 1), (2, 2), (3, 3)], "), verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Program.M", """ + { + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: call "MyCollection> MyCollectionBuilder.Create>(System.ReadOnlySpan>)" + IL_0006: ret + } + """); + } + + [Fact] + public void SpreadElement_19() + { + // Spread operand element type differs from result collection element type by CLR signature + string source = """ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Runtime.CompilerServices; + + [CollectionBuilder(typeof(MyCollectionBuilder), nameof(MyCollectionBuilder.Create))] + class MyCollection : IEnumerable + { + private readonly T[] _arr; + + public IEnumerator GetEnumerator() => ((IEnumerable)_arr).GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public MyCollection(T[] arr) + { + _arr = arr; + } + } + + class MyCollectionBuilder + { + public static MyCollection Create(ReadOnlySpan span) => new(span.ToArray()); + } + + class Program + { + static void Main() + { + M([(1, 1), (2, 2), (3, 3)]).Report(); + } + + static MyCollection<(object, object)> M(ReadOnlySpan<(int, int)> span) => [.. span]; + } + """; + + var verifier = CompileAndVerify([source, s_collectionExtensions], targetFramework: TargetFramework.Net80, expectedOutput: IncludeExpectedOutput("[(1, 1), (2, 2), (3, 3)], "), verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Program.M", """ + { + // Code size 106 (0x6a) + .maxstack 4 + .locals init (System.ReadOnlySpan> V_0, + int V_1, + System.ValueTuple[] V_2, + System.ReadOnlySpan>.Enumerator V_3, + System.ValueTuple V_4, + System.ValueTuple V_5) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldc.i4.0 + IL_0003: stloc.1 + IL_0004: ldloca.s V_0 + IL_0006: call "int System.ReadOnlySpan>.Length.get" + IL_000b: newarr "System.ValueTuple" + IL_0010: stloc.2 + IL_0011: ldloca.s V_0 + IL_0013: call "System.ReadOnlySpan>.Enumerator System.ReadOnlySpan>.GetEnumerator()" + IL_0018: stloc.3 + IL_0019: br.s IL_0055 + IL_001b: ldloca.s V_3 + IL_001d: call "ref readonly System.ValueTuple System.ReadOnlySpan>.Enumerator.Current.get" + IL_0022: ldobj "System.ValueTuple" + IL_0027: stloc.s V_4 + IL_0029: ldloc.2 + IL_002a: ldloc.1 + IL_002b: ldloc.s V_4 + IL_002d: stloc.s V_5 + IL_002f: ldloc.s V_5 + IL_0031: ldfld "int System.ValueTuple.Item1" + IL_0036: box "int" + IL_003b: ldloc.s V_5 + IL_003d: ldfld "int System.ValueTuple.Item2" + IL_0042: box "int" + IL_0047: newobj "System.ValueTuple..ctor(object, object)" + IL_004c: stelem "System.ValueTuple" + IL_0051: ldloc.1 + IL_0052: ldc.i4.1 + IL_0053: add + IL_0054: stloc.1 + IL_0055: ldloca.s V_3 + IL_0057: call "bool System.ReadOnlySpan>.Enumerator.MoveNext()" + IL_005c: brtrue.s IL_001b + IL_005e: ldloc.2 + IL_005f: newobj "System.ReadOnlySpan>..ctor(System.ValueTuple[])" + IL_0064: call "MyCollection> MyCollectionBuilder.Create>(System.ReadOnlySpan>)" + IL_0069: ret + } + """); + } + + [Fact] + public void SpreadElement_20() + { + // Nullability annotations only affect diagnostics and have no effect on the runtime, + // so 'T?[]' and 'T[]' are the same times, therefore apply 'no-copy' optimization here + string source = """ + #nullable enable + + using System; + using System.Collections; + using System.Collections.Generic; + using System.Runtime.CompilerServices; + + [CollectionBuilder(typeof(MyCollectionBuilder), nameof(MyCollectionBuilder.Create))] + class MyCollection : IEnumerable + { + private readonly T[] _arr; + + public IEnumerator GetEnumerator() => ((IEnumerable)_arr).GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public MyCollection(T[] arr) + { + _arr = arr; + } + } + + class MyCollectionBuilder + { + public static MyCollection Create(ReadOnlySpan span) => new(span.ToArray()); + } + + class Program + { + static void Main() + { + M([[1], [2], [3]]).Report(); + } + + static MyCollection M(ReadOnlySpan span) => [.. span]; + } + """; + + var verifier = CompileAndVerify([source, s_collectionExtensions], targetFramework: TargetFramework.Net80, expectedOutput: IncludeExpectedOutput("[[1], [2], [3]], "), verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Program.M", """ + { + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: call "MyCollection MyCollectionBuilder.Create(System.ReadOnlySpan)" + IL_0006: ret + } + """); + } + [Fact] public void SpreadElement_Dynamic_02() { @@ -20570,6 +20975,204 @@ public void RefStruct_04() ); } + [Fact] + public void RefStruct_EnsureCopyingSemanticsWhenReadOnlySpanParameterOfCollectionBuilderIsNotScoped() + { + string source = """ + using System; + using System.Runtime.CompilerServices; + + [CollectionBuilder(typeof(SpanWrap), "Create")] + ref struct SpanWrap + { + private readonly ReadOnlySpan _ros; + + public int Length => _ros.Length; + public T this[int index] => _ros[index]; + + public Enumerator GetEnumerator() => default; + + public SpanWrap(ReadOnlySpan ros) + { + _ros = ros; + } + + public ref struct Enumerator + { + public bool MoveNext() => default; + public T Current => default; + } + } + + static class SpanWrap + { + public static SpanWrap Create(ReadOnlySpan values) => new SpanWrap(values); + } + + class Program + { + static void Main() + { + int[] arr = { 1, 2, 3 }; + ReadOnlySpan span = arr; + SpanWrap spanWrap = [.. span]; + + arr[1] = 4; + + span.Report(); + ReportSpanWrap(spanWrap); + } + + static void ReportSpanWrap(SpanWrap spanWrap) + { + Console.Write('['); + + for (int i = 0; i < spanWrap.Length - 1; i++) + { + Console.Write(spanWrap[i] + ", "); + } + + Console.Write(spanWrap[^1]); + Console.Write(']'); + } + } + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensionsWithSpan], + targetFramework: TargetFramework.Net80, + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput("[1, 4, 3], [1, 2, 3]")); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Program.Main", """ + { + // Code size 59 (0x3b) + .maxstack 3 + .locals init (System.ReadOnlySpan V_0, //span + SpanWrap V_1) //spanWrap + IL_0000: ldc.i4.3 + IL_0001: newarr "int" + IL_0006: dup + IL_0007: ldtoken ".__StaticArrayInitTypeSize=12 .4636993D3E1DA4E9D6B8F87B79E8F7C6D018580D52661950EABC3845C5897A4D" + IL_000c: call "void System.Runtime.CompilerServices.RuntimeHelpers.InitializeArray(System.Array, System.RuntimeFieldHandle)" + IL_0011: dup + IL_0012: call "System.ReadOnlySpan System.ReadOnlySpan.op_Implicit(int[])" + IL_0017: stloc.0 + IL_0018: ldloca.s V_0 + IL_001a: call "int[] System.ReadOnlySpan.ToArray()" + IL_001f: newobj "System.ReadOnlySpan..ctor(int[])" + IL_0024: call "SpanWrap SpanWrap.Create(System.ReadOnlySpan)" + IL_0029: stloc.1 + IL_002a: ldc.i4.1 + IL_002b: ldc.i4.4 + IL_002c: stelem.i4 + IL_002d: ldloca.s V_0 + IL_002f: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" + IL_0034: ldloc.1 + IL_0035: call "void Program.ReportSpanWrap(SpanWrap)" + IL_003a: ret + } + """); + } + + [Fact] + public void RefStruct_CanSkipCopyingReadOnlySpanWhenCollectionBuilderParameterIsScoped() + { + string source = """ + using System; + using System.Runtime.CompilerServices; + + [CollectionBuilder(typeof(ArrayWrap), "Create")] + ref struct ArrayWrap + { + private readonly T[] _arr; + + public int Length => _arr.Length; + public T this[int index] => _arr[index]; + + public Enumerator GetEnumerator() => default; + + public ArrayWrap(T[] arr) + { + _arr = arr; + } + + public ref struct Enumerator + { + public bool MoveNext() => default; + public T Current => default; + } + } + + static class ArrayWrap + { + public static ArrayWrap Create(scoped ReadOnlySpan values) => new ArrayWrap(values.ToArray()); + } + + class Program + { + static void Main() + { + int[] arr = { 1, 2, 3 }; + ReadOnlySpan span = arr; + ArrayWrap arrWrap = [.. span]; + + arr[1] = 4; + + span.Report(); + ReportArrayWrap(arrWrap); + } + + static void ReportArrayWrap(ArrayWrap arrWrap) + { + Console.Write('['); + + for (int i = 0; i < arrWrap.Length - 1; i++) + { + Console.Write(arrWrap[i] + ", "); + } + + Console.Write(arrWrap[^1]); + Console.Write(']'); + } + } + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensionsWithSpan], + targetFramework: TargetFramework.Net80, + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput("[1, 4, 3], [1, 2, 3]")); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Program.Main", """ + { + // Code size 48 (0x30) + .maxstack 3 + .locals init (System.ReadOnlySpan V_0, //span + ArrayWrap V_1) //arrWrap + IL_0000: ldc.i4.3 + IL_0001: newarr "int" + IL_0006: dup + IL_0007: ldtoken ".__StaticArrayInitTypeSize=12 .4636993D3E1DA4E9D6B8F87B79E8F7C6D018580D52661950EABC3845C5897A4D" + IL_000c: call "void System.Runtime.CompilerServices.RuntimeHelpers.InitializeArray(System.Array, System.RuntimeFieldHandle)" + IL_0011: dup + IL_0012: call "System.ReadOnlySpan System.ReadOnlySpan.op_Implicit(int[])" + IL_0017: stloc.0 + IL_0018: ldloc.0 + IL_0019: call "ArrayWrap ArrayWrap.Create(scoped System.ReadOnlySpan)" + IL_001e: stloc.1 + IL_001f: ldc.i4.1 + IL_0020: ldc.i4.4 + IL_0021: stelem.i4 + IL_0022: ldloca.s V_0 + IL_0024: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" + IL_0029: ldloc.1 + IL_002a: call "void Program.ReportArrayWrap(ArrayWrap)" + IL_002f: ret + } + """); + } + [CombinatorialData] [Theory] public void RefSafety_Return_01([CombinatorialValues(TargetFramework.Net70, TargetFramework.Net80)] TargetFramework targetFramework) @@ -24016,6 +24619,36 @@ .locals init (<>y__InlineArray3 V_0) """); } + [Fact] + public void ReadOnlySpan_EnsureCopyingSemantics() + { + string source = """ + using System; + + class Program + { + static void Main() + { + int[] arr = { 1, 2, 3 }; + ReadOnlySpan span1 = arr; + ReadOnlySpan span2 = [.. span1]; + + arr[1] = 4; + + span1.Report(); + span2.Report(); + } + } + """; + + var verifier = CompileAndVerify( + [source, s_collectionExtensionsWithSpan], + targetFramework: TargetFramework.Net80, + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput("[1, 4, 3], [1, 2, 3], ")); + verifier.VerifyDiagnostics(); + } + [Fact] public void RuntimeHelpers_CreateSpan_MissingCreateSpan() { @@ -28841,6 +29474,101 @@ .locals init (System.Collections.Immutable.ImmutableArray V_0, //arr """); } + [Fact] + public void ImmutableArray_09() + { + string sourceA = """ + using System; + using System.Collections.Immutable; + + class Program + { + static void Main() + { + M([1, 2, 3]).Report(); + } + + static ImmutableArray M(ReadOnlySpan span) => [.. span]; + } + """; + + var verifier = CompileAndVerify([sourceA, s_collectionExtensions], targetFramework: TargetFramework.Net80, expectedOutput: IncludeExpectedOutput("[1, 2, 3],"), verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Program.M", """ + { + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: call "System.Collections.Immutable.ImmutableArray System.Collections.Immutable.ImmutableArray.Create(System.ReadOnlySpan)" + IL_0006: ret + } + """); + } + + [Fact] + public void ImmutableArray_10() + { + string sourceA = """ + using System; + using System.Collections.Immutable; + + class Program + { + static void Main() + { + M([1, 2, 3]).Report(); + } + + static ImmutableArray M(ReadOnlySpan span) => [.. span]; + } + """; + + var verifier = CompileAndVerify([sourceA, s_collectionExtensions], targetFramework: TargetFramework.Net80, expectedOutput: IncludeExpectedOutput("[1, 2, 3],"), verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Program.M", """ + { + // Code size 67 (0x43) + .maxstack 3 + .locals init (System.ReadOnlySpan V_0, + int V_1, + object[] V_2, + System.ReadOnlySpan.Enumerator V_3, + int V_4) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldc.i4.0 + IL_0003: stloc.1 + IL_0004: ldloca.s V_0 + IL_0006: call "int System.ReadOnlySpan.Length.get" + IL_000b: newarr "object" + IL_0010: stloc.2 + IL_0011: ldloca.s V_0 + IL_0013: call "System.ReadOnlySpan.Enumerator System.ReadOnlySpan.GetEnumerator()" + IL_0018: stloc.3 + IL_0019: br.s IL_0033 + IL_001b: ldloca.s V_3 + IL_001d: call "ref readonly int System.ReadOnlySpan.Enumerator.Current.get" + IL_0022: ldind.i4 + IL_0023: stloc.s V_4 + IL_0025: ldloc.2 + IL_0026: ldloc.1 + IL_0027: ldloc.s V_4 + IL_0029: box "int" + IL_002e: stelem.ref + IL_002f: ldloc.1 + IL_0030: ldc.i4.1 + IL_0031: add + IL_0032: stloc.1 + IL_0033: ldloca.s V_3 + IL_0035: call "bool System.ReadOnlySpan.Enumerator.MoveNext()" + IL_003a: brtrue.s IL_001b + IL_003c: ldloc.2 + IL_003d: call "System.Collections.Immutable.ImmutableArray System.Runtime.InteropServices.ImmutableCollectionsMarshal.AsImmutableArray(object[])" + IL_0042: ret + } + """); + } + [Fact] public void SpanImplicitAllocationWarning_01() {