From 511526ca35c7c048752a14e9291adb07ba865e7e Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Mon, 11 Dec 2023 15:33:13 -0800 Subject: [PATCH 01/18] Collection expression spread optimization --- .../LocalRewriter_CollectionExpression.cs | 243 ++- .../Semantics/CollectionExpressionTests.cs | 1745 +++++++++++------ .../Core/Portable/WellKnownMember.cs | 7 + .../Core/Portable/WellKnownMembers.cs | 63 + 4 files changed, 1489 insertions(+), 569 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs index 4e1e64796fafb..320af1bf1fcb1 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs @@ -5,6 +5,7 @@ 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; @@ -443,6 +444,44 @@ private BoundExpression CreateAndPopulateArray(BoundCollectionExpression node, A throw ExceptionUtilities.UnexpectedValue(node); } + // Collection-expr is of the form `[..spreadExpression]`, where 'spreadExpression' has same element type as the target collection. + // Optimize to `spreadExpression.ToArray()` if possible. + if (node is { Elements: [BoundCollectionExpressionSpreadElement { Expression: { } spreadExpression } spreadElement] } + && spreadElement.IteratorBody is BoundExpressionStatement expressionStatement + && expressionStatement.Expression is not BoundConversion) + { + var spreadTypeOriginalDefinition = spreadExpression.Type!.OriginalDefinition; + if (tryGetToArrayMethod(spreadTypeOriginalDefinition, WellKnownType.System_Collections_Generic_List_T, WellKnownMember.System_Collections_Generic_List_T__ToArray) is { } listToArrayMethod) + { + var rewrittenSpreadExpression = VisitExpression(spreadExpression); + return Factory.Call(rewrittenSpreadExpression, listToArrayMethod.AsMember((NamedTypeSymbol)spreadExpression.Type!)); + } + + if (GetAsSpanMethod(spreadExpression.Type) is { } asSpanMethod) + { + var spanType = (asSpanMethod is null ? spreadExpression : CallAsSpanMethod(spreadExpression, asSpanMethod)).Type!.OriginalDefinition; + if ((tryGetToArrayMethod(spanType, WellKnownType.System_Span_T, WellKnownMember.System_Span_T__ToArray) + ?? tryGetToArrayMethod(spanType, WellKnownType.System_ReadOnlySpan_T, WellKnownMember.System_ReadOnlySpan_T__ToArray)) + is { } toArrayMethod) + { + var rewrittenSpreadExpression = VisitExpression(spreadExpression); + if (asSpanMethod is not null) + rewrittenSpreadExpression = CallAsSpanMethod(rewrittenSpreadExpression, asSpanMethod); + return Factory.Call(rewrittenSpreadExpression, toArrayMethod.AsMember((NamedTypeSymbol)rewrittenSpreadExpression.Type!)); + } + } + + MethodSymbol? tryGetToArrayMethod(TypeSymbol spreadTypeOriginalDefinition, WellKnownType wellKnownType, WellKnownMember wellKnownMember) + { + if (spreadTypeOriginalDefinition.Equals(this._compilation.GetWellKnownType(wellKnownType))) + { + return _factory.WellKnownMethod(wellKnownMember, isOptional: true); + } + + return null; + } + } + if (numberIncludingLastSpread == 0) { int knownLength = elements.Length; @@ -492,6 +531,7 @@ private BoundExpression CreateAndPopulateArray(BoundCollectionExpression node, A localsBuilder.Add(arrayTemp); sideEffects.Add(assignmentToTemp); + BoundLocal? targetSpanTemp = null; AddCollectionExpressionElements( elements, arrayTemp, @@ -522,6 +562,25 @@ private BoundExpression CreateAndPopulateArray(BoundCollectionExpression node, A _factory.Binary(BinaryOperatorKind.Addition, indexTemp.Type, indexTemp, _factory.Literal(1)), isRef: false, indexTemp.Type)); + }, + (sideEffects, targetArray, spreadElement, rewrittenSpreadOperand) => + { + if (TryPrepareCopyToOptimization(spreadElement, rewrittenSpreadOperand) is not var (spanSliceMethod, spreadElementAsSpan, getLengthMethod, copyToMethod)) + return false; + + if (targetSpanTemp is null) + { + var targetSpan = TryConvertToSpanOrReadOnlySpan(targetArray); + if (targetSpan is null) + return false; + + targetSpanTemp = _factory.StoreToTemp(targetSpan, out var assignmentToTemp); + localsBuilder.Add(targetSpanTemp); + sideEffects.Add(assignmentToTemp); + } + + PerformCopyToOptimization(sideEffects, localsBuilder, indexTemp, targetSpanTemp, rewrittenSpreadOperand, spanSliceMethod, spreadElementAsSpan, getLengthMethod, copyToMethod); + return true; }); var locals = localsBuilder.SelectAsArray(l => l.LocalSymbol); @@ -535,6 +594,155 @@ private BoundExpression CreateAndPopulateArray(BoundCollectionExpression node, A arrayType); } + private MethodSymbol? GetAsSpanMethod(TypeSymbol type) + { + if (type is NamedTypeSymbol spanType + && (spanType.OriginalDefinition.Equals(_compilation.GetWellKnownType(WellKnownType.System_Span_T), TypeCompareKind.ConsiderEverything) + || spanType.OriginalDefinition.Equals(_compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T), TypeCompareKind.ConsiderEverything))) + { + return null; + } + + if (type is ArrayTypeSymbol { IsSZArray: true } arrayType + && _factory.WellKnownMethod(WellKnownMember.System_Span_T__ctor_Array, isOptional: true) is { } spanCtorArray) + { + return spanCtorArray.AsMember(spanCtorArray.ContainingType.Construct(arrayType.ElementType)); + } + + if (type is NamedTypeSymbol immutableArrayType + && immutableArrayType.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) + { + return immutableArrayAsSpanMethod!.AsMember(immutableArrayType); + } + + if (type is NamedTypeSymbol listType + && listType.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) + { + return collectionsMarshalAsSpanMethod.Construct(listType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type); + } + + return null; + } + + private BoundExpression? TryConvertToSpanOrReadOnlySpan(BoundExpression expression) + { + var type = expression.Type; + Debug.Assert(type is not null); + + if (GetAsSpanMethod(type) is not { } asSpanMethod) + { + return null; + } + + if (asSpanMethod is null) + { + return expression; + } + else + { + return CallAsSpanMethod(expression, asSpanMethod); + } + } + + private BoundExpression CallAsSpanMethod(BoundExpression spreadExpression, MethodSymbol asSpanMethod) + { + 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); + } + } + + /// + /// Verifies presence of methods necessary for the CopyTo optimization + /// without performing mutating actions e.g. appending to side effects or locals builders. + /// + private (MethodSymbol spanSliceMethod, BoundExpression spreadElementAsSpan, MethodSymbol getLengthMethod, MethodSymbol copyToMethod)? TryPrepareCopyToOptimization( + 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 (TryConvertToSpanOrReadOnlySpan(rewrittenSpreadOperand) is not { } spreadOperandAsSpan) + return null; + + if ((tryGetSpanMethodsForSpread(WellKnownType.System_ReadOnlySpan_T, WellKnownMember.System_ReadOnlySpan_T__get_Length, WellKnownMember.System_ReadOnlySpan_T__CopyTo_Span_T) + ?? tryGetSpanMethodsForSpread(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)? tryGetSpanMethodsForSpread( + 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 sideEffects, + ArrayBuilder 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)); + } + /// /// Create and populate an list from a collection expression. /// The collection may or may not have a known length. @@ -648,11 +856,20 @@ private BoundExpression CreateAndPopulateList(BoundCollectionExpression node, Ty _factory.Binary(BinaryOperatorKind.Addition, indexTemp.Type, indexTemp, _factory.Literal(1)), isRef: false, indexTemp.Type)); + }, + (sideEffects, spanTemp, spreadElement, rewrittenSpreadOperand) => + { + if (TryPrepareCopyToOptimization(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 = ((MethodSymbol)_factory.WellKnownMember(WellKnownMember.System_Collections_Generic_List_T__Add)).AsMember(collectionType); + 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, @@ -664,6 +881,24 @@ private BoundExpression CreateAndPopulateList(BoundCollectionExpression node, Ty // list.Add(element); expressions.Add( _factory.Call(listTemp, addMethod, rewrittenValue)); + }, + (sideEffects, spanTemp, spreadElement, rewrittenSpreadOperand) => + { + if (addRangeMethod is null) + return false; + + var type = rewrittenSpreadOperand.Type!; + + var useSiteInfo = GetNewCompoundUseSiteInfo(); + var conversion = _compilation.Conversions.ClassifyConversionFromType(type, addRangeMethod.Parameters[0].Type, isChecked: false, ref useSiteInfo); + _diagnostics.Add(rewrittenSpreadOperand.Syntax, useSiteInfo); + if (conversion.IsImplicit && conversion.IsReference) + { + sideEffects.Add(_factory.Call(listTemp, addRangeMethod, MakeConversionNode(rewrittenSpreadOperand, type, @checked: false, markAsChecked: true))); + return true; + } + + return false; }); } @@ -708,7 +943,8 @@ private void AddCollectionExpressionElements( ArrayBuilder rewrittenExpressions, int numberIncludingLastSpread, ArrayBuilder sideEffects, - Action, BoundExpression, BoundExpression> addElement) + Action, BoundExpression, BoundExpression> addElement, + Func, BoundExpression, BoundCollectionExpressionSpreadElement, BoundExpression, bool>? tryOptimizeSpreadElement = null) { for (int i = 0; i < elements.Length; i++) { @@ -719,6 +955,9 @@ private void AddCollectionExpressionElements( if (element is BoundCollectionExpressionSpreadElement spreadElement) { + if (tryOptimizeSpreadElement?.Invoke(sideEffects, rewrittenReceiver, spreadElement, rewrittenExpression) == true) + continue; + var rewrittenElement = MakeCollectionExpressionSpreadElement( spreadElement, rewrittenExpression, diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs index 930af260f223b..2ac8fe0b1d847 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs @@ -6054,96 +6054,32 @@ .locals init (System.Collections.Generic.List V_0, ("int[]", "int[]") => """ { - // Code size 49 (0x31) - .maxstack 3 - .locals init (int V_0, - int[] V_1, - int[] V_2, - int V_3, - int V_4) + // Code size 21 (0x15) + .maxstack 2 + .locals init (System.Span V_0) IL_0000: ldarg.0 - IL_0001: ldc.i4.0 - IL_0002: stloc.0 - IL_0003: dup - IL_0004: ldlen - IL_0005: conv.i4 - IL_0006: newarr "int" - IL_000b: stloc.1 - IL_000c: stloc.2 - IL_000d: ldc.i4.0 - IL_000e: stloc.3 - IL_000f: br.s IL_0023 - IL_0011: ldloc.2 - IL_0012: ldloc.3 - IL_0013: ldelem.i4 - IL_0014: stloc.s V_4 - IL_0016: ldloc.1 - IL_0017: ldloc.0 - IL_0018: ldloc.s V_4 - IL_001a: stelem.i4 - IL_001b: ldloc.0 - IL_001c: ldc.i4.1 - IL_001d: add - IL_001e: stloc.0 - IL_001f: ldloc.3 - IL_0020: ldc.i4.1 - IL_0021: add - IL_0022: stloc.3 - IL_0023: ldloc.3 - IL_0024: ldloc.2 - IL_0025: ldlen - IL_0026: conv.i4 - IL_0027: blt.s IL_0011 - IL_0029: ldloc.1 - IL_002a: ldc.i4.0 - IL_002b: call "void CollectionExtensions.Report(object, bool)" - IL_0030: ret + IL_0001: newobj "System.Span..ctor(int[])" + IL_0006: stloc.0 + IL_0007: ldloca.s V_0 + IL_0009: call "int[] System.Span.ToArray()" + IL_000e: ldc.i4.0 + IL_000f: call "void CollectionExtensions.Report(object, bool)" + IL_0014: ret } """, ("ReadOnlySpan", "ReadOnlySpan") => """ { - // Code size 72 (0x48) - .maxstack 3 - .locals init (System.ReadOnlySpan V_0, //y - System.ReadOnlySpan V_1, - int V_2, - int[] V_3, - System.ReadOnlySpan.Enumerator V_4, - int V_5) - IL_0000: ldarg.0 - IL_0001: stloc.1 - IL_0002: ldc.i4.0 - IL_0003: stloc.2 - IL_0004: ldloca.s V_1 - IL_0006: call "int System.ReadOnlySpan.Length.get" - IL_000b: newarr "int" - IL_0010: stloc.3 - IL_0011: ldloca.s V_1 - IL_0013: call "System.ReadOnlySpan.Enumerator System.ReadOnlySpan.GetEnumerator()" - IL_0018: stloc.s V_4 - IL_001a: br.s IL_002f - IL_001c: ldloca.s V_4 - IL_001e: call "ref readonly int System.ReadOnlySpan.Enumerator.Current.get" - IL_0023: ldind.i4 - IL_0024: stloc.s V_5 - IL_0026: ldloc.3 - IL_0027: ldloc.2 - IL_0028: ldloc.s V_5 - IL_002a: stelem.i4 - IL_002b: ldloc.2 - IL_002c: ldc.i4.1 - IL_002d: add - IL_002e: stloc.2 - IL_002f: ldloca.s V_4 - IL_0031: call "bool System.ReadOnlySpan.Enumerator.MoveNext()" - IL_0036: brtrue.s IL_001c - IL_0038: ldloca.s V_0 - IL_003a: ldloc.3 - IL_003b: call "System.ReadOnlySpan..ctor(int[])" - IL_0040: ldloca.s V_0 - IL_0042: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" - IL_0047: ret + // Code size 22 (0x16) + .maxstack 2 + .locals init (System.ReadOnlySpan V_0) //y + IL_0000: ldloca.s V_0 + IL_0002: ldarga.s V_0 + IL_0004: call "int[] System.ReadOnlySpan.ToArray()" + IL_0009: call "System.ReadOnlySpan..ctor(int[])" + IL_000e: ldloca.s V_0 + IL_0010: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" + IL_0015: ret } """, _ => null @@ -6190,73 +6126,64 @@ static void Append({{collectionType}} x) verifier.VerifyIL("Program.Append", """ { - // Code size 134 (0x86) - .maxstack 3 - .locals init (System.ReadOnlySpan V_0, //z - System.ReadOnlySpan V_1, + // Code size 136 (0x88) + .maxstack 5 + .locals init (System.ReadOnlySpan V_0, //y + System.ReadOnlySpan V_1, //z System.ReadOnlySpan V_2, - int V_3, - int[] V_4, - System.ReadOnlySpan.Enumerator V_5, - int V_6) + System.ReadOnlySpan V_3, + int V_4, + int[] V_5, + System.Span V_6) IL_0000: ldtoken ".__StaticArrayInitTypeSize=8_Align=4 .34FB5C825DE7CA4AEA6E712F19D439C1DA0C92C37B423936C5F618545CA4FA1F4" IL_0005: call "System.ReadOnlySpan System.Runtime.CompilerServices.RuntimeHelpers.CreateSpan(System.RuntimeFieldHandle)" - IL_000a: ldarg.0 - IL_000b: stloc.1 - IL_000c: stloc.2 - IL_000d: ldc.i4.0 - IL_000e: stloc.3 - IL_000f: ldloca.s V_1 - IL_0011: call "int System.ReadOnlySpan.Length.get" - IL_0016: ldloca.s V_2 - IL_0018: call "int System.ReadOnlySpan.Length.get" - IL_001d: add - IL_001e: newarr "int" - IL_0023: stloc.s V_4 - IL_0025: ldloca.s V_1 - IL_0027: call "System.ReadOnlySpan.Enumerator System.ReadOnlySpan.GetEnumerator()" - IL_002c: stloc.s V_5 - IL_002e: br.s IL_0044 - IL_0030: ldloca.s V_5 - IL_0032: call "ref readonly int System.ReadOnlySpan.Enumerator.Current.get" - IL_0037: ldind.i4 - IL_0038: stloc.s V_6 - IL_003a: ldloc.s V_4 - IL_003c: ldloc.3 - IL_003d: ldloc.s V_6 - IL_003f: stelem.i4 - IL_0040: ldloc.3 - IL_0041: ldc.i4.1 - IL_0042: add - IL_0043: stloc.3 - IL_0044: ldloca.s V_5 - IL_0046: call "bool System.ReadOnlySpan.Enumerator.MoveNext()" - IL_004b: brtrue.s IL_0030 - IL_004d: ldloca.s V_2 - IL_004f: call "System.ReadOnlySpan.Enumerator System.ReadOnlySpan.GetEnumerator()" - IL_0054: stloc.s V_5 - IL_0056: br.s IL_006c - IL_0058: ldloca.s V_5 - IL_005a: call "ref readonly int System.ReadOnlySpan.Enumerator.Current.get" - IL_005f: ldind.i4 - IL_0060: stloc.s V_6 - IL_0062: ldloc.s V_4 - IL_0064: ldloc.3 - IL_0065: ldloc.s V_6 - IL_0067: stelem.i4 - IL_0068: ldloc.3 - IL_0069: ldc.i4.1 - IL_006a: add - IL_006b: stloc.3 - IL_006c: ldloca.s V_5 - IL_006e: call "bool System.ReadOnlySpan.Enumerator.MoveNext()" - IL_0073: brtrue.s IL_0058 - IL_0075: ldloca.s V_0 - IL_0077: ldloc.s V_4 - IL_0079: call "System.ReadOnlySpan..ctor(int[])" - IL_007e: ldloca.s V_0 - IL_0080: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" - IL_0085: ret + IL_000a: stloc.0 + IL_000b: ldloca.s V_1 + IL_000d: ldarg.0 + IL_000e: stloc.2 + IL_000f: ldloc.0 + IL_0010: stloc.3 + IL_0011: ldc.i4.0 + IL_0012: stloc.s V_4 + IL_0014: ldloca.s V_2 + IL_0016: call "int System.ReadOnlySpan.Length.get" + IL_001b: ldloca.s V_3 + IL_001d: call "int System.ReadOnlySpan.Length.get" + IL_0022: add + IL_0023: newarr "int" + IL_0028: stloc.s V_5 + IL_002a: ldloca.s V_6 + IL_002c: ldloc.s V_5 + IL_002e: call "System.Span..ctor(int[])" + IL_0033: ldloca.s V_2 + IL_0035: ldloca.s V_6 + IL_0037: ldloc.s V_4 + IL_0039: ldloca.s V_2 + IL_003b: call "int System.ReadOnlySpan.Length.get" + IL_0040: call "System.Span System.Span.Slice(int, int)" + IL_0045: call "void System.ReadOnlySpan.CopyTo(System.Span)" + IL_004a: ldloc.s V_4 + IL_004c: ldloca.s V_2 + IL_004e: call "int System.ReadOnlySpan.Length.get" + IL_0053: add + IL_0054: stloc.s V_4 + IL_0056: ldloca.s V_3 + IL_0058: ldloca.s V_6 + IL_005a: ldloc.s V_4 + IL_005c: ldloca.s V_3 + IL_005e: call "int System.ReadOnlySpan.Length.get" + IL_0063: call "System.Span System.Span.Slice(int, int)" + IL_0068: call "void System.ReadOnlySpan.CopyTo(System.Span)" + IL_006d: ldloc.s V_4 + IL_006f: ldloca.s V_3 + IL_0071: call "int System.ReadOnlySpan.Length.get" + IL_0076: add + IL_0077: stloc.s V_4 + IL_0079: ldloc.s V_5 + IL_007b: call "System.ReadOnlySpan..ctor(int[])" + IL_0080: ldloca.s V_1 + IL_0082: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" + IL_0087: ret } """); } @@ -6877,41 +6804,18 @@ .locals init (int V_0, verifier.VerifyIL("Program.F", """ { - // Code size 63 (0x3f) - .maxstack 2 - .locals init (System.Collections.Generic.List V_0, - System.Collections.Generic.List.Enumerator V_1, - object V_2) + // Code size 21 (0x15) + .maxstack 3 + .locals init (System.Collections.Generic.List V_0) IL_0000: ldarg.0 - IL_0001: dup - IL_0002: callvirt "int System.Collections.Generic.List.Count.get" - IL_0007: newobj "System.Collections.Generic.List..ctor(int)" - IL_000c: stloc.0 - IL_000d: callvirt "System.Collections.Generic.List.Enumerator System.Collections.Generic.List.GetEnumerator()" - IL_0012: stloc.1 - .try - { - IL_0013: br.s IL_0024 - IL_0015: ldloca.s V_1 - IL_0017: call "dynamic System.Collections.Generic.List.Enumerator.Current.get" - IL_001c: stloc.2 - IL_001d: ldloc.0 - IL_001e: ldloc.2 - IL_001f: callvirt "void System.Collections.Generic.List.Add(object)" - IL_0024: ldloca.s V_1 - IL_0026: call "bool System.Collections.Generic.List.Enumerator.MoveNext()" - IL_002b: brtrue.s IL_0015 - IL_002d: leave.s IL_003d - } - finally - { - IL_002f: ldloca.s V_1 - IL_0031: constrained. "System.Collections.Generic.List.Enumerator" - IL_0037: callvirt "void System.IDisposable.Dispose()" - IL_003c: endfinally - } - IL_003d: ldloc.0 - IL_003e: ret + IL_0001: stloc.0 + IL_0002: ldloc.0 + IL_0003: callvirt "int System.Collections.Generic.List.Count.get" + IL_0008: newobj "System.Collections.Generic.List..ctor(int)" + IL_000d: dup + IL_000e: ldloc.0 + IL_000f: callvirt "void System.Collections.Generic.List.AddRange(System.Collections.Generic.IEnumerable)" + IL_0014: ret } """); } @@ -7124,13 +7028,14 @@ static void Main() expectedOutput: IncludeExpectedOutput("[1, 2, 3], ")); verifier.VerifyIL("Program.Convert", """ { - // Code size 137 (0x89) - .maxstack 3 + // Code size 113 (0x71) + .maxstack 4 .locals init (System.Collections.Generic.List V_0, int V_1, T[] V_2, - System.Collections.Generic.List.Enumerator V_3, - T V_4) + System.Span V_3, + System.Span V_4, + System.Span V_5) IL_0000: ldarg.0 IL_0001: ldarg.1 IL_0002: stloc.0 @@ -7143,65 +7048,40 @@ .locals init (System.Collections.Generic.List V_0, IL_0011: add IL_0012: newarr "T" IL_0017: stloc.2 - IL_0018: callvirt "System.Collections.Generic.List.Enumerator System.Collections.Generic.List.GetEnumerator()" - IL_001d: stloc.3 - .try - { - IL_001e: br.s IL_0036 - IL_0020: ldloca.s V_3 - IL_0022: call "T System.Collections.Generic.List.Enumerator.Current.get" - IL_0027: stloc.s V_4 - IL_0029: ldloc.2 - IL_002a: ldloc.1 - IL_002b: ldloc.s V_4 - IL_002d: stelem "T" - IL_0032: ldloc.1 - IL_0033: ldc.i4.1 - IL_0034: add - IL_0035: stloc.1 - IL_0036: ldloca.s V_3 - IL_0038: call "bool System.Collections.Generic.List.Enumerator.MoveNext()" - IL_003d: brtrue.s IL_0020 - IL_003f: leave.s IL_004f - } - finally - { - IL_0041: ldloca.s V_3 - IL_0043: constrained. "System.Collections.Generic.List.Enumerator" - IL_0049: callvirt "void System.IDisposable.Dispose()" - IL_004e: endfinally - } - IL_004f: ldloc.0 - IL_0050: callvirt "System.Collections.Generic.List.Enumerator System.Collections.Generic.List.GetEnumerator()" - IL_0055: stloc.3 - .try - { - IL_0056: br.s IL_006e - IL_0058: ldloca.s V_3 - IL_005a: call "T System.Collections.Generic.List.Enumerator.Current.get" - IL_005f: stloc.s V_4 - IL_0061: ldloc.2 - IL_0062: ldloc.1 - IL_0063: ldloc.s V_4 - IL_0065: stelem "T" - IL_006a: ldloc.1 - IL_006b: ldc.i4.1 - IL_006c: add - IL_006d: stloc.1 - IL_006e: ldloca.s V_3 - IL_0070: call "bool System.Collections.Generic.List.Enumerator.MoveNext()" - IL_0075: brtrue.s IL_0058 - IL_0077: leave.s IL_0087 - } - finally - { - IL_0079: ldloca.s V_3 - IL_007b: constrained. "System.Collections.Generic.List.Enumerator" - IL_0081: callvirt "void System.IDisposable.Dispose()" - IL_0086: endfinally - } - IL_0087: ldloc.2 - IL_0088: ret + IL_0018: ldloca.s V_3 + IL_001a: ldloc.2 + IL_001b: call "System.Span..ctor(T[])" + IL_0020: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_0025: stloc.s V_4 + IL_0027: ldloca.s V_4 + IL_0029: ldloca.s V_3 + IL_002b: ldloc.1 + IL_002c: ldloca.s V_4 + IL_002e: call "int System.Span.Length.get" + IL_0033: call "System.Span System.Span.Slice(int, int)" + IL_0038: call "void System.Span.CopyTo(System.Span)" + IL_003d: ldloc.1 + IL_003e: ldloca.s V_4 + IL_0040: call "int System.Span.Length.get" + IL_0045: add + IL_0046: stloc.1 + IL_0047: ldloc.0 + IL_0048: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_004d: stloc.s V_5 + IL_004f: ldloca.s V_5 + IL_0051: ldloca.s V_3 + IL_0053: ldloc.1 + IL_0054: ldloca.s V_5 + IL_0056: call "int System.Span.Length.get" + IL_005b: call "System.Span System.Span.Slice(int, int)" + IL_0060: call "void System.Span.CopyTo(System.Span)" + IL_0065: ldloc.1 + IL_0066: ldloca.s V_5 + IL_0068: call "int System.Span.Length.get" + IL_006d: add + IL_006e: stloc.1 + IL_006f: ldloc.2 + IL_0070: ret } """); } @@ -7241,21 +7121,22 @@ static int[] F() """)); verifier.VerifyIL("Program.F", """ { - // Code size 300 (0x12c) + // Code size 296 (0x128) .maxstack 5 .locals init (int[] V_0, int[,] V_1, System.Collections.Generic.List V_2, int V_3, int[] V_4, - int[] V_5, - int V_6, - int V_7, + System.Span V_5, + System.Span V_6, + System.Span V_7, int[,] V_8, int V_9, int V_10, int V_11, - System.Collections.Generic.List.Enumerator V_12) + int V_12, + int V_13) IL_0000: ldstr "A" IL_0005: ldc.i4.2 IL_0006: newarr "int" @@ -7301,110 +7182,88 @@ .locals init (int[] V_0, IL_0066: add IL_0067: newarr "int" IL_006c: stloc.s V_4 - IL_006e: ldloc.0 - IL_006f: stloc.s V_5 - IL_0071: ldc.i4.0 - IL_0072: stloc.s V_6 - IL_0074: br.s IL_008d - IL_0076: ldloc.s V_5 - IL_0078: ldloc.s V_6 - IL_007a: ldelem.i4 - IL_007b: stloc.s V_7 - IL_007d: ldloc.s V_4 - IL_007f: ldloc.3 - IL_0080: ldloc.s V_7 - IL_0082: stelem.i4 + IL_006e: ldloca.s V_5 + IL_0070: ldloc.s V_4 + IL_0072: call "System.Span..ctor(int[])" + IL_0077: ldloca.s V_6 + IL_0079: ldloc.0 + IL_007a: call "System.Span..ctor(int[])" + IL_007f: ldloca.s V_6 + IL_0081: ldloca.s V_5 IL_0083: ldloc.3 - IL_0084: ldc.i4.1 - IL_0085: add - IL_0086: stloc.3 - IL_0087: ldloc.s V_6 - IL_0089: ldc.i4.1 - IL_008a: add - IL_008b: stloc.s V_6 - IL_008d: ldloc.s V_6 - IL_008f: ldloc.s V_5 - IL_0091: ldlen - IL_0092: conv.i4 - IL_0093: blt.s IL_0076 - IL_0095: ldloc.1 - IL_0096: stloc.s V_8 - IL_0098: ldloc.s V_8 - IL_009a: ldc.i4.0 - IL_009b: callvirt "int System.Array.GetUpperBound(int)" - IL_00a0: stloc.s V_6 + IL_0084: ldloca.s V_6 + IL_0086: call "int System.Span.Length.get" + IL_008b: call "System.Span System.Span.Slice(int, int)" + IL_0090: call "void System.Span.CopyTo(System.Span)" + IL_0095: ldloc.3 + IL_0096: ldloca.s V_6 + IL_0098: call "int System.Span.Length.get" + IL_009d: add + IL_009e: stloc.3 + IL_009f: ldloc.1 + IL_00a0: stloc.s V_8 IL_00a2: ldloc.s V_8 - IL_00a4: ldc.i4.1 + IL_00a4: ldc.i4.0 IL_00a5: callvirt "int System.Array.GetUpperBound(int)" - IL_00aa: stloc.s V_7 + IL_00aa: stloc.s V_9 IL_00ac: ldloc.s V_8 - IL_00ae: ldc.i4.0 - IL_00af: callvirt "int System.Array.GetLowerBound(int)" - IL_00b4: stloc.s V_9 - IL_00b6: br.s IL_00ed - IL_00b8: ldloc.s V_8 - IL_00ba: ldc.i4.1 - IL_00bb: callvirt "int System.Array.GetLowerBound(int)" - IL_00c0: stloc.s V_10 - IL_00c2: br.s IL_00e1 - IL_00c4: ldloc.s V_8 - IL_00c6: ldloc.s V_9 - IL_00c8: ldloc.s V_10 - IL_00ca: call "int[*,*].Get" - IL_00cf: stloc.s V_11 - IL_00d1: ldloc.s V_4 - IL_00d3: ldloc.3 - IL_00d4: ldloc.s V_11 - IL_00d6: stelem.i4 - IL_00d7: ldloc.3 - IL_00d8: ldc.i4.1 - IL_00d9: add - IL_00da: stloc.3 - IL_00db: ldloc.s V_10 - IL_00dd: ldc.i4.1 - IL_00de: add - IL_00df: stloc.s V_10 - IL_00e1: ldloc.s V_10 - IL_00e3: ldloc.s V_7 - IL_00e5: ble.s IL_00c4 - IL_00e7: ldloc.s V_9 - IL_00e9: ldc.i4.1 - IL_00ea: add - IL_00eb: stloc.s V_9 - IL_00ed: ldloc.s V_9 - IL_00ef: ldloc.s V_6 - IL_00f1: ble.s IL_00b8 - IL_00f3: ldloc.2 - IL_00f4: callvirt "System.Collections.Generic.List.Enumerator System.Collections.Generic.List.GetEnumerator()" - IL_00f9: stloc.s V_12 - .try - { - IL_00fb: br.s IL_0110 - IL_00fd: ldloca.s V_12 - IL_00ff: call "int System.Collections.Generic.List.Enumerator.Current.get" - IL_0104: stloc.s V_7 - IL_0106: ldloc.s V_4 - IL_0108: ldloc.3 - IL_0109: ldloc.s V_7 - IL_010b: stelem.i4 - IL_010c: ldloc.3 - IL_010d: ldc.i4.1 - IL_010e: add - IL_010f: stloc.3 - IL_0110: ldloca.s V_12 - IL_0112: call "bool System.Collections.Generic.List.Enumerator.MoveNext()" - IL_0117: brtrue.s IL_00fd - IL_0119: leave.s IL_0129 - } - finally - { - IL_011b: ldloca.s V_12 - IL_011d: constrained. "System.Collections.Generic.List.Enumerator" - IL_0123: callvirt "void System.IDisposable.Dispose()" - IL_0128: endfinally - } - IL_0129: ldloc.s V_4 - IL_012b: ret + IL_00ae: ldc.i4.1 + IL_00af: callvirt "int System.Array.GetUpperBound(int)" + IL_00b4: stloc.s V_10 + IL_00b6: ldloc.s V_8 + IL_00b8: ldc.i4.0 + IL_00b9: callvirt "int System.Array.GetLowerBound(int)" + IL_00be: stloc.s V_11 + IL_00c0: br.s IL_00f7 + IL_00c2: ldloc.s V_8 + IL_00c4: ldc.i4.1 + IL_00c5: callvirt "int System.Array.GetLowerBound(int)" + IL_00ca: stloc.s V_12 + IL_00cc: br.s IL_00eb + IL_00ce: ldloc.s V_8 + IL_00d0: ldloc.s V_11 + IL_00d2: ldloc.s V_12 + IL_00d4: call "int[*,*].Get" + IL_00d9: stloc.s V_13 + IL_00db: ldloc.s V_4 + IL_00dd: ldloc.3 + IL_00de: ldloc.s V_13 + IL_00e0: stelem.i4 + IL_00e1: ldloc.3 + IL_00e2: ldc.i4.1 + IL_00e3: add + IL_00e4: stloc.3 + IL_00e5: ldloc.s V_12 + IL_00e7: ldc.i4.1 + IL_00e8: add + IL_00e9: stloc.s V_12 + IL_00eb: ldloc.s V_12 + IL_00ed: ldloc.s V_10 + IL_00ef: ble.s IL_00ce + IL_00f1: ldloc.s V_11 + IL_00f3: ldc.i4.1 + IL_00f4: add + IL_00f5: stloc.s V_11 + IL_00f7: ldloc.s V_11 + IL_00f9: ldloc.s V_9 + IL_00fb: ble.s IL_00c2 + IL_00fd: ldloc.2 + IL_00fe: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_0103: stloc.s V_7 + IL_0105: ldloca.s V_7 + IL_0107: ldloca.s V_5 + IL_0109: ldloc.3 + IL_010a: ldloca.s V_7 + IL_010c: call "int System.Span.Length.get" + IL_0111: call "System.Span System.Span.Slice(int, int)" + IL_0116: call "void System.Span.CopyTo(System.Span)" + IL_011b: ldloc.3 + IL_011c: ldloca.s V_7 + IL_011e: call "int System.Span.Length.get" + IL_0123: add + IL_0124: stloc.3 + IL_0125: ldloc.s V_4 + IL_0127: ret } """); } @@ -15583,99 +15442,63 @@ static void Main() { verifier.VerifyIL("Program.F(T[])", """ { - // Code size 80 (0x50) - .maxstack 2 + // Code size 66 (0x42) + .maxstack 5 .locals init (T[] V_0, - System.Collections.Generic.List V_1, - System.Span V_2, - int V_3, - T[] V_4, - int V_5, - T V_6) + System.Span V_1, + int V_2, + System.Span V_3) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: newobj "System.Collections.Generic.List..ctor()" - IL_0007: stloc.1 - IL_0008: ldloc.1 - IL_0009: ldloc.0 - IL_000a: ldlen - IL_000b: conv.i4 - IL_000c: call "void System.Runtime.InteropServices.CollectionsMarshal.SetCount(System.Collections.Generic.List, int)" - IL_0011: ldloc.1 - IL_0012: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" - IL_0017: stloc.2 - IL_0018: ldc.i4.0 - IL_0019: stloc.3 - IL_001a: ldloc.0 - IL_001b: stloc.s V_4 - IL_001d: ldc.i4.0 - IL_001e: stloc.s V_5 - IL_0020: br.s IL_0046 - IL_0022: ldloc.s V_4 - IL_0024: ldloc.s V_5 - IL_0026: ldelem "T" - IL_002b: stloc.s V_6 - IL_002d: ldloca.s V_2 - IL_002f: ldloc.3 - IL_0030: call "ref T System.Span.this[int].get" - IL_0035: ldloc.s V_6 - IL_0037: stobj "T" - IL_003c: ldloc.3 - IL_003d: ldc.i4.1 - IL_003e: add - IL_003f: stloc.3 - IL_0040: ldloc.s V_5 - IL_0042: ldc.i4.1 - IL_0043: add - IL_0044: stloc.s V_5 - IL_0046: ldloc.s V_5 - IL_0048: ldloc.s V_4 - IL_004a: ldlen - IL_004b: conv.i4 - IL_004c: blt.s IL_0022 - IL_004e: ldloc.1 - IL_004f: ret + IL_0007: dup + IL_0008: ldloc.0 + IL_0009: ldlen + IL_000a: conv.i4 + IL_000b: call "void System.Runtime.InteropServices.CollectionsMarshal.SetCount(System.Collections.Generic.List, int)" + IL_0010: dup + IL_0011: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_0016: stloc.1 + IL_0017: ldc.i4.0 + IL_0018: stloc.2 + IL_0019: ldloca.s V_3 + IL_001b: ldloc.0 + IL_001c: call "System.Span..ctor(T[])" + IL_0021: ldloca.s V_3 + IL_0023: ldloca.s V_1 + IL_0025: ldloc.2 + IL_0026: ldloca.s V_3 + IL_0028: call "int System.Span.Length.get" + IL_002d: call "System.Span System.Span.Slice(int, int)" + IL_0032: call "void System.Span.CopyTo(System.Span)" + IL_0037: ldloc.2 + IL_0038: ldloca.s V_3 + IL_003a: call "int System.Span.Length.get" + IL_003f: add + IL_0040: stloc.2 + IL_0041: ret } """); } else { + // https://github.com/dotnet/roslyn/issues/71216 + // Consider preferring AddRange over CopyTo for collection-expressions of List type verifier.VerifyIL("Program.F(T[])", """ { - // Code size 42 (0x2a) - .maxstack 2 - .locals init (System.Collections.Generic.List V_0, - T[] V_1, - int V_2, - T V_3) + // Code size 18 (0x12) + .maxstack 3 + .locals init (T[] V_0) IL_0000: ldarg.0 - IL_0001: dup - IL_0002: ldlen - IL_0003: conv.i4 - IL_0004: newobj "System.Collections.Generic.List..ctor(int)" - IL_0009: stloc.0 - IL_000a: stloc.1 - IL_000b: ldc.i4.0 - IL_000c: stloc.2 - IL_000d: br.s IL_0022 - IL_000f: ldloc.1 - IL_0010: ldloc.2 - IL_0011: ldelem "T" - IL_0016: stloc.3 - IL_0017: ldloc.0 - IL_0018: ldloc.3 - IL_0019: callvirt "void System.Collections.Generic.List.Add(T)" - IL_001e: ldloc.2 - IL_001f: ldc.i4.1 - IL_0020: add - IL_0021: stloc.2 - IL_0022: ldloc.2 - IL_0023: ldloc.1 - IL_0024: ldlen - IL_0025: conv.i4 - IL_0026: blt.s IL_000f - IL_0028: ldloc.0 - IL_0029: ret + IL_0001: stloc.0 + IL_0002: ldloc.0 + IL_0003: ldlen + IL_0004: conv.i4 + IL_0005: newobj "System.Collections.Generic.List..ctor(int)" + IL_000a: dup + IL_000b: ldloc.0 + IL_000c: callvirt "void System.Collections.Generic.List.AddRange(System.Collections.Generic.IEnumerable)" + IL_0011: ret } """); } @@ -17522,53 +17345,21 @@ .locals init (<>y__InlineArray3 V_0) IL_0040: ret } """); + verifier.VerifyIL("R..ctor(int, T[])", """ { - // Code size 62 (0x3e) - .maxstack 3 - .locals init (int V_0, - T[] V_1, - T[] V_2, - int V_3, - T V_4) - IL_0000: ldarg.2 - IL_0001: ldc.i4.0 - IL_0002: stloc.0 - IL_0003: dup - IL_0004: ldlen - IL_0005: conv.i4 - IL_0006: newarr "T" - IL_000b: stloc.1 - IL_000c: stloc.2 - IL_000d: ldc.i4.0 - IL_000e: stloc.3 - IL_000f: br.s IL_002b - IL_0011: ldloc.2 - IL_0012: ldloc.3 - IL_0013: ldelem "T" - IL_0018: stloc.s V_4 - IL_001a: ldloc.1 - IL_001b: ldloc.0 - IL_001c: ldloc.s V_4 - IL_001e: stelem "T" - IL_0023: ldloc.0 - IL_0024: ldc.i4.1 - IL_0025: add - IL_0026: stloc.0 - IL_0027: ldloc.3 - IL_0028: ldc.i4.1 - IL_0029: add - IL_002a: stloc.3 - IL_002b: ldloc.3 - IL_002c: ldloc.2 - IL_002d: ldlen - IL_002e: conv.i4 - IL_002f: blt.s IL_0011 - IL_0031: ldarg.0 - IL_0032: ldloc.1 - IL_0033: newobj "System.Span..ctor(T[])" - IL_0038: call "R..ctor(scoped System.Span)" - IL_003d: ret + // Code size 26 (0x1a) + .maxstack 2 + .locals init (System.Span V_0) + IL_0000: ldarg.0 + IL_0001: ldarg.2 + IL_0002: newobj "System.Span..ctor(T[])" + IL_0007: stloc.0 + IL_0008: ldloca.s V_0 + IL_000a: call "T[] System.Span.ToArray()" + IL_000f: newobj "System.Span..ctor(T[])" + IL_0014: call "R..ctor(scoped System.Span)" + IL_0019: ret } """); } @@ -18398,57 +18189,24 @@ static void M(bool b, T[] a) expectedOutput: IncludeExpectedOutput("[1, null, 3], ")); verifier.VerifyIL("Program.M", """ { - // Code size 81 (0x51) - .maxstack 3 + // Code size 40 (0x28) + .maxstack 2 .locals init (System.Span V_0, //s - int V_1, - T[] V_2, - T[] V_3, - int V_4, - T V_5) + System.Span V_1) IL_0000: ldloca.s V_0 IL_0002: initobj "System.Span" IL_0008: ldarg.0 - IL_0009: brfalse.s IL_0049 - IL_000b: ldarg.1 - IL_000c: ldc.i4.0 - IL_000d: stloc.1 - IL_000e: dup - IL_000f: ldlen - IL_0010: conv.i4 - IL_0011: newarr "T" - IL_0016: stloc.2 - IL_0017: stloc.3 - IL_0018: ldc.i4.0 - IL_0019: stloc.s V_4 - IL_001b: br.s IL_003a - IL_001d: ldloc.3 - IL_001e: ldloc.s V_4 - IL_0020: ldelem "T" - IL_0025: stloc.s V_5 - IL_0027: ldloc.2 - IL_0028: ldloc.1 - IL_0029: ldloc.s V_5 - IL_002b: stelem "T" - IL_0030: ldloc.1 - IL_0031: ldc.i4.1 - IL_0032: add - IL_0033: stloc.1 - IL_0034: ldloc.s V_4 - IL_0036: ldc.i4.1 - IL_0037: add - IL_0038: stloc.s V_4 - IL_003a: ldloc.s V_4 - IL_003c: ldloc.3 - IL_003d: ldlen - IL_003e: conv.i4 - IL_003f: blt.s IL_001d - IL_0041: ldloca.s V_0 - IL_0043: ldloc.2 - IL_0044: call "System.Span..ctor(T[])" - IL_0049: ldloca.s V_0 - IL_004b: call "void CollectionExtensions.Report(in System.Span)" - IL_0050: ret + IL_0009: brfalse.s IL_0020 + IL_000b: ldloca.s V_0 + IL_000d: ldarg.1 + IL_000e: newobj "System.Span..ctor(T[])" + IL_0013: stloc.1 + IL_0014: ldloca.s V_1 + IL_0016: call "T[] System.Span.ToArray()" + IL_001b: call "System.Span..ctor(T[])" + IL_0020: ldloca.s V_0 + IL_0022: call "void CollectionExtensions.Report(in System.Span)" + IL_0027: ret } """); } @@ -22437,56 +22195,23 @@ static void Main() verifier.VerifyDiagnostics(); verifier.VerifyIL("Program.Main", """ { - // Code size 75 (0x4b) + // Code size 47 (0x2f) .maxstack 3 - .locals init (int V_0, - int[] V_1, - int[] V_2, - int V_3, - int V_4) + .locals init (System.Span V_0) 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: ldc.i4.0 - IL_0012: stloc.0 - IL_0013: dup - IL_0014: ldlen - IL_0015: conv.i4 - IL_0016: newarr "int" - IL_001b: stloc.1 - IL_001c: stloc.2 - IL_001d: ldc.i4.0 - IL_001e: stloc.3 - IL_001f: br.s IL_0033 - IL_0021: ldloc.2 - IL_0022: ldloc.3 - IL_0023: ldelem.i4 - IL_0024: stloc.s V_4 - IL_0026: ldloc.1 - IL_0027: ldloc.0 - IL_0028: ldloc.s V_4 - IL_002a: stelem.i4 - IL_002b: ldloc.0 - IL_002c: ldc.i4.1 - IL_002d: add - IL_002e: stloc.0 - IL_002f: ldloc.3 - IL_0030: ldc.i4.1 - IL_0031: add - IL_0032: stloc.3 - IL_0033: ldloc.3 - IL_0034: ldloc.2 - IL_0035: ldlen - IL_0036: conv.i4 - IL_0037: blt.s IL_0021 - IL_0039: ldloc.1 - IL_003a: call "System.Collections.Immutable.ImmutableArray System.Runtime.InteropServices.ImmutableCollectionsMarshal.AsImmutableArray(int[])" - IL_003f: box "System.Collections.Immutable.ImmutableArray" - IL_0044: ldc.i4.0 - IL_0045: call "void CollectionExtensions.Report(object, bool)" - IL_004a: ret + IL_0011: newobj "System.Span..ctor(int[])" + IL_0016: stloc.0 + IL_0017: ldloca.s V_0 + IL_0019: call "int[] System.Span.ToArray()" + IL_001e: call "System.Collections.Immutable.ImmutableArray System.Runtime.InteropServices.ImmutableCollectionsMarshal.AsImmutableArray(int[])" + IL_0023: box "System.Collections.Immutable.ImmutableArray" + IL_0028: ldc.i4.0 + IL_0029: call "void CollectionExtensions.Report(object, bool)" + IL_002e: ret } """); } @@ -22649,6 +22374,52 @@ public void Add(T t) { } Diagnostic(ErrorCode.ERR_CollectionExpressionImmutableArray, "[1, 2, 3]").WithArguments("System.Collections.Immutable.ImmutableArray").WithLocation(7, 35)); } + [Fact] + public void ImmutableArray_08() + { + string sourceA = """ + using System.Collections.Immutable; + + class Program + { + static void Main() + { + ImmutableArray arr = [1, 2, 3]; + ImmutableArray arr1 = [..arr]; + arr1.Report(); + } + } + """; + + var verifier = CompileAndVerify(new[] { sourceA, s_collectionExtensions }, targetFramework: TargetFramework.Net80, expectedOutput: IncludeExpectedOutput("[1, 2, 3],"), verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Program.Main", """ + { + // Code size 55 (0x37) + .maxstack 3 + .locals init (System.Collections.Immutable.ImmutableArray V_0, //arr + System.ReadOnlySpan V_1) + 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: call "System.Collections.Immutable.ImmutableArray System.Runtime.InteropServices.ImmutableCollectionsMarshal.AsImmutableArray(int[])" + IL_0016: stloc.0 + IL_0017: ldloca.s V_0 + IL_0019: call "System.ReadOnlySpan System.Collections.Immutable.ImmutableArray.AsSpan()" + IL_001e: stloc.1 + IL_001f: ldloca.s V_1 + IL_0021: call "int[] System.ReadOnlySpan.ToArray()" + IL_0026: call "System.Collections.Immutable.ImmutableArray System.Runtime.InteropServices.ImmutableCollectionsMarshal.AsImmutableArray(int[])" + IL_002b: box "System.Collections.Immutable.ImmutableArray" + IL_0030: ldc.i4.0 + IL_0031: call "void CollectionExtensions.Report(object, bool)" + IL_0036: ret + } + """); + } + [Fact] public void SpanImplicitAllocationWarning_01() { @@ -23821,5 +23592,845 @@ public void Add(MyCollection c) Assert.NotNull(model.GetOperation(collectionExpression)); } + + [Fact] + public void SingleSpread_ListToArray() + { + var source = """ + using System.Collections.Generic; + + class C + { + static void Main() + { + List li = [1, 2, 3]; + li.Report(); + int[] arr = [..li]; + arr.Report(); + } + } + """; + + var verifier = CompileAndVerify(new[] { source, s_collectionExtensions }, expectedOutput: "[1, 2, 3], [1, 2, 3],"); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C.Main", """ + { + // Code size 46 (0x2e) + .maxstack 3 + IL_0000: ldc.i4.3 + IL_0001: newobj "System.Collections.Generic.List..ctor(int)" + IL_0006: dup + IL_0007: ldc.i4.1 + IL_0008: callvirt "void System.Collections.Generic.List.Add(int)" + IL_000d: dup + IL_000e: ldc.i4.2 + IL_000f: callvirt "void System.Collections.Generic.List.Add(int)" + IL_0014: dup + IL_0015: ldc.i4.3 + IL_0016: callvirt "void System.Collections.Generic.List.Add(int)" + IL_001b: dup + IL_001c: ldc.i4.0 + IL_001d: call "void CollectionExtensions.Report(object, bool)" + IL_0022: callvirt "int[] System.Collections.Generic.List.ToArray()" + IL_0027: ldc.i4.0 + IL_0028: call "void CollectionExtensions.Report(object, bool)" + IL_002d: ret + } + """); + } + + [Fact] + public void SingleSpread_SpanToArray() + { + var source = """ + using System; + + class C + { + static void Main() + { + Span li = [1, 2, 3]; + li.Report(); + int[] arr = [..li]; + arr.Report(); + } + } + """; + + var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: "[1, 2, 3], [1, 2, 3],", verify: Verification.Skipped, targetFramework: TargetFramework.Net80); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C.Main", """ + { + // Code size 68 (0x44) + .maxstack 2 + .locals init (System.Span V_0, //li + <>y__InlineArray3 V_1) + IL_0000: ldloca.s V_1 + IL_0002: initobj "<>y__InlineArray3" + IL_0008: ldloca.s V_1 + IL_000a: ldc.i4.0 + IL_000b: call "ref int .InlineArrayElementRef<<>y__InlineArray3, int>(ref <>y__InlineArray3, int)" + IL_0010: ldc.i4.1 + IL_0011: stind.i4 + IL_0012: ldloca.s V_1 + IL_0014: ldc.i4.1 + IL_0015: call "ref int .InlineArrayElementRef<<>y__InlineArray3, int>(ref <>y__InlineArray3, int)" + IL_001a: ldc.i4.2 + IL_001b: stind.i4 + IL_001c: ldloca.s V_1 + IL_001e: ldc.i4.2 + IL_001f: call "ref int .InlineArrayElementRef<<>y__InlineArray3, int>(ref <>y__InlineArray3, int)" + IL_0024: ldc.i4.3 + IL_0025: stind.i4 + IL_0026: ldloca.s V_1 + IL_0028: ldc.i4.3 + IL_0029: call "System.Span .InlineArrayAsSpan<<>y__InlineArray3, int>(ref <>y__InlineArray3, int)" + IL_002e: stloc.0 + IL_002f: ldloca.s V_0 + IL_0031: call "void CollectionExtensions.Report(in System.Span)" + IL_0036: ldloca.s V_0 + IL_0038: call "int[] System.Span.ToArray()" + IL_003d: ldc.i4.0 + IL_003e: call "void CollectionExtensions.Report(object, bool)" + IL_0043: ret + } + """); + } + + [Fact] + public void SingleSpread_ReadOnlySpanToArray() + { + var source = """ + using System; + + class C + { + static void Main() + { + ReadOnlySpan li = [1, 2, 3]; + li.Report(); + int[] arr = [..li]; + arr.Report(); + } + } + """; + + var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: "[1, 2, 3], [1, 2, 3],", verify: Verification.Skipped, targetFramework: TargetFramework.Net80); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C.Main", """ + { + // Code size 32 (0x20) + .maxstack 2 + .locals init (System.ReadOnlySpan V_0) //li + IL_0000: ldtoken ".__StaticArrayInitTypeSize=12_Align=4 .4636993D3E1DA4E9D6B8F87B79E8F7C6D018580D52661950EABC3845C5897A4D4" + IL_0005: call "System.ReadOnlySpan System.Runtime.CompilerServices.RuntimeHelpers.CreateSpan(System.RuntimeFieldHandle)" + IL_000a: stloc.0 + IL_000b: ldloca.s V_0 + IL_000d: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" + IL_0012: ldloca.s V_0 + IL_0014: call "int[] System.ReadOnlySpan.ToArray()" + IL_0019: ldc.i4.0 + IL_001a: call "void CollectionExtensions.Report(object, bool)" + IL_001f: ret + } + """); + } + + [Fact] + public void TwoSpreads_ReadOnlySpanToArray() + { + var source = """ + using System; + + class C + { + static void Main() + { + ReadOnlySpan li1 = [1, 2, 3]; + ReadOnlySpan li2 = [4, 5, 6]; + li1.Report(); + li2.Report(); + int[] arr = [..li1, ..li2]; + arr.Report(); + } + } + """; + + var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: IncludeExpectedOutput("[1, 2, 3], [4, 5, 6], [1, 2, 3, 4, 5, 6],"), verify: Verification.Skipped, targetFramework: TargetFramework.Net80); + //System.IO.File.WriteAllBytes(@"C:\Users\rikki\Desktop\assembly.dll", verifier.EmittedAssemblyData.ToArray()); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C.Main", """ + { + // Code size 153 (0x99) + .maxstack 4 + .locals init (System.ReadOnlySpan V_0, //li1 + System.ReadOnlySpan V_1, //li2 + System.ReadOnlySpan V_2, + System.ReadOnlySpan V_3, + int V_4, + int[] V_5, + System.Span V_6) + IL_0000: ldtoken ".__StaticArrayInitTypeSize=12_Align=4 .4636993D3E1DA4E9D6B8F87B79E8F7C6D018580D52661950EABC3845C5897A4D4" + IL_0005: call "System.ReadOnlySpan System.Runtime.CompilerServices.RuntimeHelpers.CreateSpan(System.RuntimeFieldHandle)" + IL_000a: stloc.0 + IL_000b: ldtoken ".__StaticArrayInitTypeSize=12_Align=4 .8CA6EE1043DEFCFD05AA29DEE581CBC519E783E414A687D7C26AC6070D3F6DEE4" + IL_0010: call "System.ReadOnlySpan System.Runtime.CompilerServices.RuntimeHelpers.CreateSpan(System.RuntimeFieldHandle)" + IL_0015: stloc.1 + IL_0016: ldloca.s V_0 + IL_0018: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" + IL_001d: ldloca.s V_1 + IL_001f: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" + IL_0024: ldloc.0 + IL_0025: stloc.2 + IL_0026: ldloc.1 + IL_0027: stloc.3 + IL_0028: ldc.i4.0 + IL_0029: stloc.s V_4 + IL_002b: ldloca.s V_2 + IL_002d: call "int System.ReadOnlySpan.Length.get" + IL_0032: ldloca.s V_3 + IL_0034: call "int System.ReadOnlySpan.Length.get" + IL_0039: add + IL_003a: newarr "int" + IL_003f: stloc.s V_5 + IL_0041: ldloca.s V_6 + IL_0043: ldloc.s V_5 + IL_0045: call "System.Span..ctor(int[])" + IL_004a: ldloca.s V_2 + IL_004c: ldloca.s V_6 + IL_004e: ldloc.s V_4 + IL_0050: ldloca.s V_2 + IL_0052: call "int System.ReadOnlySpan.Length.get" + IL_0057: call "System.Span System.Span.Slice(int, int)" + IL_005c: call "void System.ReadOnlySpan.CopyTo(System.Span)" + IL_0061: ldloc.s V_4 + IL_0063: ldloca.s V_2 + IL_0065: call "int System.ReadOnlySpan.Length.get" + IL_006a: add + IL_006b: stloc.s V_4 + IL_006d: ldloca.s V_3 + IL_006f: ldloca.s V_6 + IL_0071: ldloc.s V_4 + IL_0073: ldloca.s V_3 + IL_0075: call "int System.ReadOnlySpan.Length.get" + IL_007a: call "System.Span System.Span.Slice(int, int)" + IL_007f: call "void System.ReadOnlySpan.CopyTo(System.Span)" + IL_0084: ldloc.s V_4 + IL_0086: ldloca.s V_3 + IL_0088: call "int System.ReadOnlySpan.Length.get" + IL_008d: add + IL_008e: stloc.s V_4 + IL_0090: ldloc.s V_5 + IL_0092: ldc.i4.0 + IL_0093: call "void CollectionExtensions.Report(object, bool)" + IL_0098: ret + } + """); + } + + [Fact] + public void TwoSpreads_Covariance() + { + var source = """ + using System; + + class C + { + static void Main() + { + ReadOnlySpan li1 = [new D()]; + ReadOnlySpan li2 = [new D()]; + C[] arr = [..li1, ..li2]; + arr.Report(); + } + } + + class D : C { } + """; + + var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: IncludeExpectedOutput("[D, D],"), verify: Verification.Skipped, targetFramework: TargetFramework.Net80); + //System.IO.File.WriteAllBytes(@"C:\Users\rikki\Desktop\assembly.dll", verifier.EmittedAssemblyData.ToArray()); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C.Main", """ + { + // Code size 185 (0xb9) + .maxstack 3 + .locals init (System.ReadOnlySpan V_0, //li1 + <>y__InlineArray1 V_1, + <>y__InlineArray1 V_2, + System.ReadOnlySpan V_3, + System.ReadOnlySpan V_4, + int V_5, + C[] V_6, + System.ReadOnlySpan.Enumerator V_7, + D V_8) + IL_0000: ldloca.s V_1 + IL_0002: initobj "<>y__InlineArray1" + IL_0008: ldloca.s V_1 + IL_000a: ldc.i4.0 + IL_000b: call "ref D .InlineArrayElementRef<<>y__InlineArray1, D>(ref <>y__InlineArray1, int)" + IL_0010: newobj "D..ctor()" + IL_0015: stind.ref + IL_0016: ldloca.s V_1 + IL_0018: ldc.i4.1 + IL_0019: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, D>(in <>y__InlineArray1, int)" + IL_001e: stloc.0 + IL_001f: ldloca.s V_2 + IL_0021: initobj "<>y__InlineArray1" + IL_0027: ldloca.s V_2 + IL_0029: ldc.i4.0 + IL_002a: call "ref D .InlineArrayElementRef<<>y__InlineArray1, D>(ref <>y__InlineArray1, int)" + IL_002f: newobj "D..ctor()" + IL_0034: stind.ref + IL_0035: ldloca.s V_2 + IL_0037: ldc.i4.1 + IL_0038: call "System.ReadOnlySpan .InlineArrayAsReadOnlySpan<<>y__InlineArray1, D>(in <>y__InlineArray1, int)" + IL_003d: ldloc.0 + IL_003e: stloc.3 + IL_003f: stloc.s V_4 + IL_0041: ldc.i4.0 + IL_0042: stloc.s V_5 + IL_0044: ldloca.s V_3 + IL_0046: call "int System.ReadOnlySpan.Length.get" + IL_004b: ldloca.s V_4 + IL_004d: call "int System.ReadOnlySpan.Length.get" + IL_0052: add + IL_0053: newarr "C" + IL_0058: stloc.s V_6 + IL_005a: ldloca.s V_3 + IL_005c: call "System.ReadOnlySpan.Enumerator System.ReadOnlySpan.GetEnumerator()" + IL_0061: stloc.s V_7 + IL_0063: br.s IL_007c + IL_0065: ldloca.s V_7 + IL_0067: call "ref readonly D System.ReadOnlySpan.Enumerator.Current.get" + IL_006c: ldind.ref + IL_006d: stloc.s V_8 + IL_006f: ldloc.s V_6 + IL_0071: ldloc.s V_5 + IL_0073: ldloc.s V_8 + IL_0075: stelem.ref + IL_0076: ldloc.s V_5 + IL_0078: ldc.i4.1 + IL_0079: add + IL_007a: stloc.s V_5 + IL_007c: ldloca.s V_7 + IL_007e: call "bool System.ReadOnlySpan.Enumerator.MoveNext()" + IL_0083: brtrue.s IL_0065 + IL_0085: ldloca.s V_4 + IL_0087: call "System.ReadOnlySpan.Enumerator System.ReadOnlySpan.GetEnumerator()" + IL_008c: stloc.s V_7 + IL_008e: br.s IL_00a7 + IL_0090: ldloca.s V_7 + IL_0092: call "ref readonly D System.ReadOnlySpan.Enumerator.Current.get" + IL_0097: ldind.ref + IL_0098: stloc.s V_8 + IL_009a: ldloc.s V_6 + IL_009c: ldloc.s V_5 + IL_009e: ldloc.s V_8 + IL_00a0: stelem.ref + IL_00a1: ldloc.s V_5 + IL_00a3: ldc.i4.1 + IL_00a4: add + IL_00a5: stloc.s V_5 + IL_00a7: ldloca.s V_7 + IL_00a9: call "bool System.ReadOnlySpan.Enumerator.MoveNext()" + IL_00ae: brtrue.s IL_0090 + IL_00b0: ldloc.s V_6 + IL_00b2: ldc.i4.0 + IL_00b3: call "void CollectionExtensions.Report(object, bool)" + IL_00b8: ret + } + """); + } + + [Fact] + public void SpanToList_Spreads() + { + var source = """ + using System; + using System.Collections.Generic; + + class C + { + static void Main() + { + M([1, 2, 3], [4, 5, 6]); + } + + static void M(ReadOnlySpan e1, ReadOnlySpan e2) + { + List result = [..e1, ..e2]; + result.Report(); + } + } + """; + + var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: IncludeExpectedOutput("[1, 2, 3, 4, 5, 6],"), targetFramework: TargetFramework.Net80); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C.M", """ + { + // Code size 110 (0x6e) + .maxstack 5 + .locals init (System.ReadOnlySpan V_0, + System.ReadOnlySpan V_1, + System.Span V_2, + int V_3) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldarg.1 + IL_0003: stloc.1 + IL_0004: newobj "System.Collections.Generic.List..ctor()" + IL_0009: dup + IL_000a: ldloca.s V_0 + IL_000c: call "int System.ReadOnlySpan.Length.get" + IL_0011: ldloca.s V_1 + IL_0013: call "int System.ReadOnlySpan.Length.get" + IL_0018: add + IL_0019: call "void System.Runtime.InteropServices.CollectionsMarshal.SetCount(System.Collections.Generic.List, int)" + IL_001e: dup + IL_001f: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_0024: stloc.2 + IL_0025: ldc.i4.0 + IL_0026: stloc.3 + IL_0027: ldloca.s V_0 + IL_0029: ldloca.s V_2 + IL_002b: ldloc.3 + IL_002c: ldloca.s V_0 + IL_002e: call "int System.ReadOnlySpan.Length.get" + IL_0033: call "System.Span System.Span.Slice(int, int)" + IL_0038: call "void System.ReadOnlySpan.CopyTo(System.Span)" + IL_003d: ldloc.3 + IL_003e: ldloca.s V_0 + IL_0040: call "int System.ReadOnlySpan.Length.get" + IL_0045: add + IL_0046: stloc.3 + IL_0047: ldloca.s V_1 + IL_0049: ldloca.s V_2 + IL_004b: ldloc.3 + IL_004c: ldloca.s V_1 + IL_004e: call "int System.ReadOnlySpan.Length.get" + IL_0053: call "System.Span System.Span.Slice(int, int)" + IL_0058: call "void System.ReadOnlySpan.CopyTo(System.Span)" + IL_005d: ldloc.3 + IL_005e: ldloca.s V_1 + IL_0060: call "int System.ReadOnlySpan.Length.get" + IL_0065: add + IL_0066: stloc.3 + IL_0067: ldc.i4.0 + IL_0068: call "void CollectionExtensions.Report(object, bool)" + IL_006d: ret + } + """); + } + + [Fact] + public void ReadOnlySpanToList_Spreads() + { + var source = """ + using System; + using System.Collections.Generic; + + class C + { + static void Main() + { + M([1, 2, 3], [4, 5, 6]); + } + + static void M(ReadOnlySpan e1, ReadOnlySpan e2) + { + List result = [..e1, ..e2]; + result.Report(); + } + } + """; + + var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: IncludeExpectedOutput("[1, 2, 3, 4, 5, 6],"), targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); + System.IO.File.WriteAllBytes(@"C:\Users\rikki\Desktop\assembly.dll", verifier.EmittedAssemblyData.ToArray()); + verifier.Diagnostics.Where(d => d.Severity > DiagnosticSeverity.Hidden).Verify(); + verifier.VerifyIL("C.M", """ + { + // Code size 110 (0x6e) + .maxstack 5 + .locals init (System.ReadOnlySpan V_0, + System.ReadOnlySpan V_1, + System.Span V_2, + int V_3) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldarg.1 + IL_0003: stloc.1 + IL_0004: newobj "System.Collections.Generic.List..ctor()" + IL_0009: dup + IL_000a: ldloca.s V_0 + IL_000c: call "int System.ReadOnlySpan.Length.get" + IL_0011: ldloca.s V_1 + IL_0013: call "int System.ReadOnlySpan.Length.get" + IL_0018: add + IL_0019: call "void System.Runtime.InteropServices.CollectionsMarshal.SetCount(System.Collections.Generic.List, int)" + IL_001e: dup + IL_001f: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_0024: stloc.2 + IL_0025: ldc.i4.0 + IL_0026: stloc.3 + IL_0027: ldloca.s V_0 + IL_0029: ldloca.s V_2 + IL_002b: ldloc.3 + IL_002c: ldloca.s V_0 + IL_002e: call "int System.ReadOnlySpan.Length.get" + IL_0033: call "System.Span System.Span.Slice(int, int)" + IL_0038: call "void System.ReadOnlySpan.CopyTo(System.Span)" + IL_003d: ldloc.3 + IL_003e: ldloca.s V_0 + IL_0040: call "int System.ReadOnlySpan.Length.get" + IL_0045: add + IL_0046: stloc.3 + IL_0047: ldloca.s V_1 + IL_0049: ldloca.s V_2 + IL_004b: ldloc.3 + IL_004c: ldloca.s V_1 + IL_004e: call "int System.ReadOnlySpan.Length.get" + IL_0053: call "System.Span System.Span.Slice(int, int)" + IL_0058: call "void System.ReadOnlySpan.CopyTo(System.Span)" + IL_005d: ldloc.3 + IL_005e: ldloca.s V_1 + IL_0060: call "int System.ReadOnlySpan.Length.get" + IL_0065: add + IL_0066: stloc.3 + IL_0067: ldc.i4.0 + IL_0068: call "void CollectionExtensions.Report(object, bool)" + IL_006d: ret + } + """); + } + + [Fact] + public void ArrayToList_Spreads() + { + var source = """ + using System; + using System.Collections.Generic; + + class C + { + static void Main() + { + M([1, 2, 3], [4, 5, 6]); + } + + static void M(int[] e1, int[] e2) + { + List result = [..e1, ..e2]; + result.Report(); + } + } + """; + + var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: IncludeExpectedOutput("[1, 2, 3, 4, 5, 6],"), targetFramework: TargetFramework.Net80); + System.IO.File.WriteAllBytes(@"C:\Users\rikki\Desktop\assembly.dll", verifier.EmittedAssemblyData.ToArray()); + verifier.Diagnostics.Where(d => d.Severity > DiagnosticSeverity.Hidden).Verify(); + verifier.VerifyIL("C.M", """ + { + // Code size 118 (0x76) + .maxstack 5 + .locals init (int[] V_0, + int[] V_1, + System.Span V_2, + int V_3, + System.Span V_4, + System.Span V_5) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldarg.1 + IL_0003: stloc.1 + IL_0004: newobj "System.Collections.Generic.List..ctor()" + IL_0009: dup + IL_000a: ldloc.0 + IL_000b: ldlen + IL_000c: conv.i4 + IL_000d: ldloc.1 + IL_000e: ldlen + IL_000f: conv.i4 + IL_0010: add + IL_0011: call "void System.Runtime.InteropServices.CollectionsMarshal.SetCount(System.Collections.Generic.List, int)" + IL_0016: dup + IL_0017: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_001c: stloc.2 + IL_001d: ldc.i4.0 + IL_001e: stloc.3 + IL_001f: ldloca.s V_4 + IL_0021: ldloc.0 + IL_0022: call "System.Span..ctor(int[])" + IL_0027: ldloca.s V_4 + IL_0029: ldloca.s V_2 + IL_002b: ldloc.3 + IL_002c: ldloca.s V_4 + IL_002e: call "int System.Span.Length.get" + IL_0033: call "System.Span System.Span.Slice(int, int)" + IL_0038: call "void System.Span.CopyTo(System.Span)" + IL_003d: ldloc.3 + IL_003e: ldloca.s V_4 + IL_0040: call "int System.Span.Length.get" + IL_0045: add + IL_0046: stloc.3 + IL_0047: ldloca.s V_5 + IL_0049: ldloc.1 + IL_004a: call "System.Span..ctor(int[])" + IL_004f: ldloca.s V_5 + IL_0051: ldloca.s V_2 + IL_0053: ldloc.3 + IL_0054: ldloca.s V_5 + IL_0056: call "int System.Span.Length.get" + IL_005b: call "System.Span System.Span.Slice(int, int)" + IL_0060: call "void System.Span.CopyTo(System.Span)" + IL_0065: ldloc.3 + IL_0066: ldloca.s V_5 + IL_0068: call "int System.Span.Length.get" + IL_006d: add + IL_006e: stloc.3 + IL_006f: ldc.i4.0 + IL_0070: call "void CollectionExtensions.Report(object, bool)" + IL_0075: ret + } + """); + } + + [Fact] + public void ListToList_Spreads() + { + var source = """ + using System; + using System.Collections.Generic; + + class C + { + static void Main() + { + M([1, 2, 3], [4, 5, 6]); + } + + static void M(List e1, List e2) + { + List result = [..e1, ..e2]; + result.Report(); + } + } + """; + + var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: IncludeExpectedOutput("[1, 2, 3, 4, 5, 6],"), targetFramework: TargetFramework.Net80); + System.IO.File.WriteAllBytes(@"C:\Users\rikki\Desktop\assembly.dll", verifier.EmittedAssemblyData.ToArray()); + verifier.Diagnostics.Where(d => d.Severity > DiagnosticSeverity.Hidden).Verify(); + verifier.VerifyIL("C.M", """ + { + // Code size 124 (0x7c) + .maxstack 5 + .locals init (System.Collections.Generic.List V_0, + System.Collections.Generic.List V_1, + System.Span V_2, + int V_3, + System.Span V_4, + System.Span V_5) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldarg.1 + IL_0003: stloc.1 + IL_0004: newobj "System.Collections.Generic.List..ctor()" + IL_0009: dup + IL_000a: ldloc.0 + IL_000b: callvirt "int System.Collections.Generic.List.Count.get" + IL_0010: ldloc.1 + IL_0011: callvirt "int System.Collections.Generic.List.Count.get" + IL_0016: add + IL_0017: call "void System.Runtime.InteropServices.CollectionsMarshal.SetCount(System.Collections.Generic.List, int)" + IL_001c: dup + IL_001d: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_0022: stloc.2 + IL_0023: ldc.i4.0 + IL_0024: stloc.3 + IL_0025: ldloc.0 + IL_0026: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_002b: stloc.s V_4 + IL_002d: ldloca.s V_4 + IL_002f: ldloca.s V_2 + IL_0031: ldloc.3 + IL_0032: ldloca.s V_4 + IL_0034: call "int System.Span.Length.get" + IL_0039: call "System.Span System.Span.Slice(int, int)" + IL_003e: call "void System.Span.CopyTo(System.Span)" + IL_0043: ldloc.3 + IL_0044: ldloca.s V_4 + IL_0046: call "int System.Span.Length.get" + IL_004b: add + IL_004c: stloc.3 + IL_004d: ldloc.1 + IL_004e: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_0053: stloc.s V_5 + IL_0055: ldloca.s V_5 + IL_0057: ldloca.s V_2 + IL_0059: ldloc.3 + IL_005a: ldloca.s V_5 + IL_005c: call "int System.Span.Length.get" + IL_0061: call "System.Span System.Span.Slice(int, int)" + IL_0066: call "void System.Span.CopyTo(System.Span)" + IL_006b: ldloc.3 + IL_006c: ldloca.s V_5 + IL_006e: call "int System.Span.Length.get" + IL_0073: add + IL_0074: stloc.3 + IL_0075: ldc.i4.0 + IL_0076: call "void CollectionExtensions.Report(object, bool)" + IL_007b: ret + } + """); + } + + [Fact] + public void List_Spread_Mutation() + { + var source = """ + using System; + using System.Collections.Generic; + + class C + { + static void Main() + { + M([1, 2, 3]); + } + + static List Pop(List e1, out int i) + { + i = e1[e1.Count-1]; + e1.RemoveAt(e1.Count-1); + return e1; + } + + static void M(List e1) + { + List result = [..e1, ..Pop(e1, out var i), i]; + result.Report(); + } + } + """; + + var expectedOutput = IncludeExpectedOutput("[1, 2, 1, 2, 3],"); + var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: expectedOutput, targetFramework: TargetFramework.Net80); + verifier.Diagnostics.Where(d => d.Severity > DiagnosticSeverity.Hidden).Verify(); + verifier.VerifyIL("C.M", """ + { + // Code size 157 (0x9d) + .maxstack 5 + .locals init (int V_0, //i + System.Collections.Generic.List V_1, + System.Collections.Generic.List V_2, + System.Span V_3, + int V_4, + System.Span V_5, + System.Span V_6) + IL_0000: ldarg.0 + IL_0001: stloc.1 + IL_0002: ldarg.0 + IL_0003: ldloca.s V_0 + IL_0005: call "System.Collections.Generic.List C.Pop(System.Collections.Generic.List, out int)" + IL_000a: stloc.2 + IL_000b: newobj "System.Collections.Generic.List..ctor()" + IL_0010: dup + IL_0011: ldc.i4.1 + IL_0012: ldloc.1 + IL_0013: callvirt "int System.Collections.Generic.List.Count.get" + IL_0018: ldloc.2 + IL_0019: callvirt "int System.Collections.Generic.List.Count.get" + IL_001e: add + IL_001f: add + IL_0020: call "void System.Runtime.InteropServices.CollectionsMarshal.SetCount(System.Collections.Generic.List, int)" + IL_0025: dup + IL_0026: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_002b: stloc.3 + IL_002c: ldc.i4.0 + IL_002d: stloc.s V_4 + IL_002f: ldloc.1 + IL_0030: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_0035: stloc.s V_5 + IL_0037: ldloca.s V_5 + IL_0039: ldloca.s V_3 + IL_003b: ldloc.s V_4 + IL_003d: ldloca.s V_5 + IL_003f: call "int System.Span.Length.get" + IL_0044: call "System.Span System.Span.Slice(int, int)" + IL_0049: call "void System.Span.CopyTo(System.Span)" + IL_004e: ldloc.s V_4 + IL_0050: ldloca.s V_5 + IL_0052: call "int System.Span.Length.get" + IL_0057: add + IL_0058: stloc.s V_4 + IL_005a: ldloc.2 + IL_005b: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_0060: stloc.s V_6 + IL_0062: ldloca.s V_6 + IL_0064: ldloca.s V_3 + IL_0066: ldloc.s V_4 + IL_0068: ldloca.s V_6 + IL_006a: call "int System.Span.Length.get" + IL_006f: call "System.Span System.Span.Slice(int, int)" + IL_0074: call "void System.Span.CopyTo(System.Span)" + IL_0079: ldloc.s V_4 + IL_007b: ldloca.s V_6 + IL_007d: call "int System.Span.Length.get" + IL_0082: add + IL_0083: stloc.s V_4 + IL_0085: ldloca.s V_3 + IL_0087: ldloc.s V_4 + IL_0089: call "ref int System.Span.this[int].get" + IL_008e: ldloc.0 + IL_008f: stind.i4 + IL_0090: ldloc.s V_4 + IL_0092: ldc.i4.1 + IL_0093: add + IL_0094: stloc.s V_4 + IL_0096: ldc.i4.0 + IL_0097: call "void CollectionExtensions.Report(object, bool)" + IL_009c: ret + } + """); + + var comp = CreateCompilation(new[] { source, s_collectionExtensionsWithSpan }, targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); + comp.MakeMemberMissing(WellKnownMember.System_Runtime_InteropServices_CollectionsMarshal__AsSpan_T); + verifier = CompileAndVerify(comp, expectedOutput: expectedOutput); + verifier.VerifyIL("C.M", """ + { + // Code size 59 (0x3b) + .maxstack 3 + .locals init (int V_0, //i + System.Collections.Generic.List V_1, + System.Collections.Generic.List V_2) + IL_0000: ldarg.0 + IL_0001: stloc.1 + IL_0002: ldarg.0 + IL_0003: ldloca.s V_0 + IL_0005: call "System.Collections.Generic.List C.Pop(System.Collections.Generic.List, out int)" + IL_000a: stloc.2 + IL_000b: ldc.i4.1 + IL_000c: ldloc.1 + IL_000d: callvirt "int System.Collections.Generic.List.Count.get" + IL_0012: ldloc.2 + IL_0013: callvirt "int System.Collections.Generic.List.Count.get" + IL_0018: add + IL_0019: add + IL_001a: newobj "System.Collections.Generic.List..ctor(int)" + IL_001f: dup + IL_0020: ldloc.1 + IL_0021: callvirt "void System.Collections.Generic.List.AddRange(System.Collections.Generic.IEnumerable)" + IL_0026: dup + IL_0027: ldloc.2 + IL_0028: callvirt "void System.Collections.Generic.List.AddRange(System.Collections.Generic.IEnumerable)" + IL_002d: dup + IL_002e: ldloc.0 + IL_002f: callvirt "void System.Collections.Generic.List.Add(int)" + IL_0034: ldc.i4.0 + IL_0035: call "void CollectionExtensions.Report(object, bool)" + IL_003a: ret + } + """); + } } } diff --git a/src/Compilers/Core/Portable/WellKnownMember.cs b/src/Compilers/Core/Portable/WellKnownMember.cs index d9d7854893eb6..7de89f8f68e0f 100644 --- a/src/Compilers/Core/Portable/WellKnownMember.cs +++ b/src/Compilers/Core/Portable/WellKnownMember.cs @@ -632,6 +632,13 @@ internal enum WellKnownMember System_Runtime_InteropServices_CollectionsMarshal__SetCount_T, System_Runtime_InteropServices_ImmutableCollectionsMarshal__AsImmutableArray_T, + System_Span_T__ToArray, + System_ReadOnlySpan_T__ToArray, + System_Span_T__CopyTo_Span_T, + System_ReadOnlySpan_T__CopyTo_Span_T, + System_Collections_Immutable_ImmutableArray_T__AsSpan, + System_Collections_Generic_List_T__AddRange, + Count, // Remember to update the AllWellKnownTypeMembers tests when making changes here diff --git a/src/Compilers/Core/Portable/WellKnownMembers.cs b/src/Compilers/Core/Portable/WellKnownMembers.cs index d014305128563..e17e7fce86538 100644 --- a/src/Compilers/Core/Portable/WellKnownMembers.cs +++ b/src/Compilers/Core/Portable/WellKnownMembers.cs @@ -4385,6 +4385,63 @@ static WellKnownMembers() 1, (byte)SignatureTypeCode.GenericMethodParameter, 0, (byte)SignatureTypeCode.SZArray, (byte)SignatureTypeCode.GenericMethodParameter, 0, // Parameter Type + + // System_Span_T__ToArray + (byte)MemberFlags.Method, // Flags + (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Span_T - WellKnownType.ExtSentinel), // DeclaringTypeId + 0, // Arity + 0, // Method Signature + (byte)SignatureTypeCode.SZArray, (byte)SignatureTypeCode.GenericTypeParameter, 0, // Return Type + + // System_ReadOnlySpan_T__ToArray + (byte)MemberFlags.Method, // Flags + (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), // DeclaringTypeId + 0, // Arity + 0, // Method Signature + (byte)SignatureTypeCode.SZArray, (byte)SignatureTypeCode.GenericTypeParameter, 0, // Return Type + + // System_Span_T__CopyTo_Span_T + (byte)MemberFlags.Method, // Flags + (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Span_T - WellKnownType.ExtSentinel), // DeclaringTypeId + 0, // Arity + 1, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, // Return Type + (byte)SignatureTypeCode.GenericTypeInstance, + (byte)SignatureTypeCode.TypeHandle, (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Span_T - WellKnownType.ExtSentinel), + 1, + (byte)SignatureTypeCode.GenericTypeParameter, 0, + + // System_ReadOnlySpan_T__CopyTo_Span_T + (byte)MemberFlags.Method, // Flags + (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), // DeclaringTypeId + 0, // Arity + 1, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, // Return Type + (byte)SignatureTypeCode.GenericTypeInstance, + (byte)SignatureTypeCode.TypeHandle, (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_Span_T - WellKnownType.ExtSentinel), + 1, + (byte)SignatureTypeCode.GenericTypeParameter, 0, + + // System_Collections_Immutable_ImmutableArray_T__AsSpan + (byte)MemberFlags.Method, // Flags + (byte)WellKnownType.System_Collections_Immutable_ImmutableArray_T, // DeclaringTypeId + 0, // Arity + 0, // Method Signature + (byte)SignatureTypeCode.GenericTypeInstance, + (byte)SignatureTypeCode.TypeHandle, (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), // Return Type + 1, + (byte)SignatureTypeCode.GenericTypeParameter, 0, + + // System_Collections_Generic_List_T__AddRange + (byte)MemberFlags.Method, // Flags + (byte)WellKnownType.System_Collections_Generic_List_T, // DeclaringTypeId + 0, // Arity + 1, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, // Return Type + (byte)SignatureTypeCode.GenericTypeInstance, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Collections_Generic_IEnumerable_T, + 1, + (byte)SignatureTypeCode.GenericTypeParameter, 0, }; string[] allNames = new string[(int)WellKnownMember.Count] @@ -4928,6 +4985,12 @@ static WellKnownMembers() "AsSpan", // System_Runtime_InteropServices_CollectionsMarshal__AsSpan_T "SetCount", // System_Runtime_InteropServices_CollectionsMarshal__SetCount_T "AsImmutableArray", // System_Runtime_InteropServices_ImmutableCollectionsMarshal__AsImmutableArray_T + "ToArray", // System_Span_T__ToArray + "ToArray", // System_ReadOnlySpan_T__ToArray + "CopyTo", // System_Span_T__CopyTo_Span_T + "CopyTo", // System_ReadOnlySpan_T__CopyTo_Span_T + "AsSpan", // System_Collections_Immutable_ImmutableArray_T__AsSpan + "AddRange", // System_Collections_Generic_List_T__AddRange }; s_descriptors = MemberDescriptor.InitializeFromStream(new System.IO.MemoryStream(initializationBytes, writable: false), allNames); From fd3145e9e935e6a8c6f2ea01ec9852e6131d7a4f Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Tue, 12 Dec 2023 13:23:41 -0800 Subject: [PATCH 02/18] cleanup --- .../LocalRewriter_CollectionExpression.cs | 34 ++++++++++--------- .../Semantics/CollectionExpressionTests.cs | 4 --- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs index a3d91b2bf3556..74cc25e7744ab 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs @@ -470,7 +470,7 @@ private BoundExpression CreateAndPopulateArray(BoundCollectionExpression node, A return Factory.Call(rewrittenSpreadExpression, listToArrayMethod.AsMember((NamedTypeSymbol)spreadExpression.Type!)); } - if (GetAsSpanMethod(spreadExpression.Type) is { } asSpanMethod) + if (TryGetSpanConversion(spreadExpression.Type, out var asSpanMethod)) { var spanType = (asSpanMethod is null ? spreadExpression : CallAsSpanMethod(spreadExpression, asSpanMethod)).Type!.OriginalDefinition; if ((tryGetToArrayMethod(spanType, WellKnownType.System_Span_T, WellKnownMember.System_Span_T__ToArray) @@ -607,36 +607,45 @@ private BoundExpression CreateAndPopulateArray(BoundCollectionExpression node, A arrayType); } - private MethodSymbol? GetAsSpanMethod(TypeSymbol type) + /// + /// Returns true if type is convertible to Span or ReadOnlySpan. + /// If non-identity conversion, also returns a non-null asSpanMethod. + /// + private bool TryGetSpanConversion(TypeSymbol type, out MethodSymbol? asSpanMethod) { if (type is NamedTypeSymbol spanType && (spanType.OriginalDefinition.Equals(_compilation.GetWellKnownType(WellKnownType.System_Span_T), TypeCompareKind.ConsiderEverything) || spanType.OriginalDefinition.Equals(_compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T), TypeCompareKind.ConsiderEverything))) { - return null; + asSpanMethod = null; + return true; } if (type is ArrayTypeSymbol { IsSZArray: true } arrayType && _factory.WellKnownMethod(WellKnownMember.System_Span_T__ctor_Array, isOptional: true) is { } spanCtorArray) { - return spanCtorArray.AsMember(spanCtorArray.ContainingType.Construct(arrayType.ElementType)); + asSpanMethod = spanCtorArray.AsMember(spanCtorArray.ContainingType.Construct(arrayType.ElementType)); + return true; } if (type is NamedTypeSymbol immutableArrayType && immutableArrayType.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) { - return immutableArrayAsSpanMethod!.AsMember(immutableArrayType); + asSpanMethod = immutableArrayAsSpanMethod!.AsMember(immutableArrayType); + return true; } if (type is NamedTypeSymbol listType && listType.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) { - return collectionsMarshalAsSpanMethod.Construct(listType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type); + asSpanMethod = collectionsMarshalAsSpanMethod.Construct(listType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type); + return true; } - return null; + asSpanMethod = null; + return false; } private BoundExpression? TryConvertToSpanOrReadOnlySpan(BoundExpression expression) @@ -644,19 +653,12 @@ private BoundExpression CreateAndPopulateArray(BoundCollectionExpression node, A var type = expression.Type; Debug.Assert(type is not null); - if (GetAsSpanMethod(type) is not { } asSpanMethod) + if (!TryGetSpanConversion(type, out var asSpanMethod)) { return null; } - if (asSpanMethod is null) - { - return expression; - } - else - { - return CallAsSpanMethod(expression, asSpanMethod); - } + return asSpanMethod is null ? expression : CallAsSpanMethod(expression, asSpanMethod); } private BoundExpression CallAsSpanMethod(BoundExpression spreadExpression, MethodSymbol asSpanMethod) diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs index 97cf17cf1d790..ee42b92f11f49 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs @@ -24998,7 +24998,6 @@ static void Main() """; var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: IncludeExpectedOutput("[1, 2, 3], [4, 5, 6], [1, 2, 3, 4, 5, 6],"), verify: Verification.Skipped, targetFramework: TargetFramework.Net80); - //System.IO.File.WriteAllBytes(@"C:\Users\rikki\Desktop\assembly.dll", verifier.EmittedAssemblyData.ToArray()); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.Main", """ { @@ -25090,7 +25089,6 @@ class D : C { } """; var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: IncludeExpectedOutput("[D, D],"), verify: Verification.Skipped, targetFramework: TargetFramework.Net80); - //System.IO.File.WriteAllBytes(@"C:\Users\rikki\Desktop\assembly.dll", verifier.EmittedAssemblyData.ToArray()); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.Main", """ { @@ -25287,7 +25285,6 @@ static void M(ReadOnlySpan e1, ReadOnlySpan e2) """; var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: IncludeExpectedOutput("[1, 2, 3, 4, 5, 6],"), targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); - System.IO.File.WriteAllBytes(@"C:\Users\rikki\Desktop\assembly.dll", verifier.EmittedAssemblyData.ToArray()); verifier.Diagnostics.Where(d => d.Severity > DiagnosticSeverity.Hidden).Verify(); verifier.VerifyIL("C.M", """ { @@ -25459,7 +25456,6 @@ static void M(List e1, List e2) """; var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: IncludeExpectedOutput("[1, 2, 3, 4, 5, 6],"), targetFramework: TargetFramework.Net80); - System.IO.File.WriteAllBytes(@"C:\Users\rikki\Desktop\assembly.dll", verifier.EmittedAssemblyData.ToArray()); verifier.Diagnostics.Where(d => d.Severity > DiagnosticSeverity.Hidden).Verify(); verifier.VerifyIL("C.M", """ { From 00a17f61ada0a7275ce2c9213ad344e2bf7a113e Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Tue, 12 Dec 2023 13:33:21 -0800 Subject: [PATCH 03/18] Adjust test baseline to reflect intention of test --- .../Semantics/CollectionExpressionTests.cs | 59 ++++++++++--------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs index 828f6764a227a..f224a9c72f7cd 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs @@ -16581,7 +16581,7 @@ public CollectionBuilderAttribute(Type builderType, string methodName) { } class Program { static List F1(object x) => [x]; - static List F2(List y) => [..y]; + static List F2(List y) => [..y]; } """; var comp = CreateEmptyCompilation(new[] { sourceA, sourceB }, parseOptions: TestOptions.RegularPreview.WithNoRefSafetyRulesAttribute()); @@ -16603,56 +16603,57 @@ .maxstack 4 """); verifier.VerifyIL("Program.F2", """ { - // Code size 80 (0x50) + // Code size 85 (0x55) .maxstack 3 .locals init (int V_0, object[] V_1, - System.Collections.Generic.IEnumerator V_2, - object V_3, + System.Collections.Generic.IEnumerator V_2, + int V_3, System.IDisposable V_4) IL_0000: ldarg.0 IL_0001: ldc.i4.0 IL_0002: stloc.0 IL_0003: dup - IL_0004: callvirt "int System.Collections.Generic.List.Length.get" + IL_0004: callvirt "int System.Collections.Generic.List.Length.get" IL_0009: newarr "object" IL_000e: stloc.1 - IL_000f: callvirt "System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator()" + IL_000f: callvirt "System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator()" IL_0014: stloc.2 .try { - IL_0015: br.s IL_0026 + IL_0015: br.s IL_002b IL_0017: ldloc.2 - IL_0018: callvirt "object System.Collections.Generic.IEnumerator.Current.get" + IL_0018: callvirt "int System.Collections.Generic.IEnumerator.Current.get" IL_001d: stloc.3 IL_001e: ldloc.1 IL_001f: ldloc.0 IL_0020: ldloc.3 - IL_0021: stelem.ref - IL_0022: ldloc.0 - IL_0023: ldc.i4.1 - IL_0024: add - IL_0025: stloc.0 - IL_0026: ldloc.2 - IL_0027: callvirt "bool System.Collections.IEnumerator.MoveNext()" - IL_002c: brtrue.s IL_0017 - IL_002e: leave.s IL_0044 + IL_0021: box "int" + IL_0026: stelem.ref + IL_0027: ldloc.0 + IL_0028: ldc.i4.1 + IL_0029: add + IL_002a: stloc.0 + IL_002b: ldloc.2 + IL_002c: callvirt "bool System.Collections.IEnumerator.MoveNext()" + IL_0031: brtrue.s IL_0017 + IL_0033: leave.s IL_0049 } finally { - IL_0030: ldloc.2 - IL_0031: isinst "System.IDisposable" - IL_0036: stloc.s V_4 - IL_0038: ldloc.s V_4 - IL_003a: brfalse.s IL_0043 - IL_003c: ldloc.s V_4 - IL_003e: callvirt "void System.IDisposable.Dispose()" - IL_0043: endfinally + IL_0035: ldloc.2 + IL_0036: isinst "System.IDisposable" + IL_003b: stloc.s V_4 + IL_003d: ldloc.s V_4 + IL_003f: brfalse.s IL_0048 + IL_0041: ldloc.s V_4 + IL_0043: callvirt "void System.IDisposable.Dispose()" + IL_0048: endfinally } - IL_0044: ldloc.1 - IL_0045: newobj "System.ReadOnlySpan..ctor(object[])" - IL_004a: call "System.Collections.Generic.List System.Collections.Generic.ListBuilder.Create(System.ReadOnlySpan)" - IL_004f: ret + IL_0049: ldloc.1 + IL_004a: newobj "System.ReadOnlySpan..ctor(object[])" + IL_004f: call "System.Collections.Generic.List System.Collections.Generic.ListBuilder.Create(System.ReadOnlySpan)" + IL_0054: ret } """); } From d295100c46e8644485accad998407e2be7ba70bf Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Tue, 12 Dec 2023 13:49:20 -0800 Subject: [PATCH 04/18] Fix WellKnownTypeValidationTests --- .../SymbolsTests/WellKnownTypeValidationTests.vb | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb index f851cd72b04ee..8700f144d3848 100644 --- a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb +++ b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb @@ -809,7 +809,12 @@ End Namespace WellKnownMember.System_Runtime_CompilerServices_Unsafe__AsRef_T, WellKnownMember.System_Runtime_InteropServices_CollectionsMarshal__AsSpan_T, WellKnownMember.System_Runtime_InteropServices_CollectionsMarshal__SetCount_T, - WellKnownMember.System_Runtime_InteropServices_ImmutableCollectionsMarshal__AsImmutableArray_T + WellKnownMember.System_Runtime_InteropServices_ImmutableCollectionsMarshal__AsImmutableArray_T, + WellKnownMember.System_Span_T__ToArray, + WellKnownMember.System_ReadOnlySpan_T__ToArray, + WellKnownMember.System_Span_T__CopyTo_Span_T, + WellKnownMember.System_ReadOnlySpan_T__CopyTo_Span_T, + WellKnownMember.System_Collections_Immutable_ImmutableArray_T__AsSpan ' Not always available. Continue For End Select @@ -1009,7 +1014,12 @@ End Namespace WellKnownMember.System_Runtime_CompilerServices_Unsafe__AsRef_T, WellKnownMember.System_Runtime_InteropServices_CollectionsMarshal__AsSpan_T, WellKnownMember.System_Runtime_InteropServices_CollectionsMarshal__SetCount_T, - WellKnownMember.System_Runtime_InteropServices_ImmutableCollectionsMarshal__AsImmutableArray_T + WellKnownMember.System_Runtime_InteropServices_ImmutableCollectionsMarshal__AsImmutableArray_T, + WellKnownMember.System_Span_T__ToArray, + WellKnownMember.System_ReadOnlySpan_T__ToArray, + WellKnownMember.System_Span_T__CopyTo_Span_T, + WellKnownMember.System_ReadOnlySpan_T__CopyTo_Span_T, + WellKnownMember.System_Collections_Immutable_ImmutableArray_T__AsSpan ' Not always available. Continue For End Select From 853f8aafa8f76cf7708b0ef8446495cd7068b123 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Tue, 12 Dec 2023 14:52:30 -0800 Subject: [PATCH 05/18] address feedback and fixup tests --- .../Semantics/CollectionExpressionTests.cs | 52 +++++++++---------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs index f224a9c72f7cd..50d57d08dc593 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs @@ -25840,7 +25840,7 @@ static void Main() } """; - var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: "[1, 2, 3], [1, 2, 3],", verify: Verification.Skipped, targetFramework: TargetFramework.Net80); + var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: IncludeExpectedOutput("[1, 2, 3], [1, 2, 3],"), targetFramework: TargetFramework.Net80, verify: Verification.Skipped); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.Main", """ { @@ -25898,7 +25898,7 @@ static void Main() } """; - var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: "[1, 2, 3], [1, 2, 3],", verify: Verification.Skipped, targetFramework: TargetFramework.Net80); + var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: IncludeExpectedOutput("[1, 2, 3], [1, 2, 3],"), targetFramework: TargetFramework.Net80, verify: Verification.Skipped); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.Main", """ { @@ -26138,7 +26138,7 @@ static void Main() M([1, 2, 3], [4, 5, 6]); } - static void M(ReadOnlySpan e1, ReadOnlySpan e2) + static void M(Span e1, Span e2) { List result = [..e1, ..e2]; result.Report(); @@ -26146,14 +26146,14 @@ static void M(ReadOnlySpan e1, ReadOnlySpan e2) } """; - var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: IncludeExpectedOutput("[1, 2, 3, 4, 5, 6],"), targetFramework: TargetFramework.Net80); + var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, verify: Verification.Fails, expectedOutput: IncludeExpectedOutput("[1, 2, 3, 4, 5, 6],"), targetFramework: TargetFramework.Net80); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", """ { // Code size 110 (0x6e) .maxstack 5 - .locals init (System.ReadOnlySpan V_0, - System.ReadOnlySpan V_1, + .locals init (System.Span V_0, + System.Span V_1, System.Span V_2, int V_3) IL_0000: ldarg.0 @@ -26163,9 +26163,9 @@ .locals init (System.ReadOnlySpan V_0, IL_0004: newobj "System.Collections.Generic.List..ctor()" IL_0009: dup IL_000a: ldloca.s V_0 - IL_000c: call "int System.ReadOnlySpan.Length.get" + IL_000c: call "int System.Span.Length.get" IL_0011: ldloca.s V_1 - IL_0013: call "int System.ReadOnlySpan.Length.get" + IL_0013: call "int System.Span.Length.get" IL_0018: add IL_0019: call "void System.Runtime.InteropServices.CollectionsMarshal.SetCount(System.Collections.Generic.List, int)" IL_001e: dup @@ -26177,24 +26177,24 @@ .locals init (System.ReadOnlySpan V_0, IL_0029: ldloca.s V_2 IL_002b: ldloc.3 IL_002c: ldloca.s V_0 - IL_002e: call "int System.ReadOnlySpan.Length.get" + IL_002e: call "int System.Span.Length.get" IL_0033: call "System.Span System.Span.Slice(int, int)" - IL_0038: call "void System.ReadOnlySpan.CopyTo(System.Span)" + IL_0038: call "void System.Span.CopyTo(System.Span)" IL_003d: ldloc.3 IL_003e: ldloca.s V_0 - IL_0040: call "int System.ReadOnlySpan.Length.get" + IL_0040: call "int System.Span.Length.get" IL_0045: add IL_0046: stloc.3 IL_0047: ldloca.s V_1 IL_0049: ldloca.s V_2 IL_004b: ldloc.3 IL_004c: ldloca.s V_1 - IL_004e: call "int System.ReadOnlySpan.Length.get" + IL_004e: call "int System.Span.Length.get" IL_0053: call "System.Span System.Span.Slice(int, int)" - IL_0058: call "void System.ReadOnlySpan.CopyTo(System.Span)" + IL_0058: call "void System.Span.CopyTo(System.Span)" IL_005d: ldloc.3 IL_005e: ldloca.s V_1 - IL_0060: call "int System.ReadOnlySpan.Length.get" + IL_0060: call "int System.Span.Length.get" IL_0065: add IL_0066: stloc.3 IL_0067: ldc.i4.0 @@ -26226,8 +26226,8 @@ static void M(ReadOnlySpan e1, ReadOnlySpan e2) } """; - var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: IncludeExpectedOutput("[1, 2, 3, 4, 5, 6],"), targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); - verifier.Diagnostics.Where(d => d.Severity > DiagnosticSeverity.Hidden).Verify(); + var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, verify: Verification.FailsPEVerify, expectedOutput: IncludeExpectedOutput("[1, 2, 3, 4, 5, 6],"), targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); + verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", """ { // Code size 110 (0x6e) @@ -26288,7 +26288,6 @@ .locals init (System.ReadOnlySpan V_0, public void ArrayToList_Spreads() { var source = """ - using System; using System.Collections.Generic; class C @@ -26306,9 +26305,8 @@ static void M(int[] e1, int[] e2) } """; - var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: IncludeExpectedOutput("[1, 2, 3, 4, 5, 6],"), targetFramework: TargetFramework.Net80); - System.IO.File.WriteAllBytes(@"C:\Users\rikki\Desktop\assembly.dll", verifier.EmittedAssemblyData.ToArray()); - verifier.Diagnostics.Where(d => d.Severity > DiagnosticSeverity.Hidden).Verify(); + var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, verify: Verification.FailsPEVerify, expectedOutput: IncludeExpectedOutput("[1, 2, 3, 4, 5, 6],"), targetFramework: TargetFramework.Net80); + verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", """ { // Code size 118 (0x76) @@ -26379,7 +26377,6 @@ .locals init (int[] V_0, public void ListToList_Spreads() { var source = """ - using System; using System.Collections.Generic; class C @@ -26397,8 +26394,8 @@ static void M(List e1, List e2) } """; - var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: IncludeExpectedOutput("[1, 2, 3, 4, 5, 6],"), targetFramework: TargetFramework.Net80); - verifier.Diagnostics.Where(d => d.Severity > DiagnosticSeverity.Hidden).Verify(); + var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: IncludeExpectedOutput("[1, 2, 3, 4, 5, 6],"), targetFramework: TargetFramework.Net80, verify: Verification.Skipped); + verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", """ { // Code size 124 (0x7c) @@ -26467,7 +26464,6 @@ .locals init (System.Collections.Generic.List V_0, public void List_Spread_Mutation() { var source = """ - using System; using System.Collections.Generic; class C @@ -26493,8 +26489,8 @@ static void M(List e1) """; var expectedOutput = IncludeExpectedOutput("[1, 2, 1, 2, 3],"); - var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: expectedOutput, targetFramework: TargetFramework.Net80); - verifier.Diagnostics.Where(d => d.Severity > DiagnosticSeverity.Hidden).Verify(); + var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: expectedOutput, targetFramework: TargetFramework.Net80, verify: Verification.Skipped); + verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", """ { // Code size 157 (0x9d) @@ -26572,9 +26568,9 @@ .locals init (int V_0, //i } """); - var comp = CreateCompilation(new[] { source, s_collectionExtensionsWithSpan }, targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); + var comp = CreateCompilation(new[] { source, s_collectionExtensionsWithSpan }, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); comp.MakeMemberMissing(WellKnownMember.System_Runtime_InteropServices_CollectionsMarshal__AsSpan_T); - verifier = CompileAndVerify(comp, expectedOutput: expectedOutput); + verifier = CompileAndVerify(comp, expectedOutput: expectedOutput, verify: Verification.Skipped); verifier.VerifyIL("C.M", """ { // Code size 59 (0x3b) From 38ee84efa9671eb84e0308a54fccf099898a79a5 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Tue, 12 Dec 2023 14:55:16 -0800 Subject: [PATCH 06/18] Fix MissingSpecialMember tests --- .../CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs index 23536c5b6d3cd..e477803077b41 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs @@ -1064,6 +1064,11 @@ public void AllWellKnownTypeMembers() case WellKnownMember.System_Runtime_InteropServices_CollectionsMarshal__AsSpan_T: case WellKnownMember.System_Runtime_InteropServices_CollectionsMarshal__SetCount_T: case WellKnownMember.System_Runtime_InteropServices_ImmutableCollectionsMarshal__AsImmutableArray_T: + case WellKnownMember.System_Span_T__ToArray: + case WellKnownMember.System_ReadOnlySpan_T__ToArray: + case WellKnownMember.System_Span_T__CopyTo_Span_T: + case WellKnownMember.System_ReadOnlySpan_T__CopyTo_Span_T: + case WellKnownMember.System_Collections_Immutable_ImmutableArray_T__AsSpan: // Not always available. continue; } From 63dbe87a3df44fb7e01d154526a12c0995088e7a Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Wed, 13 Dec 2023 14:37:11 -0800 Subject: [PATCH 07/18] more --- .../LocalRewriter_CollectionExpression.cs | 33 ++--- .../Semantics/CollectionExpressionTests.cs | 118 ++++++++++++++++++ 2 files changed, 136 insertions(+), 15 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs index 714c76cae2e6a..1d2e0c824e997 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs @@ -531,26 +531,24 @@ private BoundExpression CreateAndPopulateArray(BoundCollectionExpression node, A if (tryGetToArrayMethod(spreadTypeOriginalDefinition, WellKnownType.System_Collections_Generic_List_T, WellKnownMember.System_Collections_Generic_List_T__ToArray) is { } listToArrayMethod) { var rewrittenSpreadExpression = VisitExpression(spreadExpression); - return Factory.Call(rewrittenSpreadExpression, listToArrayMethod.AsMember((NamedTypeSymbol)spreadExpression.Type!)); + return _factory.Call(rewrittenSpreadExpression, listToArrayMethod.AsMember((NamedTypeSymbol)spreadExpression.Type!)); } if (TryGetSpanConversion(spreadExpression.Type, out var asSpanMethod)) { - var spanType = (asSpanMethod is null ? spreadExpression : CallAsSpanMethod(spreadExpression, asSpanMethod)).Type!.OriginalDefinition; + var spanType = CallAsSpanMethod(spreadExpression, asSpanMethod).Type!.OriginalDefinition; if ((tryGetToArrayMethod(spanType, WellKnownType.System_Span_T, WellKnownMember.System_Span_T__ToArray) ?? tryGetToArrayMethod(spanType, WellKnownType.System_ReadOnlySpan_T, WellKnownMember.System_ReadOnlySpan_T__ToArray)) is { } toArrayMethod) { - var rewrittenSpreadExpression = VisitExpression(spreadExpression); - if (asSpanMethod is not null) - rewrittenSpreadExpression = CallAsSpanMethod(rewrittenSpreadExpression, asSpanMethod); - return Factory.Call(rewrittenSpreadExpression, toArrayMethod.AsMember((NamedTypeSymbol)rewrittenSpreadExpression.Type!)); + var rewrittenSpreadExpression = CallAsSpanMethod(VisitExpression(spreadExpression), asSpanMethod); + return _factory.Call(rewrittenSpreadExpression, toArrayMethod.AsMember((NamedTypeSymbol)rewrittenSpreadExpression.Type!)); } } MethodSymbol? tryGetToArrayMethod(TypeSymbol spreadTypeOriginalDefinition, WellKnownType wellKnownType, WellKnownMember wellKnownMember) { - if (spreadTypeOriginalDefinition.Equals(this._compilation.GetWellKnownType(wellKnownType))) + if (TypeSymbol.Equals(spreadTypeOriginalDefinition, this._compilation.GetWellKnownType(wellKnownType), TypeCompareKind.AllIgnoreOptions)) { return _factory.WellKnownMethod(wellKnownMember, isOptional: true); } @@ -640,14 +638,14 @@ private BoundExpression CreateAndPopulateArray(BoundCollectionExpression node, A isRef: false, indexTemp.Type)); }, - (sideEffects, targetArray, spreadElement, rewrittenSpreadOperand) => + (sideEffects, arrayTemp, spreadElement, rewrittenSpreadOperand) => { if (TryPrepareCopyToOptimization(spreadElement, rewrittenSpreadOperand) is not var (spanSliceMethod, spreadElementAsSpan, getLengthMethod, copyToMethod)) return false; if (targetSpanTemp is null) { - var targetSpan = TryConvertToSpanOrReadOnlySpan(targetArray); + var targetSpan = TryConvertToSpanOrReadOnlySpan(arrayTemp); if (targetSpan is null) return false; @@ -696,7 +694,7 @@ private bool TryGetSpanConversion(TypeSymbol type, out MethodSymbol? asSpanMetho && immutableArrayType.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(immutableArrayType); + asSpanMethod = immutableArrayAsSpanMethod.AsMember(immutableArrayType); return true; } @@ -722,11 +720,15 @@ private bool TryGetSpanConversion(TypeSymbol type, out MethodSymbol? asSpanMetho return null; } - return asSpanMethod is null ? expression : CallAsSpanMethod(expression, asSpanMethod); + return CallAsSpanMethod(expression, asSpanMethod); } - private BoundExpression CallAsSpanMethod(BoundExpression spreadExpression, MethodSymbol asSpanMethod) + 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); @@ -970,7 +972,8 @@ private BoundExpression CreateAndPopulateList(BoundCollectionExpression node, Ty _diagnostics.Add(rewrittenSpreadOperand.Syntax, useSiteInfo); if (conversion.IsImplicit && conversion.IsReference) { - sideEffects.Add(_factory.Call(listTemp, addRangeMethod, MakeConversionNode(rewrittenSpreadOperand, type, @checked: false, markAsChecked: true))); + conversion.MarkUnderlyingConversionsCheckedRecursive(); + sideEffects.Add(_factory.Call(listTemp, addRangeMethod, MakeConversionNode(rewrittenSpreadOperand.Syntax, rewrittenSpreadOperand, conversion, type, @checked: false))); return true; } @@ -1020,7 +1023,7 @@ private void AddCollectionExpressionElements( int numberIncludingLastSpread, ArrayBuilder sideEffects, Action, BoundExpression, BoundExpression> addElement, - Func, BoundExpression, BoundCollectionExpressionSpreadElement, BoundExpression, bool>? tryOptimizeSpreadElement = null) + Func, BoundExpression, BoundCollectionExpressionSpreadElement, BoundExpression, bool> tryOptimizeSpreadElement) { for (int i = 0; i < elements.Length; i++) { @@ -1031,7 +1034,7 @@ private void AddCollectionExpressionElements( if (element is BoundCollectionExpressionSpreadElement spreadElement) { - if (tryOptimizeSpreadElement?.Invoke(sideEffects, rewrittenReceiver, spreadElement, rewrittenExpression) == true) + if (tryOptimizeSpreadElement.Invoke(sideEffects, rewrittenReceiver, spreadElement, rewrittenExpression)) continue; var rewrittenElement = MakeCollectionExpressionSpreadElement( diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs index 50d57d08dc593..eded169e461fa 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs @@ -26607,5 +26607,123 @@ .locals init (int V_0, //i } """); } + + [Fact] + public void Spread_RuntimeEquivalentElement() + { + var source = """ + using System; + using System.Collections.Generic; + + class C + { + System.IntPtr[] M1(List list) + { + return [..list]; + } + + List M2(System.IntPtr[] list) + { + return [..list]; + } + + nint[] M3(List list) + { + return [..list]; + } + } + """; + + var verifier = CompileAndVerify(source, targetFramework: TargetFramework.Net80); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C.M1", """ + { + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.1 + IL_0001: callvirt "nint[] System.Collections.Generic.List.ToArray()" + IL_0006: ret + } + """); + + verifier.VerifyIL("C.M2", """ + { + // Code size 66 (0x42) + .maxstack 5 + .locals init (nint[] V_0, + System.Span V_1, + int V_2, + System.Span V_3) + IL_0000: ldarg.1 + IL_0001: stloc.0 + IL_0002: newobj "System.Collections.Generic.List..ctor()" + IL_0007: dup + IL_0008: ldloc.0 + IL_0009: ldlen + IL_000a: conv.i4 + IL_000b: call "void System.Runtime.InteropServices.CollectionsMarshal.SetCount(System.Collections.Generic.List, int)" + IL_0010: dup + IL_0011: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_0016: stloc.1 + IL_0017: ldc.i4.0 + IL_0018: stloc.2 + IL_0019: ldloca.s V_3 + IL_001b: ldloc.0 + IL_001c: call "System.Span..ctor(nint[])" + IL_0021: ldloca.s V_3 + IL_0023: ldloca.s V_1 + IL_0025: ldloc.2 + IL_0026: ldloca.s V_3 + IL_0028: call "int System.Span.Length.get" + IL_002d: call "System.Span System.Span.Slice(int, int)" + IL_0032: call "void System.Span.CopyTo(System.Span)" + IL_0037: ldloc.2 + IL_0038: ldloca.s V_3 + IL_003a: call "int System.Span.Length.get" + IL_003f: add + IL_0040: stloc.2 + IL_0041: ret + } + """); + + verifier.VerifyIL("C.M3", """ + { + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.1 + IL_0001: callvirt "nint[] System.Collections.Generic.List.ToArray()" + IL_0006: ret + } + """); + } + + [Fact] + public void Spread_Array_Await() + { + var source = """ + using System.Threading.Tasks; + using System.Collections.Generic; + + class C + { + static async Task Main() + { + List items = [1]; + int[] items1 = [..await M2(), (await M2())[0]]; + items1.Report(); + } + + static async Task M2() + { + await Task.Yield(); + return new[] { 2 }; + } + } + """; + + var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: null /*IncludeExpectedOutput("[2, 1], [1, 2, 1, 2],")*/, targetFramework: TargetFramework.Net80, verify: Verification.Skipped); + System.IO.File.WriteAllBytes(@"C:\Users\rikki\Desktop\assembly.dll", verifier.EmittedAssemblyData.ToArray()); + verifier.VerifyDiagnostics(); + } } } From 3eac1bf0ab9439b1cb2ec666e0adac50ffaac6e0 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Thu, 14 Dec 2023 12:44:05 -0800 Subject: [PATCH 08/18] cleanup, feedback, TODOs --- .../Semantics/CollectionExpressionTests.cs | 172 +++++++++++++++++- 1 file changed, 171 insertions(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs index eded169e461fa..4c1d368b732f2 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs @@ -26373,6 +26373,166 @@ .locals init (int[] V_0, """); } + [Fact] + public void MDArrayToList_Spreads() + { + var source = """ + using System.Collections.Generic; + + class C + { + static void Main() + { + M(new [,] { { 1, 2, 3 } }, new [,] { { 4, 5, 6 } }); + } + + static void M(int[,] e1, int[,] e2) + { + List result = [..e1, ..e2]; + result.Report(); + } + } + """; + + var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, verify: Verification.FailsPEVerify, expectedOutput: IncludeExpectedOutput("[1, 2, 3, 4, 5, 6],"), targetFramework: TargetFramework.Net80); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C.M", """ + { + // Code size 251 (0xfb) + .maxstack 3 + .locals init (int[,] V_0, + int[,] V_1, + System.Collections.Generic.List V_2, + System.Span V_3, + int V_4, + int[,] V_5, + int V_6, + int V_7, + int V_8, + int V_9, + int V_10) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldarg.1 + IL_0003: stloc.1 + IL_0004: newobj "System.Collections.Generic.List..ctor()" + IL_0009: stloc.2 + IL_000a: ldloc.2 + IL_000b: ldloc.0 + IL_000c: callvirt "int System.Array.Length.get" + IL_0011: ldloc.1 + IL_0012: callvirt "int System.Array.Length.get" + IL_0017: add + IL_0018: call "void System.Runtime.InteropServices.CollectionsMarshal.SetCount(System.Collections.Generic.List, int)" + IL_001d: ldloc.2 + IL_001e: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_0023: stloc.3 + IL_0024: ldc.i4.0 + IL_0025: stloc.s V_4 + IL_0027: ldloc.0 + IL_0028: stloc.s V_5 + IL_002a: ldloc.s V_5 + IL_002c: ldc.i4.0 + IL_002d: callvirt "int System.Array.GetUpperBound(int)" + IL_0032: stloc.s V_6 + IL_0034: ldloc.s V_5 + IL_0036: ldc.i4.1 + IL_0037: callvirt "int System.Array.GetUpperBound(int)" + IL_003c: stloc.s V_7 + IL_003e: ldloc.s V_5 + IL_0040: ldc.i4.0 + IL_0041: callvirt "int System.Array.GetLowerBound(int)" + IL_0046: stloc.s V_8 + IL_0048: br.s IL_0087 + IL_004a: ldloc.s V_5 + IL_004c: ldc.i4.1 + IL_004d: callvirt "int System.Array.GetLowerBound(int)" + IL_0052: stloc.s V_9 + IL_0054: br.s IL_007b + IL_0056: ldloc.s V_5 + IL_0058: ldloc.s V_8 + IL_005a: ldloc.s V_9 + IL_005c: call "int[*,*].Get" + IL_0061: stloc.s V_10 + IL_0063: ldloca.s V_3 + IL_0065: ldloc.s V_4 + IL_0067: call "ref int System.Span.this[int].get" + IL_006c: ldloc.s V_10 + IL_006e: stind.i4 + IL_006f: ldloc.s V_4 + IL_0071: ldc.i4.1 + IL_0072: add + IL_0073: stloc.s V_4 + IL_0075: ldloc.s V_9 + IL_0077: ldc.i4.1 + IL_0078: add + IL_0079: stloc.s V_9 + IL_007b: ldloc.s V_9 + IL_007d: ldloc.s V_7 + IL_007f: ble.s IL_0056 + IL_0081: ldloc.s V_8 + IL_0083: ldc.i4.1 + IL_0084: add + IL_0085: stloc.s V_8 + IL_0087: ldloc.s V_8 + IL_0089: ldloc.s V_6 + IL_008b: ble.s IL_004a + IL_008d: ldloc.1 + IL_008e: stloc.s V_5 + IL_0090: ldloc.s V_5 + IL_0092: ldc.i4.0 + IL_0093: callvirt "int System.Array.GetUpperBound(int)" + IL_0098: stloc.s V_7 + IL_009a: ldloc.s V_5 + IL_009c: ldc.i4.1 + IL_009d: callvirt "int System.Array.GetUpperBound(int)" + IL_00a2: stloc.s V_6 + IL_00a4: ldloc.s V_5 + IL_00a6: ldc.i4.0 + IL_00a7: callvirt "int System.Array.GetLowerBound(int)" + IL_00ac: stloc.s V_8 + IL_00ae: br.s IL_00ed + IL_00b0: ldloc.s V_5 + IL_00b2: ldc.i4.1 + IL_00b3: callvirt "int System.Array.GetLowerBound(int)" + IL_00b8: stloc.s V_9 + IL_00ba: br.s IL_00e1 + IL_00bc: ldloc.s V_5 + IL_00be: ldloc.s V_8 + IL_00c0: ldloc.s V_9 + IL_00c2: call "int[*,*].Get" + IL_00c7: stloc.s V_10 + IL_00c9: ldloca.s V_3 + IL_00cb: ldloc.s V_4 + IL_00cd: call "ref int System.Span.this[int].get" + IL_00d2: ldloc.s V_10 + IL_00d4: stind.i4 + IL_00d5: ldloc.s V_4 + IL_00d7: ldc.i4.1 + IL_00d8: add + IL_00d9: stloc.s V_4 + IL_00db: ldloc.s V_9 + IL_00dd: ldc.i4.1 + IL_00de: add + IL_00df: stloc.s V_9 + IL_00e1: ldloc.s V_9 + IL_00e3: ldloc.s V_6 + IL_00e5: ble.s IL_00bc + IL_00e7: ldloc.s V_8 + IL_00e9: ldc.i4.1 + IL_00ea: add + IL_00eb: stloc.s V_8 + IL_00ed: ldloc.s V_8 + IL_00ef: ldloc.s V_7 + IL_00f1: ble.s IL_00b0 + IL_00f3: ldloc.2 + IL_00f4: ldc.i4.0 + IL_00f5: call "void CollectionExtensions.Report(object, bool)" + IL_00fa: ret + } + """); + } + [Fact] public void ListToList_Spreads() { @@ -26608,6 +26768,11 @@ .locals init (int V_0, //i """); } + // TODO2: more test cases + // consider testing more combinations of runtime-equivalent source and dest element types + // test the dynamic case and/or highlight an existing test + // test targeting to span + // test targeting to IEnumerable/other interfaces [Fact] public void Spread_RuntimeEquivalentElement() { @@ -26721,9 +26886,14 @@ static async Task M2() } """; + // TODO2: get rid of span temp to avoid async problems, update baselines. var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: null /*IncludeExpectedOutput("[2, 1], [1, 2, 1, 2],")*/, targetFramework: TargetFramework.Net80, verify: Verification.Skipped); - System.IO.File.WriteAllBytes(@"C:\Users\rikki\Desktop\assembly.dll", verifier.EmittedAssemblyData.ToArray()); verifier.VerifyDiagnostics(); } + + // TODO2 add tests: + // - List.AddRange where spread operand is an IEnumerable + // - " " where spread operand is IEnumerable where U : T + // - " " where spread operand is struct : IEnumerable } } From 882a6439383a6290dde79b99a586d4ce56a2ca99 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Thu, 14 Dec 2023 14:34:43 -0800 Subject: [PATCH 09/18] more tests --- .../Semantics/CollectionExpressionTests.cs | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs index 4c1d368b732f2..2ecc2ac2a6041 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs @@ -7465,6 +7465,172 @@ static void Main() Diagnostic(ErrorCode.ERR_NoImplicitConv, "x").WithArguments("object", "int").WithLocation(6, 25)); } + [Fact] + public void SpreadElement_Dynamic_04() + { + var source = """ + using System.Collections.Generic; + + class Program + { + static void Main() + { + object[] objs = [1,2,3]; + objs.Report(); + List dyns = [..objs]; + dyns.Report(); + } + } + """; + + var verifier = CompileAndVerify(new[] { source, s_collectionExtensions }, expectedOutput: IncludeExpectedOutput("[1, 2, 3], [1, 2, 3],"), targetFramework: TargetFramework.Net80); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.Main", """ + { + // Code size 111 (0x6f) + .maxstack 5 + .locals init (object[] V_0, + System.Span V_1, + int V_2, + System.Span V_3) + IL_0000: ldc.i4.3 + IL_0001: newarr "object" + IL_0006: dup + IL_0007: ldc.i4.0 + IL_0008: ldc.i4.1 + IL_0009: box "int" + IL_000e: stelem.ref + IL_000f: dup + IL_0010: ldc.i4.1 + IL_0011: ldc.i4.2 + IL_0012: box "int" + IL_0017: stelem.ref + IL_0018: dup + IL_0019: ldc.i4.2 + IL_001a: ldc.i4.3 + IL_001b: box "int" + IL_0020: stelem.ref + IL_0021: dup + IL_0022: ldc.i4.0 + IL_0023: call "void CollectionExtensions.Report(object, bool)" + IL_0028: stloc.0 + IL_0029: newobj "System.Collections.Generic.List..ctor()" + IL_002e: dup + IL_002f: ldloc.0 + IL_0030: ldlen + IL_0031: conv.i4 + IL_0032: call "void System.Runtime.InteropServices.CollectionsMarshal.SetCount(System.Collections.Generic.List, int)" + IL_0037: dup + IL_0038: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_003d: stloc.1 + IL_003e: ldc.i4.0 + IL_003f: stloc.2 + IL_0040: ldloca.s V_3 + IL_0042: ldloc.0 + IL_0043: call "System.Span..ctor(object[])" + IL_0048: ldloca.s V_3 + IL_004a: ldloca.s V_1 + IL_004c: ldloc.2 + IL_004d: ldloca.s V_3 + IL_004f: call "int System.Span.Length.get" + IL_0054: call "System.Span System.Span.Slice(int, int)" + IL_0059: call "void System.Span.CopyTo(System.Span)" + IL_005e: ldloc.2 + IL_005f: ldloca.s V_3 + IL_0061: call "int System.Span.Length.get" + IL_0066: add + IL_0067: stloc.2 + IL_0068: ldc.i4.0 + IL_0069: call "void CollectionExtensions.Report(object, bool)" + IL_006e: ret + } + """); + } + + [Fact] + public void SpreadElement_NullabilityDifference() + { + var source = """ + #nullable enable + using System.Collections.Generic; + + class Program + { + static void Main() + { + object[] objs = [1,2,3]; + objs.Report(); + List list = [..objs]; + list.Report(); + } + } + """; + + var verifier = CompileAndVerify(new[] { source, s_collectionExtensions }, expectedOutput: IncludeExpectedOutput("[1, 2, 3], [1, 2, 3],"), targetFramework: TargetFramework.Net80); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Program.Main", """ + { + // Code size 111 (0x6f) + .maxstack 5 + .locals init (object[] V_0, + System.Span V_1, + int V_2, + System.Span V_3) + IL_0000: ldc.i4.3 + IL_0001: newarr "object" + IL_0006: dup + IL_0007: ldc.i4.0 + IL_0008: ldc.i4.1 + IL_0009: box "int" + IL_000e: stelem.ref + IL_000f: dup + IL_0010: ldc.i4.1 + IL_0011: ldc.i4.2 + IL_0012: box "int" + IL_0017: stelem.ref + IL_0018: dup + IL_0019: ldc.i4.2 + IL_001a: ldc.i4.3 + IL_001b: box "int" + IL_0020: stelem.ref + IL_0021: dup + IL_0022: ldc.i4.0 + IL_0023: call "void CollectionExtensions.Report(object, bool)" + IL_0028: stloc.0 + IL_0029: newobj "System.Collections.Generic.List..ctor()" + IL_002e: dup + IL_002f: ldloc.0 + IL_0030: ldlen + IL_0031: conv.i4 + IL_0032: call "void System.Runtime.InteropServices.CollectionsMarshal.SetCount(System.Collections.Generic.List, int)" + IL_0037: dup + IL_0038: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_003d: stloc.1 + IL_003e: ldc.i4.0 + IL_003f: stloc.2 + IL_0040: ldloca.s V_3 + IL_0042: ldloc.0 + IL_0043: call "System.Span..ctor(object[])" + IL_0048: ldloca.s V_3 + IL_004a: ldloca.s V_1 + IL_004c: ldloc.2 + IL_004d: ldloca.s V_3 + IL_004f: call "int System.Span.Length.get" + IL_0054: call "System.Span System.Span.Slice(int, int)" + IL_0059: call "void System.Span.CopyTo(System.Span)" + IL_005e: ldloc.2 + IL_005f: ldloca.s V_3 + IL_0061: call "int System.Span.Length.get" + IL_0066: add + IL_0067: stloc.2 + IL_0068: ldc.i4.0 + IL_0069: call "void CollectionExtensions.Report(object, bool)" + IL_006e: ret + } + """); + } + [Fact] public void SpreadElement_MissingList() { @@ -26891,6 +27057,28 @@ static async Task M2() verifier.VerifyDiagnostics(); } + [Fact] + public void Spread_TargetSpan() + { + var source = """ + using System; + class C + { + static void Main() + { + int[] arr = [1, 2, 3]; + arr.Report(); + Span span = [..arr]; + span.Report(); + } + } + """; + + var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: null /*IncludeExpectedOutput("[2, 1], [1, 2, 1, 2],")*/, targetFramework: TargetFramework.Net80, verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C.Main", """ """); + } + // TODO2 add tests: // - List.AddRange where spread operand is an IEnumerable // - " " where spread operand is IEnumerable where U : T From 501d5b1410eb7c659c5c07f6eedebdd449aef574 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Thu, 14 Dec 2023 15:43:10 -0800 Subject: [PATCH 10/18] Get rid of temp for destination span. Adjust test baselines --- .../LocalRewriter_CollectionExpression.cs | 20 +- .../Semantics/CollectionExpressionTests.cs | 255 ++++++++++-------- 2 files changed, 156 insertions(+), 119 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs index 1d2e0c824e997..199e97a4316cf 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs @@ -606,7 +606,6 @@ private BoundExpression CreateAndPopulateArray(BoundCollectionExpression node, A localsBuilder.Add(arrayTemp); sideEffects.Add(assignmentToTemp); - BoundLocal? targetSpanTemp = null; AddCollectionExpressionElements( elements, arrayTemp, @@ -643,18 +642,13 @@ private BoundExpression CreateAndPopulateArray(BoundCollectionExpression node, A if (TryPrepareCopyToOptimization(spreadElement, rewrittenSpreadOperand) is not var (spanSliceMethod, spreadElementAsSpan, getLengthMethod, copyToMethod)) return false; - if (targetSpanTemp is null) - { - var targetSpan = TryConvertToSpanOrReadOnlySpan(arrayTemp); - if (targetSpan is null) - return false; - - targetSpanTemp = _factory.StoreToTemp(targetSpan, out var assignmentToTemp); - localsBuilder.Add(targetSpanTemp); - sideEffects.Add(assignmentToTemp); - } + // 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. + var targetSpan = TryConvertToSpanOrReadOnlySpan(arrayTemp); + if (targetSpan is null) + return false; - PerformCopyToOptimization(sideEffects, localsBuilder, indexTemp, targetSpanTemp, rewrittenSpreadOperand, spanSliceMethod, spreadElementAsSpan, getLengthMethod, copyToMethod); + PerformCopyToOptimization(sideEffects, localsBuilder, indexTemp, targetSpan, rewrittenSpreadOperand, spanSliceMethod, spreadElementAsSpan, getLengthMethod, copyToMethod); return true; }); @@ -960,7 +954,7 @@ private BoundExpression CreateAndPopulateList(BoundCollectionExpression node, Ty expressions.Add( _factory.Call(listTemp, addMethod, rewrittenValue)); }, - (sideEffects, spanTemp, spreadElement, rewrittenSpreadOperand) => + (sideEffects, listTemp, spreadElement, rewrittenSpreadOperand) => { if (addRangeMethod is null) return false; diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs index 2ecc2ac2a6041..4e728a2fbae34 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs @@ -6624,7 +6624,7 @@ static void Append({{collectionType}} x) verifier.VerifyIL("Program.Append", """ { - // Code size 136 (0x88) + // Code size 145 (0x91) .maxstack 5 .locals init (System.ReadOnlySpan V_0, //y System.ReadOnlySpan V_1, //z @@ -6650,10 +6650,10 @@ .locals init (System.ReadOnlySpan V_0, //y IL_0022: add IL_0023: newarr "int" IL_0028: stloc.s V_5 - IL_002a: ldloca.s V_6 + IL_002a: ldloca.s V_2 IL_002c: ldloc.s V_5 - IL_002e: call "System.Span..ctor(int[])" - IL_0033: ldloca.s V_2 + IL_002e: newobj "System.Span..ctor(int[])" + IL_0033: stloc.s V_6 IL_0035: ldloca.s V_6 IL_0037: ldloc.s V_4 IL_0039: ldloca.s V_2 @@ -6666,22 +6666,25 @@ .locals init (System.ReadOnlySpan V_0, //y IL_0053: add IL_0054: stloc.s V_4 IL_0056: ldloca.s V_3 - IL_0058: ldloca.s V_6 - IL_005a: ldloc.s V_4 - IL_005c: ldloca.s V_3 - IL_005e: call "int System.ReadOnlySpan.Length.get" - IL_0063: call "System.Span System.Span.Slice(int, int)" - IL_0068: call "void System.ReadOnlySpan.CopyTo(System.Span)" - IL_006d: ldloc.s V_4 - IL_006f: ldloca.s V_3 - IL_0071: call "int System.ReadOnlySpan.Length.get" - IL_0076: add - IL_0077: stloc.s V_4 - IL_0079: ldloc.s V_5 - IL_007b: call "System.ReadOnlySpan..ctor(int[])" - IL_0080: ldloca.s V_1 - IL_0082: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" - IL_0087: ret + IL_0058: ldloc.s V_5 + IL_005a: newobj "System.Span..ctor(int[])" + IL_005f: stloc.s V_6 + IL_0061: ldloca.s V_6 + IL_0063: ldloc.s V_4 + IL_0065: ldloca.s V_3 + IL_0067: call "int System.ReadOnlySpan.Length.get" + IL_006c: call "System.Span System.Span.Slice(int, int)" + IL_0071: call "void System.ReadOnlySpan.CopyTo(System.Span)" + IL_0076: ldloc.s V_4 + IL_0078: ldloca.s V_3 + IL_007a: call "int System.ReadOnlySpan.Length.get" + IL_007f: add + IL_0080: stloc.s V_4 + IL_0082: ldloc.s V_5 + IL_0084: call "System.ReadOnlySpan..ctor(int[])" + IL_0089: ldloca.s V_1 + IL_008b: call "void CollectionExtensions.Report(in System.ReadOnlySpan)" + IL_0090: ret } """); } @@ -7740,7 +7743,7 @@ static void Main() expectedOutput: IncludeExpectedOutput("[1, 2, 3], ")); verifier.VerifyIL("Program.Convert", """ { - // Code size 113 (0x71) + // Code size 120 (0x78) .maxstack 4 .locals init (System.Collections.Generic.List V_0, int V_1, @@ -7760,40 +7763,43 @@ .locals init (System.Collections.Generic.List V_0, IL_0011: add IL_0012: newarr "T" IL_0017: stloc.2 - IL_0018: ldloca.s V_3 - IL_001a: ldloc.2 - IL_001b: call "System.Span..ctor(T[])" - IL_0020: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" - IL_0025: stloc.s V_4 - IL_0027: ldloca.s V_4 - IL_0029: ldloca.s V_3 - IL_002b: ldloc.1 - IL_002c: ldloca.s V_4 - IL_002e: call "int System.Span.Length.get" - IL_0033: call "System.Span System.Span.Slice(int, int)" - IL_0038: call "void System.Span.CopyTo(System.Span)" - IL_003d: ldloc.1 - IL_003e: ldloca.s V_4 - IL_0040: call "int System.Span.Length.get" - IL_0045: add - IL_0046: stloc.1 - IL_0047: ldloc.0 - IL_0048: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" - IL_004d: stloc.s V_5 - IL_004f: ldloca.s V_5 - IL_0051: ldloca.s V_3 - IL_0053: ldloc.1 - IL_0054: ldloca.s V_5 - IL_0056: call "int System.Span.Length.get" - IL_005b: call "System.Span System.Span.Slice(int, int)" - IL_0060: call "void System.Span.CopyTo(System.Span)" - IL_0065: ldloc.1 - IL_0066: ldloca.s V_5 - IL_0068: call "int System.Span.Length.get" - IL_006d: add - IL_006e: stloc.1 - IL_006f: ldloc.2 - IL_0070: ret + IL_0018: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_001d: stloc.3 + IL_001e: ldloca.s V_3 + IL_0020: ldloc.2 + IL_0021: newobj "System.Span..ctor(T[])" + IL_0026: stloc.s V_5 + IL_0028: ldloca.s V_5 + IL_002a: ldloc.1 + IL_002b: ldloca.s V_3 + IL_002d: call "int System.Span.Length.get" + IL_0032: call "System.Span System.Span.Slice(int, int)" + IL_0037: call "void System.Span.CopyTo(System.Span)" + IL_003c: ldloc.1 + IL_003d: ldloca.s V_3 + IL_003f: call "int System.Span.Length.get" + IL_0044: add + IL_0045: stloc.1 + IL_0046: ldloc.0 + IL_0047: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_004c: stloc.s V_4 + IL_004e: ldloca.s V_4 + IL_0050: ldloc.2 + IL_0051: newobj "System.Span..ctor(T[])" + IL_0056: stloc.s V_5 + IL_0058: ldloca.s V_5 + IL_005a: ldloc.1 + IL_005b: ldloca.s V_4 + IL_005d: call "int System.Span.Length.get" + IL_0062: call "System.Span System.Span.Slice(int, int)" + IL_0067: call "void System.Span.CopyTo(System.Span)" + IL_006c: ldloc.1 + IL_006d: ldloca.s V_4 + IL_006f: call "int System.Span.Length.get" + IL_0074: add + IL_0075: stloc.1 + IL_0076: ldloc.2 + IL_0077: ret } """); } @@ -7833,7 +7839,7 @@ static int[] F() """)); verifier.VerifyIL("Program.F", """ { - // Code size 296 (0x128) + // Code size 305 (0x131) .maxstack 5 .locals init (int[] V_0, int[,] V_1, @@ -7895,20 +7901,20 @@ .locals init (int[] V_0, IL_0067: newarr "int" IL_006c: stloc.s V_4 IL_006e: ldloca.s V_5 - IL_0070: ldloc.s V_4 - IL_0072: call "System.Span..ctor(int[])" - IL_0077: ldloca.s V_6 - IL_0079: ldloc.0 - IL_007a: call "System.Span..ctor(int[])" - IL_007f: ldloca.s V_6 - IL_0081: ldloca.s V_5 + IL_0070: ldloc.0 + IL_0071: call "System.Span..ctor(int[])" + IL_0076: ldloca.s V_5 + IL_0078: ldloc.s V_4 + IL_007a: newobj "System.Span..ctor(int[])" + IL_007f: stloc.s V_7 + IL_0081: ldloca.s V_7 IL_0083: ldloc.3 - IL_0084: ldloca.s V_6 + IL_0084: ldloca.s V_5 IL_0086: call "int System.Span.Length.get" IL_008b: call "System.Span System.Span.Slice(int, int)" IL_0090: call "void System.Span.CopyTo(System.Span)" IL_0095: ldloc.3 - IL_0096: ldloca.s V_6 + IL_0096: ldloca.s V_5 IL_0098: call "int System.Span.Length.get" IL_009d: add IL_009e: stloc.3 @@ -7961,21 +7967,24 @@ .locals init (int[] V_0, IL_00fb: ble.s IL_00c2 IL_00fd: ldloc.2 IL_00fe: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" - IL_0103: stloc.s V_7 - IL_0105: ldloca.s V_7 - IL_0107: ldloca.s V_5 - IL_0109: ldloc.3 - IL_010a: ldloca.s V_7 - IL_010c: call "int System.Span.Length.get" - IL_0111: call "System.Span System.Span.Slice(int, int)" - IL_0116: call "void System.Span.CopyTo(System.Span)" - IL_011b: ldloc.3 - IL_011c: ldloca.s V_7 - IL_011e: call "int System.Span.Length.get" - IL_0123: add - IL_0124: stloc.3 - IL_0125: ldloc.s V_4 - IL_0127: ret + IL_0103: stloc.s V_6 + IL_0105: ldloca.s V_6 + IL_0107: ldloc.s V_4 + IL_0109: newobj "System.Span..ctor(int[])" + IL_010e: stloc.s V_7 + IL_0110: ldloca.s V_7 + IL_0112: ldloc.3 + IL_0113: ldloca.s V_6 + IL_0115: call "int System.Span.Length.get" + IL_011a: call "System.Span System.Span.Slice(int, int)" + IL_011f: call "void System.Span.CopyTo(System.Span)" + IL_0124: ldloc.3 + IL_0125: ldloca.s V_6 + IL_0127: call "int System.Span.Length.get" + IL_012c: add + IL_012d: stloc.3 + IL_012e: ldloc.s V_4 + IL_0130: ret } """); } @@ -16409,11 +16418,13 @@ .locals init (T[] V_0, { // https://github.com/dotnet/roslyn/issues/71216 // Consider preferring AddRange over CopyTo for collection-expressions of List type + // TODO2: why do we have an extra store/load for the AddRange argument? verifier.VerifyIL("Program.F(T[])", """ { - // Code size 18 (0x12) + // Code size 20 (0x14) .maxstack 3 - .locals init (T[] V_0) + .locals init (T[] V_0, + T[] V_1) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldloc.0 @@ -16422,8 +16433,10 @@ .locals init (T[] V_0) IL_0005: newobj "System.Collections.Generic.List..ctor(int)" IL_000a: dup IL_000b: ldloc.0 - IL_000c: callvirt "void System.Collections.Generic.List.AddRange(System.Collections.Generic.IEnumerable)" - IL_0011: ret + IL_000c: stloc.1 + IL_000d: ldloc.1 + IL_000e: callvirt "void System.Collections.Generic.List.AddRange(System.Collections.Generic.IEnumerable)" + IL_0013: ret } """); } @@ -25864,7 +25877,7 @@ public void M() comp.VerifyEmitDiagnostics(); } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/70381")] + [Fact(Skip="TODO2 my mac is crashing here"), WorkItem("https://github.com/dotnet/roslyn/issues/70381")] public void ExtremelyNestedCollectionExpressionDoesNotOverflow_1() { var code = $$""" @@ -25901,7 +25914,7 @@ public void Add(MyCollection c) Assert.NotNull(model.GetOperation(collectionExpression)); } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/70381")] + [Fact(Skip="TODO2 my mac is crashing here"), WorkItem("https://github.com/dotnet/roslyn/issues/70381")] public void ExtremelyNestedCollectionExpressionDoesNotOverflow_2() { var code = $$""" @@ -26109,7 +26122,7 @@ static void Main() verifier.VerifyDiagnostics(); verifier.VerifyIL("C.Main", """ { - // Code size 153 (0x99) + // Code size 162 (0xa2) .maxstack 4 .locals init (System.ReadOnlySpan V_0, //li1 System.ReadOnlySpan V_1, //li2 @@ -26141,10 +26154,10 @@ .locals init (System.ReadOnlySpan V_0, //li1 IL_0039: add IL_003a: newarr "int" IL_003f: stloc.s V_5 - IL_0041: ldloca.s V_6 + IL_0041: ldloca.s V_2 IL_0043: ldloc.s V_5 - IL_0045: call "System.Span..ctor(int[])" - IL_004a: ldloca.s V_2 + IL_0045: newobj "System.Span..ctor(int[])" + IL_004a: stloc.s V_6 IL_004c: ldloca.s V_6 IL_004e: ldloc.s V_4 IL_0050: ldloca.s V_2 @@ -26157,21 +26170,24 @@ .locals init (System.ReadOnlySpan V_0, //li1 IL_006a: add IL_006b: stloc.s V_4 IL_006d: ldloca.s V_3 - IL_006f: ldloca.s V_6 - IL_0071: ldloc.s V_4 - IL_0073: ldloca.s V_3 - IL_0075: call "int System.ReadOnlySpan.Length.get" - IL_007a: call "System.Span System.Span.Slice(int, int)" - IL_007f: call "void System.ReadOnlySpan.CopyTo(System.Span)" - IL_0084: ldloc.s V_4 - IL_0086: ldloca.s V_3 - IL_0088: call "int System.ReadOnlySpan.Length.get" - IL_008d: add - IL_008e: stloc.s V_4 - IL_0090: ldloc.s V_5 - IL_0092: ldc.i4.0 - IL_0093: call "void CollectionExtensions.Report(object, bool)" - IL_0098: ret + IL_006f: ldloc.s V_5 + IL_0071: newobj "System.Span..ctor(int[])" + IL_0076: stloc.s V_6 + IL_0078: ldloca.s V_6 + IL_007a: ldloc.s V_4 + IL_007c: ldloca.s V_3 + IL_007e: call "int System.ReadOnlySpan.Length.get" + IL_0083: call "System.Span System.Span.Slice(int, int)" + IL_0088: call "void System.ReadOnlySpan.CopyTo(System.Span)" + IL_008d: ldloc.s V_4 + IL_008f: ldloca.s V_3 + IL_0091: call "int System.ReadOnlySpan.Length.get" + IL_0096: add + IL_0097: stloc.s V_4 + IL_0099: ldloc.s V_5 + IL_009b: ldc.i4.0 + IL_009c: call "void CollectionExtensions.Report(object, bool)" + IL_00a1: ret } """); } @@ -27076,7 +27092,34 @@ static void Main() var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: null /*IncludeExpectedOutput("[2, 1], [1, 2, 1, 2],")*/, targetFramework: TargetFramework.Net80, verify: Verification.Skipped); verifier.VerifyDiagnostics(); - verifier.VerifyIL("C.Main", """ """); + verifier.VerifyIL("C.Main", """ + { + // Code size 54 (0x36) + .maxstack 3 + .locals init (int[] V_0, //arr + System.Span V_1, //span + System.Span V_2) + 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: stloc.0 + IL_0012: ldloc.0 + IL_0013: ldc.i4.0 + IL_0014: call "void CollectionExtensions.Report(object, bool)" + IL_0019: ldloca.s V_1 + IL_001b: ldloc.0 + IL_001c: newobj "System.Span..ctor(int[])" + IL_0021: stloc.2 + IL_0022: ldloca.s V_2 + IL_0024: call "int[] System.Span.ToArray()" + IL_0029: call "System.Span..ctor(int[])" + IL_002e: ldloca.s V_1 + IL_0030: call "void CollectionExtensions.Report(in System.Span)" + IL_0035: ret + } + """); } // TODO2 add tests: From 36fb6088c2a4daf8652fb76e7a2d6841d47d266a Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Thu, 14 Dec 2023 21:43:23 -0800 Subject: [PATCH 11/18] Address more feedback --- .../LocalRewriter_CollectionExpression.cs | 2 +- .../Semantics/CollectionExpressionTests.cs | 600 ++++++++++++++---- 2 files changed, 483 insertions(+), 119 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs index 199e97a4316cf..842556944f936 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs @@ -964,7 +964,7 @@ private BoundExpression CreateAndPopulateList(BoundCollectionExpression node, Ty var useSiteInfo = GetNewCompoundUseSiteInfo(); var conversion = _compilation.Conversions.ClassifyConversionFromType(type, addRangeMethod.Parameters[0].Type, isChecked: false, ref useSiteInfo); _diagnostics.Add(rewrittenSpreadOperand.Syntax, useSiteInfo); - if (conversion.IsImplicit && conversion.IsReference) + if (conversion.IsIdentity || (conversion.IsImplicit && conversion.IsReference)) { conversion.MarkUnderlyingConversionsCheckedRecursive(); sideEffects.Add(_factory.Call(listTemp, addRangeMethod, MakeConversionNode(rewrittenSpreadOperand.Syntax, rewrittenSpreadOperand, conversion, type, @checked: false))); diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs index 4e728a2fbae34..74d7c0382bf51 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs @@ -6468,85 +6468,31 @@ static void F({{spreadType}} x) ("IEnumerable", "IEnumerable") => """ { - // Code size 62 (0x3e) - .maxstack 2 - .locals init (System.Collections.Generic.List V_0, - System.Collections.Generic.IEnumerator V_1, - int V_2) + // Code size 24 (0x18) + .maxstack 3 IL_0000: newobj "System.Collections.Generic.List..ctor()" - IL_0005: stloc.0 + IL_0005: dup IL_0006: ldarg.0 - IL_0007: callvirt "System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator()" - IL_000c: stloc.1 - .try - { - IL_000d: br.s IL_001d - IL_000f: ldloc.1 - IL_0010: callvirt "int System.Collections.Generic.IEnumerator.Current.get" - IL_0015: stloc.2 - IL_0016: ldloc.0 - IL_0017: ldloc.2 - IL_0018: callvirt "void System.Collections.Generic.List.Add(int)" - IL_001d: ldloc.1 - IL_001e: callvirt "bool System.Collections.IEnumerator.MoveNext()" - IL_0023: brtrue.s IL_000f - IL_0025: leave.s IL_0031 - } - finally - { - IL_0027: ldloc.1 - IL_0028: brfalse.s IL_0030 - IL_002a: ldloc.1 - IL_002b: callvirt "void System.IDisposable.Dispose()" - IL_0030: endfinally - } - IL_0031: ldloc.0 - IL_0032: newobj "<>z__ReadOnlyList..ctor(System.Collections.Generic.List)" - IL_0037: ldc.i4.0 - IL_0038: call "void CollectionExtensions.Report(object, bool)" - IL_003d: ret + IL_0007: callvirt "void System.Collections.Generic.List.AddRange(System.Collections.Generic.IEnumerable)" + IL_000c: newobj "<>z__ReadOnlyList..ctor(System.Collections.Generic.List)" + IL_0011: ldc.i4.0 + IL_0012: call "void CollectionExtensions.Report(object, bool)" + IL_0017: ret } """, ("IEnumerable", "int[]") => """ { - // Code size 62 (0x3e) - .maxstack 2 - .locals init (System.Collections.Generic.List V_0, - System.Collections.Generic.IEnumerator V_1, - int V_2) + // Code size 24 (0x18) + .maxstack 3 IL_0000: newobj "System.Collections.Generic.List..ctor()" - IL_0005: stloc.0 + IL_0005: dup IL_0006: ldarg.0 - IL_0007: callvirt "System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator()" - IL_000c: stloc.1 - .try - { - IL_000d: br.s IL_001d - IL_000f: ldloc.1 - IL_0010: callvirt "int System.Collections.Generic.IEnumerator.Current.get" - IL_0015: stloc.2 - IL_0016: ldloc.0 - IL_0017: ldloc.2 - IL_0018: callvirt "void System.Collections.Generic.List.Add(int)" - IL_001d: ldloc.1 - IL_001e: callvirt "bool System.Collections.IEnumerator.MoveNext()" - IL_0023: brtrue.s IL_000f - IL_0025: leave.s IL_0031 - } - finally - { - IL_0027: ldloc.1 - IL_0028: brfalse.s IL_0030 - IL_002a: ldloc.1 - IL_002b: callvirt "void System.IDisposable.Dispose()" - IL_0030: endfinally - } - IL_0031: ldloc.0 - IL_0032: callvirt "int[] System.Collections.Generic.List.ToArray()" - IL_0037: ldc.i4.0 - IL_0038: call "void CollectionExtensions.Report(object, bool)" - IL_003d: ret + IL_0007: callvirt "void System.Collections.Generic.List.AddRange(System.Collections.Generic.IEnumerable)" + IL_000c: callvirt "int[] System.Collections.Generic.List.ToArray()" + IL_0011: ldc.i4.0 + IL_0012: call "void CollectionExtensions.Report(object, bool)" + IL_0017: ret } """, ("int[]", "int[]") => @@ -7551,6 +7497,120 @@ .locals init (object[] V_0, """); } + [Fact] + public void SpreadElement_Dynamic_05() + { + var source = """ + using System.Collections.Generic; + + class Program + { + static void Main() + { + List dyns = [1,2,3]; + dyns.Report(); + object[] objs = [..dyns]; + objs.Report(); + } + } + """; + + var verifier = CompileAndVerify(new[] { source, s_collectionExtensions }, expectedOutput: IncludeExpectedOutput("[1, 2, 3], [1, 2, 3],"), targetFramework: TargetFramework.Net80); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.Main", """ + { + // Code size 158 (0x9e) + .maxstack 3 + .locals init (System.Span V_0, + int V_1, + object[] V_2, + System.Collections.Generic.List.Enumerator V_3, + object V_4) + IL_0000: newobj "System.Collections.Generic.List..ctor()" + IL_0005: dup + IL_0006: ldc.i4.3 + IL_0007: call "void System.Runtime.InteropServices.CollectionsMarshal.SetCount(System.Collections.Generic.List, int)" + IL_000c: dup + IL_000d: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_0012: stloc.0 + IL_0013: ldc.i4.0 + IL_0014: stloc.1 + IL_0015: ldloca.s V_0 + IL_0017: ldloc.1 + IL_0018: call "ref dynamic System.Span.this[int].get" + IL_001d: ldc.i4.1 + IL_001e: box "int" + IL_0023: stind.ref + IL_0024: ldloc.1 + IL_0025: ldc.i4.1 + IL_0026: add + IL_0027: stloc.1 + IL_0028: ldloca.s V_0 + IL_002a: ldloc.1 + IL_002b: call "ref dynamic System.Span.this[int].get" + IL_0030: ldc.i4.2 + IL_0031: box "int" + IL_0036: stind.ref + IL_0037: ldloc.1 + IL_0038: ldc.i4.1 + IL_0039: add + IL_003a: stloc.1 + IL_003b: ldloca.s V_0 + IL_003d: ldloc.1 + IL_003e: call "ref dynamic System.Span.this[int].get" + IL_0043: ldc.i4.3 + IL_0044: box "int" + IL_0049: stind.ref + IL_004a: ldloc.1 + IL_004b: ldc.i4.1 + IL_004c: add + IL_004d: stloc.1 + IL_004e: dup + IL_004f: ldc.i4.0 + IL_0050: call "void CollectionExtensions.Report(object, bool)" + IL_0055: ldc.i4.0 + IL_0056: stloc.1 + IL_0057: dup + IL_0058: callvirt "int System.Collections.Generic.List.Count.get" + IL_005d: newarr "object" + IL_0062: stloc.2 + IL_0063: callvirt "System.Collections.Generic.List.Enumerator System.Collections.Generic.List.GetEnumerator()" + IL_0068: stloc.3 + .try + { + IL_0069: br.s IL_007d + IL_006b: ldloca.s V_3 + IL_006d: call "dynamic System.Collections.Generic.List.Enumerator.Current.get" + IL_0072: stloc.s V_4 + IL_0074: ldloc.2 + IL_0075: ldloc.1 + IL_0076: ldloc.s V_4 + IL_0078: stelem.ref + IL_0079: ldloc.1 + IL_007a: ldc.i4.1 + IL_007b: add + IL_007c: stloc.1 + IL_007d: ldloca.s V_3 + IL_007f: call "bool System.Collections.Generic.List.Enumerator.MoveNext()" + IL_0084: brtrue.s IL_006b + IL_0086: leave.s IL_0096 + } + finally + { + IL_0088: ldloca.s V_3 + IL_008a: constrained. "System.Collections.Generic.List.Enumerator" + IL_0090: callvirt "void System.IDisposable.Dispose()" + IL_0095: endfinally + } + IL_0096: ldloc.2 + IL_0097: ldc.i4.0 + IL_0098: call "void CollectionExtensions.Report(object, bool)" + IL_009d: ret + } + """); + } + [Fact] public void SpreadElement_NullabilityDifference() { @@ -24395,50 +24455,26 @@ static void Main() verifier.VerifyDiagnostics(); verifier.VerifyIL("Program.Main", """ { - // Code size 93 (0x5d) + // Code size 57 (0x39) .maxstack 3 - .locals init (System.Collections.Generic.List V_0, - System.Collections.Generic.IEnumerator V_1, - int V_2) + .locals init (System.Collections.Generic.IEnumerable V_0) //arr 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: newobj "<>z__ReadOnlyArray..ctor(int[])" - IL_0016: newobj "System.Collections.Generic.List..ctor()" - IL_001b: stloc.0 - IL_001c: callvirt "System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator()" - IL_0021: stloc.1 - .try - { - IL_0022: br.s IL_0032 - IL_0024: ldloc.1 - IL_0025: callvirt "int System.Collections.Generic.IEnumerator.Current.get" - IL_002a: stloc.2 - IL_002b: ldloc.0 - IL_002c: ldloc.2 - IL_002d: callvirt "void System.Collections.Generic.List.Add(int)" - IL_0032: ldloc.1 - IL_0033: callvirt "bool System.Collections.IEnumerator.MoveNext()" - IL_0038: brtrue.s IL_0024 - IL_003a: leave.s IL_0046 - } - finally - { - IL_003c: ldloc.1 - IL_003d: brfalse.s IL_0045 - IL_003f: ldloc.1 - IL_0040: callvirt "void System.IDisposable.Dispose()" - IL_0045: endfinally - } - IL_0046: ldloc.0 - IL_0047: callvirt "int[] System.Collections.Generic.List.ToArray()" - IL_004c: call "System.Collections.Immutable.ImmutableArray System.Runtime.InteropServices.ImmutableCollectionsMarshal.AsImmutableArray(int[])" - IL_0051: box "System.Collections.Immutable.ImmutableArray" - IL_0056: ldc.i4.0 - IL_0057: call "void CollectionExtensions.Report(object, bool)" - IL_005c: ret + IL_0016: stloc.0 + IL_0017: newobj "System.Collections.Generic.List..ctor()" + IL_001c: dup + IL_001d: ldloc.0 + IL_001e: callvirt "void System.Collections.Generic.List.AddRange(System.Collections.Generic.IEnumerable)" + IL_0023: callvirt "int[] System.Collections.Generic.List.ToArray()" + IL_0028: call "System.Collections.Immutable.ImmutableArray System.Runtime.InteropServices.ImmutableCollectionsMarshal.AsImmutableArray(int[])" + IL_002d: box "System.Collections.Immutable.ImmutableArray" + IL_0032: ldc.i4.0 + IL_0033: call "void CollectionExtensions.Report(object, bool)" + IL_0038: ret } """); } @@ -25877,7 +25913,7 @@ public void M() comp.VerifyEmitDiagnostics(); } - [Fact(Skip="TODO2 my mac is crashing here"), WorkItem("https://github.com/dotnet/roslyn/issues/70381")] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/70381")] public void ExtremelyNestedCollectionExpressionDoesNotOverflow_1() { var code = $$""" @@ -25914,7 +25950,7 @@ public void Add(MyCollection c) Assert.NotNull(model.GetOperation(collectionExpression)); } - [Fact(Skip="TODO2 my mac is crashing here"), WorkItem("https://github.com/dotnet/roslyn/issues/70381")] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/70381")] public void ExtremelyNestedCollectionExpressionDoesNotOverflow_2() { var code = $$""" @@ -26950,11 +26986,6 @@ .locals init (int V_0, //i """); } - // TODO2: more test cases - // consider testing more combinations of runtime-equivalent source and dest element types - // test the dynamic case and/or highlight an existing test - // test targeting to span - // test targeting to IEnumerable/other interfaces [Fact] public void Spread_RuntimeEquivalentElement() { @@ -27056,7 +27087,7 @@ class C static async Task Main() { List items = [1]; - int[] items1 = [..await M2(), (await M2())[0]]; + int[] items1 = [..items, ..await M2(), (await M2())[0]]; items1.Report(); } @@ -27068,9 +27099,29 @@ static async Task M2() } """; - // TODO2: get rid of span temp to avoid async problems, update baselines. - var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: null /*IncludeExpectedOutput("[2, 1], [1, 2, 1, 2],")*/, targetFramework: TargetFramework.Net80, verify: Verification.Skipped); + var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: IncludeExpectedOutput("[1, 2, 2],"), targetFramework: TargetFramework.Net80, verify: Verification.Skipped); verifier.VerifyDiagnostics(); + verifier.VerifyIL("C.Main", """ + { + // Code size 47 (0x2f) + .maxstack 2 + .locals init (C.
d__0 V_0) + IL_0000: ldloca.s V_0 + IL_0002: call "System.Runtime.CompilerServices.AsyncTaskMethodBuilder System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Create()" + IL_0007: stfld "System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.
d__0.<>t__builder" + IL_000c: ldloca.s V_0 + IL_000e: ldc.i4.m1 + IL_000f: stfld "int C.
d__0.<>1__state" + IL_0014: ldloca.s V_0 + IL_0016: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.
d__0.<>t__builder" + IL_001b: ldloca.s V_0 + IL_001d: call "void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Startd__0>(ref C.
d__0)" + IL_0022: ldloca.s V_0 + IL_0024: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.
d__0.<>t__builder" + IL_0029: call "System.Threading.Tasks.Task System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Task.get" + IL_002e: ret + } + """); } [Fact] @@ -27090,7 +27141,7 @@ static void Main() } """; - var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: null /*IncludeExpectedOutput("[2, 1], [1, 2, 1, 2],")*/, targetFramework: TargetFramework.Net80, verify: Verification.Skipped); + var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: IncludeExpectedOutput("[1, 2, 3], [1, 2, 3],"), targetFramework: TargetFramework.Net80, verify: Verification.Skipped); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.Main", """ { @@ -27122,6 +27173,319 @@ .locals init (int[] V_0, //arr """); } + [Fact] + public void Spread_TargetIEnumerable() + { + var source = """ + using System.Collections.Generic; + class C + { + static void Main() + { + int[] arr = [1, 2, 3]; + arr.Report(); + IEnumerable enumerable = [..arr]; + enumerable.Report(); + } + } + """; + + var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: IncludeExpectedOutput("[1, 2, 3], [1, 2, 3],"), targetFramework: TargetFramework.Net80, verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C.Main", """ + { + // Code size 49 (0x31) + .maxstack 3 + .locals init (System.Span V_0) + 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: ldc.i4.0 + IL_0013: call "void CollectionExtensions.Report(object, bool)" + IL_0018: newobj "System.Span..ctor(int[])" + IL_001d: stloc.0 + IL_001e: ldloca.s V_0 + IL_0020: call "int[] System.Span.ToArray()" + IL_0025: newobj "<>z__ReadOnlyArray..ctor(int[])" + IL_002a: ldc.i4.0 + IL_002b: call "void CollectionExtensions.Report(object, bool)" + IL_0030: ret + } + """); + } + + [Fact] + public void Spread_TargetICollection() + { + var source = """ + using System.Collections.Generic; + class C + { + static void Main() + { + List list = [1, 2, 3]; + list.Report(); + ICollection icollection = [..list]; + icollection.Report(); + } + } + """; + + var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: IncludeExpectedOutput("[1, 2, 3], [1, 2, 3],"), targetFramework: TargetFramework.Net80, verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C.Main", """ + { + // Code size 143 (0x8f) + .maxstack 5 + .locals init (System.Span V_0, + int V_1, + System.Collections.Generic.List V_2, + System.Span V_3) + IL_0000: newobj "System.Collections.Generic.List..ctor()" + IL_0005: dup + IL_0006: ldc.i4.3 + IL_0007: call "void System.Runtime.InteropServices.CollectionsMarshal.SetCount(System.Collections.Generic.List, int)" + IL_000c: dup + IL_000d: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_0012: stloc.0 + IL_0013: ldc.i4.0 + IL_0014: stloc.1 + IL_0015: ldloca.s V_0 + IL_0017: ldloc.1 + IL_0018: call "ref int System.Span.this[int].get" + IL_001d: ldc.i4.1 + IL_001e: stind.i4 + IL_001f: ldloc.1 + IL_0020: ldc.i4.1 + IL_0021: add + IL_0022: stloc.1 + IL_0023: ldloca.s V_0 + IL_0025: ldloc.1 + IL_0026: call "ref int System.Span.this[int].get" + IL_002b: ldc.i4.2 + IL_002c: stind.i4 + IL_002d: ldloc.1 + IL_002e: ldc.i4.1 + IL_002f: add + IL_0030: stloc.1 + IL_0031: ldloca.s V_0 + IL_0033: ldloc.1 + IL_0034: call "ref int System.Span.this[int].get" + IL_0039: ldc.i4.3 + IL_003a: stind.i4 + IL_003b: ldloc.1 + IL_003c: ldc.i4.1 + IL_003d: add + IL_003e: stloc.1 + IL_003f: dup + IL_0040: ldc.i4.0 + IL_0041: call "void CollectionExtensions.Report(object, bool)" + IL_0046: stloc.2 + IL_0047: newobj "System.Collections.Generic.List..ctor()" + IL_004c: dup + IL_004d: ldloc.2 + IL_004e: callvirt "int System.Collections.Generic.List.Count.get" + IL_0053: call "void System.Runtime.InteropServices.CollectionsMarshal.SetCount(System.Collections.Generic.List, int)" + IL_0058: dup + IL_0059: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_005e: stloc.0 + IL_005f: ldc.i4.0 + IL_0060: stloc.1 + IL_0061: ldloc.2 + IL_0062: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_0067: stloc.3 + IL_0068: ldloca.s V_3 + IL_006a: ldloca.s V_0 + IL_006c: ldloc.1 + IL_006d: ldloca.s V_3 + IL_006f: call "int System.Span.Length.get" + IL_0074: call "System.Span System.Span.Slice(int, int)" + IL_0079: call "void System.Span.CopyTo(System.Span)" + IL_007e: ldloc.1 + IL_007f: ldloca.s V_3 + IL_0081: call "int System.Span.Length.get" + IL_0086: add + IL_0087: stloc.1 + IL_0088: ldc.i4.0 + IL_0089: call "void CollectionExtensions.Report(object, bool)" + IL_008e: ret + } + """); + } + + [Fact] + public void List_AddRange_IEnumerable() + { + var source = """ + using System.Collections.Generic; + class C + { + static void Main() + { + IEnumerable e = [1, 2, 3]; + e.Report(); + List list = [..e]; + list.Report(); + } + } + """; + + var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: IncludeExpectedOutput("[1, 2, 3], [1, 2, 3],"), targetFramework: TargetFramework.Net80, verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + + // https://github.com/dotnet/roslyn/issues/71273 + // It's strange that using a *less* specific type can result in *better* codegen when using a List target type. + verifier.VerifyIL("C.Main", """ + { + // Code size 49 (0x31) + .maxstack 3 + .locals init (System.Collections.Generic.IEnumerable V_0) //e + 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: newobj "<>z__ReadOnlyArray..ctor(int[])" + IL_0016: stloc.0 + IL_0017: ldloc.0 + IL_0018: ldc.i4.0 + IL_0019: call "void CollectionExtensions.Report(object, bool)" + IL_001e: newobj "System.Collections.Generic.List..ctor()" + IL_0023: dup + IL_0024: ldloc.0 + IL_0025: callvirt "void System.Collections.Generic.List.AddRange(System.Collections.Generic.IEnumerable)" + IL_002a: ldc.i4.0 + IL_002b: call "void CollectionExtensions.Report(object, bool)" + IL_0030: ret + } + """); + } + + [Fact] + public void List_AddRange_ICollection() + { + var source = """ + using System.Collections.Generic; + class C + { + static void Main() + { + ICollection e = [1, 2, 3]; + e.Report(); + List list = [..e]; + list.Report(); + } + } + """; + + var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: IncludeExpectedOutput("[1, 2, 3], [1, 2, 3],"), targetFramework: TargetFramework.Net80, verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + + // https://github.com/dotnet/roslyn/issues/71273 + // Ideally we'd like to be able to use *both* something like AddRange, *and* AsSpan/CopyTo/etc. while building the same target collection + verifier.VerifyIL("C.Main", """ + { + // Code size 163 (0xa3) + .maxstack 3 + .locals init (System.Span V_0, + int V_1, + System.Collections.Generic.ICollection V_2, + System.Collections.Generic.List V_3, + System.Collections.Generic.IEnumerator V_4, + int V_5) + IL_0000: newobj "System.Collections.Generic.List..ctor()" + IL_0005: dup + IL_0006: ldc.i4.3 + IL_0007: call "void System.Runtime.InteropServices.CollectionsMarshal.SetCount(System.Collections.Generic.List, int)" + IL_000c: dup + IL_000d: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_0012: stloc.0 + IL_0013: ldc.i4.0 + IL_0014: stloc.1 + IL_0015: ldloca.s V_0 + IL_0017: ldloc.1 + IL_0018: call "ref int System.Span.this[int].get" + IL_001d: ldc.i4.1 + IL_001e: stind.i4 + IL_001f: ldloc.1 + IL_0020: ldc.i4.1 + IL_0021: add + IL_0022: stloc.1 + IL_0023: ldloca.s V_0 + IL_0025: ldloc.1 + IL_0026: call "ref int System.Span.this[int].get" + IL_002b: ldc.i4.2 + IL_002c: stind.i4 + IL_002d: ldloc.1 + IL_002e: ldc.i4.1 + IL_002f: add + IL_0030: stloc.1 + IL_0031: ldloca.s V_0 + IL_0033: ldloc.1 + IL_0034: call "ref int System.Span.this[int].get" + IL_0039: ldc.i4.3 + IL_003a: stind.i4 + IL_003b: ldloc.1 + IL_003c: ldc.i4.1 + IL_003d: add + IL_003e: stloc.1 + IL_003f: dup + IL_0040: ldc.i4.0 + IL_0041: call "void CollectionExtensions.Report(object, bool)" + IL_0046: stloc.2 + IL_0047: newobj "System.Collections.Generic.List..ctor()" + IL_004c: stloc.3 + IL_004d: ldloc.3 + IL_004e: ldloc.2 + IL_004f: callvirt "int System.Collections.Generic.ICollection.Count.get" + IL_0054: call "void System.Runtime.InteropServices.CollectionsMarshal.SetCount(System.Collections.Generic.List, int)" + IL_0059: ldloc.3 + IL_005a: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_005f: stloc.0 + IL_0060: ldc.i4.0 + IL_0061: stloc.1 + IL_0062: ldloc.2 + IL_0063: callvirt "System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator()" + IL_0068: stloc.s V_4 + .try + { + IL_006a: br.s IL_0084 + IL_006c: ldloc.s V_4 + IL_006e: callvirt "int System.Collections.Generic.IEnumerator.Current.get" + IL_0073: stloc.s V_5 + IL_0075: ldloca.s V_0 + IL_0077: ldloc.1 + IL_0078: call "ref int System.Span.this[int].get" + IL_007d: ldloc.s V_5 + IL_007f: stind.i4 + IL_0080: ldloc.1 + IL_0081: ldc.i4.1 + IL_0082: add + IL_0083: stloc.1 + IL_0084: ldloc.s V_4 + IL_0086: callvirt "bool System.Collections.IEnumerator.MoveNext()" + IL_008b: brtrue.s IL_006c + IL_008d: leave.s IL_009b + } + finally + { + IL_008f: ldloc.s V_4 + IL_0091: brfalse.s IL_009a + IL_0093: ldloc.s V_4 + IL_0095: callvirt "void System.IDisposable.Dispose()" + IL_009a: endfinally + } + IL_009b: ldloc.3 + IL_009c: ldc.i4.0 + IL_009d: call "void CollectionExtensions.Report(object, bool)" + IL_00a2: ret + } + """); + } + // TODO2 add tests: // - List.AddRange where spread operand is an IEnumerable // - " " where spread operand is IEnumerable where U : T From dc7ef72f7e6217107bfa24516c1b20566651d36c Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Thu, 14 Dec 2023 22:09:17 -0800 Subject: [PATCH 12/18] More tests. Address TODOs. --- .../LocalRewriter_CollectionExpression.cs | 2 +- .../Semantics/CollectionExpressionTests.cs | 183 ++++++++++++++++-- 2 files changed, 171 insertions(+), 14 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs index 842556944f936..bb9c18679f410 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs @@ -967,7 +967,7 @@ private BoundExpression CreateAndPopulateList(BoundCollectionExpression node, Ty if (conversion.IsIdentity || (conversion.IsImplicit && conversion.IsReference)) { conversion.MarkUnderlyingConversionsCheckedRecursive(); - sideEffects.Add(_factory.Call(listTemp, addRangeMethod, MakeConversionNode(rewrittenSpreadOperand.Syntax, rewrittenSpreadOperand, conversion, type, @checked: false))); + sideEffects.Add(_factory.Call(listTemp, addRangeMethod, rewrittenSpreadOperand)); return true; } diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs index 74d7c0382bf51..6a4c99562fd84 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs @@ -16478,13 +16478,11 @@ .locals init (T[] V_0, { // https://github.com/dotnet/roslyn/issues/71216 // Consider preferring AddRange over CopyTo for collection-expressions of List type - // TODO2: why do we have an extra store/load for the AddRange argument? verifier.VerifyIL("Program.F(T[])", """ { - // Code size 20 (0x14) + // Code size 18 (0x12) .maxstack 3 - .locals init (T[] V_0, - T[] V_1) + .locals init (T[] V_0) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldloc.0 @@ -16493,10 +16491,8 @@ .locals init (T[] V_0, IL_0005: newobj "System.Collections.Generic.List..ctor(int)" IL_000a: dup IL_000b: ldloc.0 - IL_000c: stloc.1 - IL_000d: ldloc.1 - IL_000e: callvirt "void System.Collections.Generic.List.AddRange(System.Collections.Generic.IEnumerable)" - IL_0013: ret + IL_000c: callvirt "void System.Collections.Generic.List.AddRange(System.Collections.Generic.IEnumerable)" + IL_0011: ret } """); } @@ -27364,6 +27360,172 @@ .locals init (System.Collections.Generic.IEnumerable V_0) //e """); } + [Fact] + public void List_AddRange_IEnumerable_Covariant() + { + var source = """ + using System.Collections.Generic; + class C + { + static void Main() + { + IEnumerable e = ["a", "b", "c"]; + e.Report(); + List list = [..e]; + list.Report(); + } + } + """; + + var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: IncludeExpectedOutput("[a, b, c], [a, b, c],"), targetFramework: TargetFramework.Net80, verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("C.Main", """ + { + // Code size 62 (0x3e) + .maxstack 4 + .locals init (System.Collections.Generic.IEnumerable V_0) //e + IL_0000: ldc.i4.3 + IL_0001: newarr "string" + IL_0006: dup + IL_0007: ldc.i4.0 + IL_0008: ldstr "a" + IL_000d: stelem.ref + IL_000e: dup + IL_000f: ldc.i4.1 + IL_0010: ldstr "b" + IL_0015: stelem.ref + IL_0016: dup + IL_0017: ldc.i4.2 + IL_0018: ldstr "c" + IL_001d: stelem.ref + IL_001e: newobj "<>z__ReadOnlyArray..ctor(string[])" + IL_0023: stloc.0 + IL_0024: ldloc.0 + IL_0025: ldc.i4.0 + IL_0026: call "void CollectionExtensions.Report(object, bool)" + IL_002b: newobj "System.Collections.Generic.List..ctor()" + IL_0030: dup + IL_0031: ldloc.0 + IL_0032: callvirt "void System.Collections.Generic.List.AddRange(System.Collections.Generic.IEnumerable)" + IL_0037: ldc.i4.0 + IL_0038: call "void CollectionExtensions.Report(object, bool)" + IL_003d: ret + } + """); + } + + [Theory] + [InlineData("where T : IEnumerable")] + [InlineData("where T : struct, IEnumerable")] + public void List_AddRange_IEnumerable_Constraint(string constraints) + { + var source = $$""" + using System.Collections.Generic; + using System.Collections.Immutable; + class C + { + static void Main() + { + ImmutableArray e = ["a", "b", "c"]; + e.Report(); + M, string>(e); + } + + static void M(T t) {{constraints}} + { + List list = [..t]; + list.Report(); + } + } + """; + + var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: IncludeExpectedOutput("[a, b, c], [a, b, c],"), targetFramework: TargetFramework.Net80, verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("C.M", """ + { + // Code size 64 (0x40) + .maxstack 2 + .locals init (System.Collections.Generic.List V_0, + System.Collections.Generic.IEnumerator V_1, + U V_2) + IL_0000: newobj "System.Collections.Generic.List..ctor()" + IL_0005: stloc.0 + IL_0006: ldarga.s V_0 + IL_0008: constrained. "T" + IL_000e: callvirt "System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator()" + IL_0013: stloc.1 + .try + { + IL_0014: br.s IL_0024 + IL_0016: ldloc.1 + IL_0017: callvirt "U System.Collections.Generic.IEnumerator.Current.get" + IL_001c: stloc.2 + IL_001d: ldloc.0 + IL_001e: ldloc.2 + IL_001f: callvirt "void System.Collections.Generic.List.Add(U)" + IL_0024: ldloc.1 + IL_0025: callvirt "bool System.Collections.IEnumerator.MoveNext()" + IL_002a: brtrue.s IL_0016 + IL_002c: leave.s IL_0038 + } + finally + { + IL_002e: ldloc.1 + IL_002f: brfalse.s IL_0037 + IL_0031: ldloc.1 + IL_0032: callvirt "void System.IDisposable.Dispose()" + IL_0037: endfinally + } + IL_0038: ldloc.0 + IL_0039: ldc.i4.0 + IL_003a: call "void CollectionExtensions.Report(object, bool)" + IL_003f: ret + } + """); + } + + [Fact] + public void List_AddRange_IEnumerable_ClassConstraint() + { + var source = """ + using System.Collections.Generic; + class C + { + static void Main() + { + IEnumerable e = ["a", "b", "c"]; + e.Report(); + M, string>(e); + } + + static void M(T t) where T : class, IEnumerable + { + List list = [..t]; + list.Report(); + } + } + """; + + var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: IncludeExpectedOutput("[a, b, c], [a, b, c],"), targetFramework: TargetFramework.Net80, verify: Verification.Skipped); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("C.M", """ + { + // Code size 19 (0x13) + .maxstack 3 + IL_0000: newobj "System.Collections.Generic.List..ctor()" + IL_0005: dup + IL_0006: ldarg.0 + IL_0007: callvirt "void System.Collections.Generic.List.AddRange(System.Collections.Generic.IEnumerable)" + IL_000c: ldc.i4.0 + IL_000d: call "void CollectionExtensions.Report(object, bool)" + IL_0012: ret + } + """); + } + [Fact] public void List_AddRange_ICollection() { @@ -27485,10 +27647,5 @@ .locals init (System.Span V_0, } """); } - - // TODO2 add tests: - // - List.AddRange where spread operand is an IEnumerable - // - " " where spread operand is IEnumerable where U : T - // - " " where spread operand is struct : IEnumerable } } From c93244e76edbb34a866f512ad4a1bb6d5e01eb62 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Thu, 14 Dec 2023 22:10:06 -0800 Subject: [PATCH 13/18] Fix raw string indenting --- .../Semantics/CollectionExpressionTests.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs index 6a4c99562fd84..a6b20e6f5e10a 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs @@ -27512,17 +27512,17 @@ static void M(T t) where T : class, IEnumerable verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M", """ - { - // Code size 19 (0x13) - .maxstack 3 - IL_0000: newobj "System.Collections.Generic.List..ctor()" - IL_0005: dup - IL_0006: ldarg.0 - IL_0007: callvirt "void System.Collections.Generic.List.AddRange(System.Collections.Generic.IEnumerable)" - IL_000c: ldc.i4.0 - IL_000d: call "void CollectionExtensions.Report(object, bool)" - IL_0012: ret - } + { + // Code size 19 (0x13) + .maxstack 3 + IL_0000: newobj "System.Collections.Generic.List..ctor()" + IL_0005: dup + IL_0006: ldarg.0 + IL_0007: callvirt "void System.Collections.Generic.List.AddRange(System.Collections.Generic.IEnumerable)" + IL_000c: ldc.i4.0 + IL_000d: call "void CollectionExtensions.Report(object, bool)" + IL_0012: ret + } """); } From cce39d03d29b8749244cfd58f647e420eaec5380 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Thu, 14 Dec 2023 23:31:07 -0800 Subject: [PATCH 14/18] Skip verification on a few more tests --- .../Test/Emit2/Semantics/CollectionExpressionTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs index a6b20e6f5e10a..d4606b22a54df 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs @@ -7432,7 +7432,7 @@ static void Main() } """; - var verifier = CompileAndVerify(new[] { source, s_collectionExtensions }, expectedOutput: IncludeExpectedOutput("[1, 2, 3], [1, 2, 3],"), targetFramework: TargetFramework.Net80); + var verifier = CompileAndVerify(new[] { source, s_collectionExtensions }, expectedOutput: IncludeExpectedOutput("[1, 2, 3], [1, 2, 3],"), targetFramework: TargetFramework.Net80, verify: Verification.Skipped); verifier.VerifyDiagnostics(); verifier.VerifyIL("Program.Main", """ @@ -7515,7 +7515,7 @@ static void Main() } """; - var verifier = CompileAndVerify(new[] { source, s_collectionExtensions }, expectedOutput: IncludeExpectedOutput("[1, 2, 3], [1, 2, 3],"), targetFramework: TargetFramework.Net80); + var verifier = CompileAndVerify(new[] { source, s_collectionExtensions }, expectedOutput: IncludeExpectedOutput("[1, 2, 3], [1, 2, 3],"), targetFramework: TargetFramework.Net80, verify: Verification.Skipped); verifier.VerifyDiagnostics(); verifier.VerifyIL("Program.Main", """ @@ -7630,7 +7630,7 @@ static void Main() } """; - var verifier = CompileAndVerify(new[] { source, s_collectionExtensions }, expectedOutput: IncludeExpectedOutput("[1, 2, 3], [1, 2, 3],"), targetFramework: TargetFramework.Net80); + var verifier = CompileAndVerify(new[] { source, s_collectionExtensions }, expectedOutput: IncludeExpectedOutput("[1, 2, 3], [1, 2, 3],"), targetFramework: TargetFramework.Net80, verify: Verification.Skipped); verifier.VerifyDiagnostics(); verifier.VerifyIL("Program.Main", """ { @@ -27008,7 +27008,7 @@ nint[] M3(List list) } """; - var verifier = CompileAndVerify(source, targetFramework: TargetFramework.Net80); + var verifier = CompileAndVerify(source, targetFramework: TargetFramework.Net80, verify: Verification.Skipped); verifier.VerifyDiagnostics(); verifier.VerifyIL("C.M1", """ { From 8625ac3670d9f9282fbac724a049150494887c8f Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Fri, 15 Dec 2023 15:01:20 -0800 Subject: [PATCH 15/18] Address feedback --- .../LocalRewriter_CollectionExpression.cs | 79 +-- .../Semantics/CollectionExpressionTests.cs | 457 +++++++++++++++++- 2 files changed, 481 insertions(+), 55 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs index bb9c18679f410..04a2a3adc4e75 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs @@ -528,7 +528,7 @@ private BoundExpression CreateAndPopulateArray(BoundCollectionExpression node, A && expressionStatement.Expression is not BoundConversion) { var spreadTypeOriginalDefinition = spreadExpression.Type!.OriginalDefinition; - if (tryGetToArrayMethod(spreadTypeOriginalDefinition, WellKnownType.System_Collections_Generic_List_T, WellKnownMember.System_Collections_Generic_List_T__ToArray) is { } listToArrayMethod) + if (tryGetToArrayMethod(spreadTypeOriginalDefinition, WellKnownType.System_Collections_Generic_List_T, WellKnownMember.System_Collections_Generic_List_T__ToArray, out MethodSymbol? listToArrayMethod)) { var rewrittenSpreadExpression = VisitExpression(spreadExpression); return _factory.Call(rewrittenSpreadExpression, listToArrayMethod.AsMember((NamedTypeSymbol)spreadExpression.Type!)); @@ -537,23 +537,24 @@ private BoundExpression CreateAndPopulateArray(BoundCollectionExpression node, A if (TryGetSpanConversion(spreadExpression.Type, out var asSpanMethod)) { var spanType = CallAsSpanMethod(spreadExpression, asSpanMethod).Type!.OriginalDefinition; - if ((tryGetToArrayMethod(spanType, WellKnownType.System_Span_T, WellKnownMember.System_Span_T__ToArray) - ?? tryGetToArrayMethod(spanType, WellKnownType.System_ReadOnlySpan_T, WellKnownMember.System_ReadOnlySpan_T__ToArray)) - is { } toArrayMethod) + if (tryGetToArrayMethod(spanType, WellKnownType.System_Span_T, WellKnownMember.System_Span_T__ToArray, out var toArrayMethod) + || tryGetToArrayMethod(spanType, WellKnownType.System_ReadOnlySpan_T, WellKnownMember.System_ReadOnlySpan_T__ToArray, out toArrayMethod)) { var rewrittenSpreadExpression = CallAsSpanMethod(VisitExpression(spreadExpression), asSpanMethod); return _factory.Call(rewrittenSpreadExpression, toArrayMethod.AsMember((NamedTypeSymbol)rewrittenSpreadExpression.Type!)); } } - MethodSymbol? tryGetToArrayMethod(TypeSymbol spreadTypeOriginalDefinition, WellKnownType wellKnownType, WellKnownMember wellKnownMember) + bool tryGetToArrayMethod(TypeSymbol spreadTypeOriginalDefinition, WellKnownType wellKnownType, WellKnownMember wellKnownMember, [NotNullWhen(true)] out MethodSymbol? toArrayMethod) { if (TypeSymbol.Equals(spreadTypeOriginalDefinition, this._compilation.GetWellKnownType(wellKnownType), TypeCompareKind.AllIgnoreOptions)) { - return _factory.WellKnownMethod(wellKnownMember, isOptional: true); + toArrayMethod = _factory.WellKnownMethod(wellKnownMember, isOptional: true); + return toArrayMethod is { }; } - return null; + toArrayMethod = null; + return false; } } @@ -637,15 +638,14 @@ private BoundExpression CreateAndPopulateArray(BoundCollectionExpression node, A isRef: false, indexTemp.Type)); }, - (sideEffects, arrayTemp, spreadElement, rewrittenSpreadOperand) => + (ArrayBuilder sideEffects, BoundExpression arrayTemp, BoundCollectionExpressionSpreadElement spreadElement, BoundExpression rewrittenSpreadOperand) => { - if (TryPrepareCopyToOptimization(spreadElement, rewrittenSpreadOperand) is not var (spanSliceMethod, spreadElementAsSpan, getLengthMethod, copyToMethod)) + 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. - var targetSpan = TryConvertToSpanOrReadOnlySpan(arrayTemp); - if (targetSpan is null) + if (!TryConvertToSpanOrReadOnlySpan(arrayTemp, out var targetSpan)) return false; PerformCopyToOptimization(sideEffects, localsBuilder, indexTemp, targetSpan, rewrittenSpreadOperand, spanSliceMethod, spreadElementAsSpan, getLengthMethod, copyToMethod); @@ -669,34 +669,37 @@ private BoundExpression CreateAndPopulateArray(BoundCollectionExpression node, A /// private bool TryGetSpanConversion(TypeSymbol type, out MethodSymbol? asSpanMethod) { - if (type is NamedTypeSymbol spanType - && (spanType.OriginalDefinition.Equals(_compilation.GetWellKnownType(WellKnownType.System_Span_T), TypeCompareKind.ConsiderEverything) - || spanType.OriginalDefinition.Equals(_compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T), TypeCompareKind.ConsiderEverything))) + if (type is ArrayTypeSymbol { IsSZArray: true } arrayType + && _factory.WellKnownMethod(WellKnownMember.System_Span_T__ctor_Array, isOptional: true) is { } spanCtorArray) { - asSpanMethod = null; + asSpanMethod = spanCtorArray.AsMember(spanCtorArray.ContainingType.Construct(arrayType.ElementType)); return true; } - if (type is ArrayTypeSymbol { IsSZArray: true } arrayType - && _factory.WellKnownMethod(WellKnownMember.System_Span_T__ctor_Array, isOptional: true) is { } spanCtorArray) + if (type is not NamedTypeSymbol namedType) { - asSpanMethod = spanCtorArray.AsMember(spanCtorArray.ContainingType.Construct(arrayType.ElementType)); + asSpanMethod = null; + return false; + } + + if (namedType.OriginalDefinition.Equals(_compilation.GetWellKnownType(WellKnownType.System_Span_T), TypeCompareKind.ConsiderEverything) + || namedType.OriginalDefinition.Equals(_compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T), TypeCompareKind.ConsiderEverything)) + { + asSpanMethod = null; return true; } - if (type is NamedTypeSymbol immutableArrayType - && immutableArrayType.OriginalDefinition.Equals(_compilation.GetWellKnownType(WellKnownType.System_Collections_Immutable_ImmutableArray_T), TypeCompareKind.ConsiderEverything) + if (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(immutableArrayType); + asSpanMethod = immutableArrayAsSpanMethod.AsMember(namedType); return true; } - if (type is NamedTypeSymbol listType - && listType.OriginalDefinition.Equals(_compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_List_T), TypeCompareKind.ConsiderEverything) + 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(listType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type); + asSpanMethod = collectionsMarshalAsSpanMethod.Construct(namedType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type); return true; } @@ -704,17 +707,19 @@ private bool TryGetSpanConversion(TypeSymbol type, out MethodSymbol? asSpanMetho return false; } - private BoundExpression? TryConvertToSpanOrReadOnlySpan(BoundExpression expression) + private bool TryConvertToSpanOrReadOnlySpan(BoundExpression expression, [NotNullWhen(true)] out BoundExpression? span) { var type = expression.Type; Debug.Assert(type is not null); if (!TryGetSpanConversion(type, out var asSpanMethod)) { - return null; + span = null; + return false; } - return CallAsSpanMethod(expression, asSpanMethod); + span = CallAsSpanMethod(expression, asSpanMethod); + return true; } private BoundExpression CallAsSpanMethod(BoundExpression spreadExpression, MethodSymbol? asSpanMethod) @@ -741,7 +746,7 @@ private BoundExpression CallAsSpanMethod(BoundExpression spreadExpression, Metho /// Verifies presence of methods necessary for the CopyTo optimization /// without performing mutating actions e.g. appending to side effects or locals builders. /// - private (MethodSymbol spanSliceMethod, BoundExpression spreadElementAsSpan, MethodSymbol getLengthMethod, MethodSymbol copyToMethod)? TryPrepareCopyToOptimization( + private (MethodSymbol spanSliceMethod, BoundExpression spreadElementAsSpan, MethodSymbol getLengthMethod, MethodSymbol copyToMethod)? PrepareCopyToOptimization( BoundCollectionExpressionSpreadElement spreadElement, BoundExpression rewrittenSpreadOperand) { @@ -753,11 +758,11 @@ private BoundExpression CallAsSpanMethod(BoundExpression spreadExpression, Metho if (_factory.WellKnownMethod(WellKnownMember.System_Span_T__Slice_Int_Int, isOptional: true) is not { } spanSliceMethod) return null; - if (TryConvertToSpanOrReadOnlySpan(rewrittenSpreadOperand) is not { } spreadOperandAsSpan) + if (!TryConvertToSpanOrReadOnlySpan(rewrittenSpreadOperand, out var spreadOperandAsSpan)) return null; - if ((tryGetSpanMethodsForSpread(WellKnownType.System_ReadOnlySpan_T, WellKnownMember.System_ReadOnlySpan_T__get_Length, WellKnownMember.System_ReadOnlySpan_T__CopyTo_Span_T) - ?? tryGetSpanMethodsForSpread(WellKnownType.System_Span_T, WellKnownMember.System_Span_T__get_Length, WellKnownMember.System_Span_T__CopyTo_Span_T)) + 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; @@ -766,7 +771,7 @@ is not (var getLengthMethod, var copyToMethod)) return (spanSliceMethod, spreadOperandAsSpan, getLengthMethod, copyToMethod); // gets either Span or ReadOnlySpan methods for operating on the source spread element. - (MethodSymbol getLengthMethod, MethodSymbol copyToMethod)? tryGetSpanMethodsForSpread( + (MethodSymbol getLengthMethod, MethodSymbol copyToMethod)? getSpanMethodsForSpread( WellKnownType wellKnownSpanType, WellKnownMember getLengthMember, WellKnownMember copyToMember) @@ -775,7 +780,7 @@ is not (var getLengthMethod, var copyToMethod)) && _factory.WellKnownMethod(getLengthMember, isOptional: true) is { } getLengthMethod && _factory.WellKnownMethod(copyToMember, isOptional: true) is { } copyToMethod) { - return (getLengthMethod!, copyToMethod!); + return (getLengthMethod, copyToMethod); } return null; @@ -929,9 +934,9 @@ private BoundExpression CreateAndPopulateList(BoundCollectionExpression node, Ty isRef: false, indexTemp.Type)); }, - (sideEffects, spanTemp, spreadElement, rewrittenSpreadOperand) => + (ArrayBuilder sideEffects, BoundExpression spanTemp, BoundCollectionExpressionSpreadElement spreadElement, BoundExpression rewrittenSpreadOperand) => { - if (TryPrepareCopyToOptimization(spreadElement, rewrittenSpreadOperand) is not var (spanSliceMethod, spreadElementAsSpan, getLengthMethod, copyToMethod)) + if (PrepareCopyToOptimization(spreadElement, rewrittenSpreadOperand) is not var (spanSliceMethod, spreadElementAsSpan, getLengthMethod, copyToMethod)) return false; PerformCopyToOptimization(sideEffects, localsBuilder, indexTemp, spanTemp, rewrittenSpreadOperand, spanSliceMethod, spreadElementAsSpan, getLengthMethod, copyToMethod); @@ -954,7 +959,7 @@ private BoundExpression CreateAndPopulateList(BoundCollectionExpression node, Ty expressions.Add( _factory.Call(listTemp, addMethod, rewrittenValue)); }, - (sideEffects, listTemp, spreadElement, rewrittenSpreadOperand) => + (ArrayBuilder sideEffects, BoundExpression listTemp, BoundCollectionExpressionSpreadElement spreadElement, BoundExpression rewrittenSpreadOperand) => { if (addRangeMethod is null) return false; @@ -1028,7 +1033,7 @@ private void AddCollectionExpressionElements( if (element is BoundCollectionExpressionSpreadElement spreadElement) { - if (tryOptimizeSpreadElement.Invoke(sideEffects, rewrittenReceiver, spreadElement, rewrittenExpression)) + if (tryOptimizeSpreadElement(sideEffects, rewrittenReceiver, spreadElement, rewrittenExpression)) continue; var rewrittenElement = MakeCollectionExpressionSpreadElement( diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs index d4606b22a54df..9890776e3a89d 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs @@ -4842,6 +4842,230 @@ unsafe static void Main() CompileAndVerify(new[] { source, s_collectionExtensions }, options: TestOptions.UnsafeReleaseExe, verify: Verification.Skipped, expectedOutput: "[0, 2], "); } + [Fact] + public void PointerType_06() + { + string source = """ + using System; + + unsafe class Program + { + static void Main() + { + int*[] arr = [null]; + Console.Write(arr.Length); + Console.Write((nint)arr[0]); + int*[] arr1 = [..arr]; + Console.Write(arr1.Length); + Console.Write((nint)arr1[0]); + } + } + """; + var verifier = CompileAndVerify(new[] { source, s_collectionExtensions }, options: TestOptions.UnsafeReleaseExe, verify: Verification.Skipped, expectedOutput: "1010"); + verifier.VerifyIL("Program.Main", """ + { + // Code size 86 (0x56) + .maxstack 4 + .locals init (int V_0, + int*[] V_1, + int*[] V_2, + int V_3, + int* V_4) + IL_0000: ldc.i4.1 + IL_0001: newarr "int*" + IL_0006: dup + IL_0007: ldc.i4.0 + IL_0008: ldc.i4.0 + IL_0009: conv.u + IL_000a: stelem.i + IL_000b: dup + IL_000c: ldlen + IL_000d: conv.i4 + IL_000e: call "void System.Console.Write(int)" + IL_0013: dup + IL_0014: ldc.i4.0 + IL_0015: ldelem.i + IL_0016: conv.i8 + IL_0017: call "void System.Console.Write(long)" + IL_001c: ldc.i4.0 + IL_001d: stloc.0 + IL_001e: dup + IL_001f: ldlen + IL_0020: conv.i4 + IL_0021: newarr "int*" + IL_0026: stloc.1 + IL_0027: stloc.2 + IL_0028: ldc.i4.0 + IL_0029: stloc.3 + IL_002a: br.s IL_003e + IL_002c: ldloc.2 + IL_002d: ldloc.3 + IL_002e: ldelem.i + IL_002f: stloc.s V_4 + IL_0031: ldloc.1 + IL_0032: ldloc.0 + IL_0033: ldloc.s V_4 + IL_0035: stelem.i + IL_0036: ldloc.0 + IL_0037: ldc.i4.1 + IL_0038: add + IL_0039: stloc.0 + IL_003a: ldloc.3 + IL_003b: ldc.i4.1 + IL_003c: add + IL_003d: stloc.3 + IL_003e: ldloc.3 + IL_003f: ldloc.2 + IL_0040: ldlen + IL_0041: conv.i4 + IL_0042: blt.s IL_002c + IL_0044: ldloc.1 + IL_0045: dup + IL_0046: ldlen + IL_0047: conv.i4 + IL_0048: call "void System.Console.Write(int)" + IL_004d: ldc.i4.0 + IL_004e: ldelem.i + IL_004f: conv.i8 + IL_0050: call "void System.Console.Write(long)" + IL_0055: ret + } + """); + } + + [Fact] + public void PointerType_07() + { + string source = """ + using System; + + unsafe class Program + { + static void Main() + { + int*[] arr = [null]; + Console.Write(arr.Length); + Console.Write((nint)arr[0]); + int*[] arr1 = [..arr, ..arr]; + Console.Write(arr1.Length); + Console.Write((nint)arr1[0]); + Console.Write((nint)arr1[1]); + } + } + """; + var verifier = CompileAndVerify(new[] { source, s_collectionExtensions }, options: TestOptions.UnsafeReleaseExe, verify: Verification.Skipped, expectedOutput: "10200"); + verifier.VerifyIL("Program.Main", """ + { + // Code size 149 (0x95) + .maxstack 4 + .locals init (int*[] V_0, + int*[] V_1, + int V_2, + int*[] V_3, + int*[] V_4, + int V_5, + int* V_6) + IL_0000: ldc.i4.1 + IL_0001: newarr "int*" + IL_0006: dup + IL_0007: ldc.i4.0 + IL_0008: ldc.i4.0 + IL_0009: conv.u + IL_000a: stelem.i + IL_000b: dup + IL_000c: ldlen + IL_000d: conv.i4 + IL_000e: call "void System.Console.Write(int)" + IL_0013: dup + IL_0014: ldc.i4.0 + IL_0015: ldelem.i + IL_0016: conv.i8 + IL_0017: call "void System.Console.Write(long)" + IL_001c: dup + IL_001d: stloc.0 + IL_001e: stloc.1 + IL_001f: ldc.i4.0 + IL_0020: stloc.2 + IL_0021: ldloc.0 + IL_0022: ldlen + IL_0023: conv.i4 + IL_0024: ldloc.1 + IL_0025: ldlen + IL_0026: conv.i4 + IL_0027: add + IL_0028: newarr "int*" + IL_002d: stloc.3 + IL_002e: ldloc.0 + IL_002f: stloc.s V_4 + IL_0031: ldc.i4.0 + IL_0032: stloc.s V_5 + IL_0034: br.s IL_004c + IL_0036: ldloc.s V_4 + IL_0038: ldloc.s V_5 + IL_003a: ldelem.i + IL_003b: stloc.s V_6 + IL_003d: ldloc.3 + IL_003e: ldloc.2 + IL_003f: ldloc.s V_6 + IL_0041: stelem.i + IL_0042: ldloc.2 + IL_0043: ldc.i4.1 + IL_0044: add + IL_0045: stloc.2 + IL_0046: ldloc.s V_5 + IL_0048: ldc.i4.1 + IL_0049: add + IL_004a: stloc.s V_5 + IL_004c: ldloc.s V_5 + IL_004e: ldloc.s V_4 + IL_0050: ldlen + IL_0051: conv.i4 + IL_0052: blt.s IL_0036 + IL_0054: ldloc.1 + IL_0055: stloc.s V_4 + IL_0057: ldc.i4.0 + IL_0058: stloc.s V_5 + IL_005a: br.s IL_0072 + IL_005c: ldloc.s V_4 + IL_005e: ldloc.s V_5 + IL_0060: ldelem.i + IL_0061: stloc.s V_6 + IL_0063: ldloc.3 + IL_0064: ldloc.2 + IL_0065: ldloc.s V_6 + IL_0067: stelem.i + IL_0068: ldloc.2 + IL_0069: ldc.i4.1 + IL_006a: add + IL_006b: stloc.2 + IL_006c: ldloc.s V_5 + IL_006e: ldc.i4.1 + IL_006f: add + IL_0070: stloc.s V_5 + IL_0072: ldloc.s V_5 + IL_0074: ldloc.s V_4 + IL_0076: ldlen + IL_0077: conv.i4 + IL_0078: blt.s IL_005c + IL_007a: ldloc.3 + IL_007b: dup + IL_007c: ldlen + IL_007d: conv.i4 + IL_007e: call "void System.Console.Write(int)" + IL_0083: dup + IL_0084: ldc.i4.0 + IL_0085: ldelem.i + IL_0086: conv.i8 + IL_0087: call "void System.Console.Write(long)" + IL_008c: ldc.i4.1 + IL_008d: ldelem.i + IL_008e: conv.i8 + IL_008f: call "void System.Console.Write(long)" + IL_0094: ret + } + """); + } + [Fact] public void CollectionInitializerType_01() { @@ -27097,25 +27321,222 @@ static async Task M2() var verifier = CompileAndVerify(new[] { source, s_collectionExtensionsWithSpan }, expectedOutput: IncludeExpectedOutput("[1, 2, 2],"), targetFramework: TargetFramework.Net80, verify: Verification.Skipped); verifier.VerifyDiagnostics(); - verifier.VerifyIL("C.Main", """ + verifier.VerifyIL("C.
d__0.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", """ { - // Code size 47 (0x2f) - .maxstack 2 - .locals init (C.
d__0 V_0) - IL_0000: ldloca.s V_0 - IL_0002: call "System.Runtime.CompilerServices.AsyncTaskMethodBuilder System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Create()" - IL_0007: stfld "System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.
d__0.<>t__builder" - IL_000c: ldloca.s V_0 - IL_000e: ldc.i4.m1 - IL_000f: stfld "int C.
d__0.<>1__state" - IL_0014: ldloca.s V_0 - IL_0016: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.
d__0.<>t__builder" - IL_001b: ldloca.s V_0 - IL_001d: call "void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Startd__0>(ref C.
d__0)" - IL_0022: ldloca.s V_0 - IL_0024: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.
d__0.<>t__builder" - IL_0029: call "System.Threading.Tasks.Task System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Task.get" - IL_002e: ret + // Code size 536 (0x218) + .maxstack 4 + .locals init (int V_0, + System.Collections.Generic.List V_1, //items + int[] V_2, + int[] V_3, + System.Span V_4, + System.Span V_5, + System.Runtime.CompilerServices.TaskAwaiter V_6, + System.Span V_7, + System.Exception V_8) + IL_0000: ldarg.0 + IL_0001: ldfld "int C.
d__0.<>1__state" + IL_0006: stloc.0 + .try + { + IL_0007: ldloc.0 + IL_0008: brfalse.s IL_005f + IL_000a: ldloc.0 + IL_000b: ldc.i4.1 + IL_000c: beq IL_0185 + IL_0011: ldc.i4.1 + IL_0012: newobj "System.Collections.Generic.List..ctor(int)" + IL_0017: dup + IL_0018: ldc.i4.1 + IL_0019: callvirt "void System.Collections.Generic.List.Add(int)" + IL_001e: stloc.1 + IL_001f: ldarg.0 + IL_0020: ldloc.1 + IL_0021: stfld "System.Collections.Generic.List C.
d__0.<>7__wrap3" + IL_0026: call "System.Threading.Tasks.Task C.M2()" + IL_002b: callvirt "System.Runtime.CompilerServices.TaskAwaiter System.Threading.Tasks.Task.GetAwaiter()" + IL_0030: stloc.s V_6 + IL_0032: ldloca.s V_6 + IL_0034: call "bool System.Runtime.CompilerServices.TaskAwaiter.IsCompleted.get" + IL_0039: brtrue.s IL_007c + IL_003b: ldarg.0 + IL_003c: ldc.i4.0 + IL_003d: dup + IL_003e: stloc.0 + IL_003f: stfld "int C.
d__0.<>1__state" + IL_0044: ldarg.0 + IL_0045: ldloc.s V_6 + IL_0047: stfld "System.Runtime.CompilerServices.TaskAwaiter C.
d__0.<>u__1" + IL_004c: ldarg.0 + IL_004d: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.
d__0.<>t__builder" + IL_0052: ldloca.s V_6 + IL_0054: ldarg.0 + IL_0055: call "void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AwaitUnsafeOnCompleted, C.
d__0>(ref System.Runtime.CompilerServices.TaskAwaiter, ref C.
d__0)" + IL_005a: leave IL_0217 + IL_005f: ldarg.0 + IL_0060: ldfld "System.Runtime.CompilerServices.TaskAwaiter C.
d__0.<>u__1" + IL_0065: stloc.s V_6 + IL_0067: ldarg.0 + IL_0068: ldflda "System.Runtime.CompilerServices.TaskAwaiter C.
d__0.<>u__1" + IL_006d: initobj "System.Runtime.CompilerServices.TaskAwaiter" + IL_0073: ldarg.0 + IL_0074: ldc.i4.m1 + IL_0075: dup + IL_0076: stloc.0 + IL_0077: stfld "int C.
d__0.<>1__state" + IL_007c: ldloca.s V_6 + IL_007e: call "int[] System.Runtime.CompilerServices.TaskAwaiter.GetResult()" + IL_0083: stloc.3 + IL_0084: ldarg.0 + IL_0085: ldc.i4.0 + IL_0086: stfld "int C.
d__0.<>7__wrap4" + IL_008b: ldarg.0 + IL_008c: ldc.i4.1 + IL_008d: ldarg.0 + IL_008e: ldfld "System.Collections.Generic.List C.
d__0.<>7__wrap3" + IL_0093: callvirt "int System.Collections.Generic.List.Count.get" + IL_0098: ldloc.3 + IL_0099: ldlen + IL_009a: conv.i4 + IL_009b: add + IL_009c: add + IL_009d: newarr "int" + IL_00a2: stfld "int[] C.
d__0.<>7__wrap5" + IL_00a7: ldarg.0 + IL_00a8: ldfld "System.Collections.Generic.List C.
d__0.<>7__wrap3" + IL_00ad: call "System.Span System.Runtime.InteropServices.CollectionsMarshal.AsSpan(System.Collections.Generic.List)" + IL_00b2: stloc.s V_4 + IL_00b4: ldloca.s V_4 + IL_00b6: ldarg.0 + IL_00b7: ldfld "int[] C.
d__0.<>7__wrap5" + IL_00bc: newobj "System.Span..ctor(int[])" + IL_00c1: stloc.s V_7 + IL_00c3: ldloca.s V_7 + IL_00c5: ldarg.0 + IL_00c6: ldfld "int C.
d__0.<>7__wrap4" + IL_00cb: ldloca.s V_4 + IL_00cd: call "int System.Span.Length.get" + IL_00d2: call "System.Span System.Span.Slice(int, int)" + IL_00d7: call "void System.Span.CopyTo(System.Span)" + IL_00dc: ldarg.0 + IL_00dd: ldarg.0 + IL_00de: ldfld "int C.
d__0.<>7__wrap4" + IL_00e3: ldloca.s V_4 + IL_00e5: call "int System.Span.Length.get" + IL_00ea: add + IL_00eb: stfld "int C.
d__0.<>7__wrap4" + IL_00f0: ldloc.3 + IL_00f1: newobj "System.Span..ctor(int[])" + IL_00f6: stloc.s V_5 + IL_00f8: ldloca.s V_5 + IL_00fa: ldarg.0 + IL_00fb: ldfld "int[] C.
d__0.<>7__wrap5" + IL_0100: newobj "System.Span..ctor(int[])" + IL_0105: stloc.s V_7 + IL_0107: ldloca.s V_7 + IL_0109: ldarg.0 + IL_010a: ldfld "int C.
d__0.<>7__wrap4" + IL_010f: ldloca.s V_5 + IL_0111: call "int System.Span.Length.get" + IL_0116: call "System.Span System.Span.Slice(int, int)" + IL_011b: call "void System.Span.CopyTo(System.Span)" + IL_0120: ldarg.0 + IL_0121: ldarg.0 + IL_0122: ldfld "int C.
d__0.<>7__wrap4" + IL_0127: ldloca.s V_5 + IL_0129: call "int System.Span.Length.get" + IL_012e: add + IL_012f: stfld "int C.
d__0.<>7__wrap4" + IL_0134: ldarg.0 + IL_0135: ldarg.0 + IL_0136: ldfld "int[] C.
d__0.<>7__wrap5" + IL_013b: stfld "int[] C.
d__0.<>7__wrap1" + IL_0140: ldarg.0 + IL_0141: ldarg.0 + IL_0142: ldfld "int C.
d__0.<>7__wrap4" + IL_0147: stfld "int C.
d__0.<>7__wrap2" + IL_014c: call "System.Threading.Tasks.Task C.M2()" + IL_0151: callvirt "System.Runtime.CompilerServices.TaskAwaiter System.Threading.Tasks.Task.GetAwaiter()" + IL_0156: stloc.s V_6 + IL_0158: ldloca.s V_6 + IL_015a: call "bool System.Runtime.CompilerServices.TaskAwaiter.IsCompleted.get" + IL_015f: brtrue.s IL_01a2 + IL_0161: ldarg.0 + IL_0162: ldc.i4.1 + IL_0163: dup + IL_0164: stloc.0 + IL_0165: stfld "int C.
d__0.<>1__state" + IL_016a: ldarg.0 + IL_016b: ldloc.s V_6 + IL_016d: stfld "System.Runtime.CompilerServices.TaskAwaiter C.
d__0.<>u__1" + IL_0172: ldarg.0 + IL_0173: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.
d__0.<>t__builder" + IL_0178: ldloca.s V_6 + IL_017a: ldarg.0 + IL_017b: call "void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AwaitUnsafeOnCompleted, C.
d__0>(ref System.Runtime.CompilerServices.TaskAwaiter, ref C.
d__0)" + IL_0180: leave IL_0217 + IL_0185: ldarg.0 + IL_0186: ldfld "System.Runtime.CompilerServices.TaskAwaiter C.
d__0.<>u__1" + IL_018b: stloc.s V_6 + IL_018d: ldarg.0 + IL_018e: ldflda "System.Runtime.CompilerServices.TaskAwaiter C.
d__0.<>u__1" + IL_0193: initobj "System.Runtime.CompilerServices.TaskAwaiter" + IL_0199: ldarg.0 + IL_019a: ldc.i4.m1 + IL_019b: dup + IL_019c: stloc.0 + IL_019d: stfld "int C.
d__0.<>1__state" + IL_01a2: ldloca.s V_6 + IL_01a4: call "int[] System.Runtime.CompilerServices.TaskAwaiter.GetResult()" + IL_01a9: stloc.2 + IL_01aa: ldarg.0 + IL_01ab: ldfld "int[] C.
d__0.<>7__wrap1" + IL_01b0: ldarg.0 + IL_01b1: ldfld "int C.
d__0.<>7__wrap2" + IL_01b6: ldloc.2 + IL_01b7: ldc.i4.0 + IL_01b8: ldelem.i4 + IL_01b9: stelem.i4 + IL_01ba: ldarg.0 + IL_01bb: ldarg.0 + IL_01bc: ldfld "int C.
d__0.<>7__wrap4" + IL_01c1: ldc.i4.1 + IL_01c2: add + IL_01c3: stfld "int C.
d__0.<>7__wrap4" + IL_01c8: ldarg.0 + IL_01c9: ldfld "int[] C.
d__0.<>7__wrap5" + IL_01ce: ldarg.0 + IL_01cf: ldnull + IL_01d0: stfld "int[] C.
d__0.<>7__wrap1" + IL_01d5: ldarg.0 + IL_01d6: ldnull + IL_01d7: stfld "System.Collections.Generic.List C.
d__0.<>7__wrap3" + IL_01dc: ldarg.0 + IL_01dd: ldnull + IL_01de: stfld "int[] C.
d__0.<>7__wrap5" + IL_01e3: ldc.i4.0 + IL_01e4: call "void CollectionExtensions.Report(object, bool)" + IL_01e9: leave.s IL_0204 + } + catch System.Exception + { + IL_01eb: stloc.s V_8 + IL_01ed: ldarg.0 + IL_01ee: ldc.i4.s -2 + IL_01f0: stfld "int C.
d__0.<>1__state" + IL_01f5: ldarg.0 + IL_01f6: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.
d__0.<>t__builder" + IL_01fb: ldloc.s V_8 + IL_01fd: call "void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetException(System.Exception)" + IL_0202: leave.s IL_0217 + } + IL_0204: ldarg.0 + IL_0205: ldc.i4.s -2 + IL_0207: stfld "int C.
d__0.<>1__state" + IL_020c: ldarg.0 + IL_020d: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.
d__0.<>t__builder" + IL_0212: call "void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetResult()" + IL_0217: ret } """); } From da6ad135e4f5184eed497a4a6ae07b4f9422443c Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Fri, 15 Dec 2023 15:32:30 -0800 Subject: [PATCH 16/18] address feedback --- .../LocalRewriter_CollectionExpression.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs index 04a2a3adc4e75..31a6f1b1d052b 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs @@ -613,7 +613,7 @@ bool tryGetToArrayMethod(TypeSymbol spreadTypeOriginalDefinition, WellKnownType localsBuilder, numberIncludingLastSpread, sideEffects, - (ArrayBuilder expressions, BoundExpression arrayTemp, BoundExpression rewrittenValue) => + addElement: (ArrayBuilder expressions, BoundExpression arrayTemp, BoundExpression rewrittenValue) => { Debug.Assert(arrayTemp.Type is ArrayTypeSymbol); Debug.Assert(indexTemp.Type is { SpecialType: SpecialType.System_Int32 }); @@ -638,7 +638,7 @@ bool tryGetToArrayMethod(TypeSymbol spreadTypeOriginalDefinition, WellKnownType isRef: false, indexTemp.Type)); }, - (ArrayBuilder sideEffects, BoundExpression arrayTemp, BoundCollectionExpressionSpreadElement spreadElement, BoundExpression rewrittenSpreadOperand) => + tryOptimizeSpreadElement: (ArrayBuilder sideEffects, BoundExpression arrayTemp, BoundCollectionExpressionSpreadElement spreadElement, BoundExpression rewrittenSpreadOperand) => { if (PrepareCopyToOptimization(spreadElement, rewrittenSpreadOperand) is not var (spanSliceMethod, spreadElementAsSpan, getLengthMethod, copyToMethod)) return false; @@ -672,6 +672,11 @@ private bool TryGetSpanConversion(TypeSymbol type, out MethodSymbol? asSpanMetho if (type is ArrayTypeSymbol { IsSZArray: true } arrayType && _factory.WellKnownMethod(WellKnownMember.System_Span_T__ctor_Array, isOptional: true) is { } spanCtorArray) { +#if DEBUG + // We expect the array element type to be usable as a type argument + var useSiteInfo = GetNewCompoundUseSiteInfo(); + Debug.Assert(_compilation.Conversions.ClassifyConversionFromType(source: arrayType.ElementType, destination: _compilation.GetSpecialType(SpecialType.System_Object), isChecked: false, ref useSiteInfo).IsImplicit); +#endif asSpanMethod = spanCtorArray.AsMember(spanCtorArray.ContainingType.Construct(arrayType.ElementType)); return true; } @@ -909,7 +914,7 @@ private BoundExpression CreateAndPopulateList(BoundCollectionExpression node, Ty localsBuilder, numberIncludingLastSpread, sideEffects, - (ArrayBuilder expressions, BoundExpression spanTemp, BoundExpression rewrittenValue) => + addElement: (ArrayBuilder expressions, BoundExpression spanTemp, BoundExpression rewrittenValue) => { Debug.Assert(spanTemp.Type is NamedTypeSymbol); Debug.Assert(indexTemp.Type is { SpecialType: SpecialType.System_Int32 }); @@ -934,7 +939,7 @@ private BoundExpression CreateAndPopulateList(BoundCollectionExpression node, Ty isRef: false, indexTemp.Type)); }, - (ArrayBuilder sideEffects, BoundExpression spanTemp, BoundCollectionExpressionSpreadElement spreadElement, BoundExpression rewrittenSpreadOperand) => + tryOptimizeSpreadElement: (ArrayBuilder sideEffects, BoundExpression spanTemp, BoundCollectionExpressionSpreadElement spreadElement, BoundExpression rewrittenSpreadOperand) => { if (PrepareCopyToOptimization(spreadElement, rewrittenSpreadOperand) is not var (spanSliceMethod, spreadElementAsSpan, getLengthMethod, copyToMethod)) return false; @@ -953,13 +958,13 @@ private BoundExpression CreateAndPopulateList(BoundCollectionExpression node, Ty localsBuilder, numberIncludingLastSpread, sideEffects, - (ArrayBuilder expressions, BoundExpression listTemp, BoundExpression rewrittenValue) => + addElement: (ArrayBuilder expressions, BoundExpression listTemp, BoundExpression rewrittenValue) => { // list.Add(element); expressions.Add( _factory.Call(listTemp, addMethod, rewrittenValue)); }, - (ArrayBuilder sideEffects, BoundExpression listTemp, BoundCollectionExpressionSpreadElement spreadElement, BoundExpression rewrittenSpreadOperand) => + tryOptimizeSpreadElement: (ArrayBuilder sideEffects, BoundExpression listTemp, BoundCollectionExpressionSpreadElement spreadElement, BoundExpression rewrittenSpreadOperand) => { if (addRangeMethod is null) return false; From a071343fff7dfb29fc539dadedc46c28923d925e Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Fri, 15 Dec 2023 15:38:00 -0800 Subject: [PATCH 17/18] Adjust assertion --- .../LocalRewriter/LocalRewriter_CollectionExpression.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs index 31a6f1b1d052b..a770cc8da97bf 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs @@ -674,7 +674,7 @@ private bool TryGetSpanConversion(TypeSymbol type, out MethodSymbol? asSpanMetho { #if DEBUG // We expect the array element type to be usable as a type argument - var useSiteInfo = GetNewCompoundUseSiteInfo(); + var useSiteInfo = CompoundUseSiteInfo.Discarded; Debug.Assert(_compilation.Conversions.ClassifyConversionFromType(source: arrayType.ElementType, destination: _compilation.GetSpecialType(SpecialType.System_Object), isChecked: false, ref useSiteInfo).IsImplicit); #endif asSpanMethod = spanCtorArray.AsMember(spanCtorArray.ContainingType.Construct(arrayType.ElementType)); From 1b1ad03e1b05284e1aec3d8dc703c544737b8ca0 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Fri, 15 Dec 2023 16:41:50 -0800 Subject: [PATCH 18/18] Add targetFramework to pointer tests. Fix impl. --- .../LocalRewriter_CollectionExpression.cs | 13 +++++++------ .../Emit2/Semantics/CollectionExpressionTests.cs | 8 ++++---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs index a770cc8da97bf..144a7608a8d17 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs @@ -667,18 +667,19 @@ bool tryGetToArrayMethod(TypeSymbol spreadTypeOriginalDefinition, WellKnownType /// Returns true if type is convertible to Span or ReadOnlySpan. /// If non-identity conversion, also returns a non-null asSpanMethod. /// + /// We are assuming that the well-known types we are converting to/from do not have constraints on their type parameters. private bool TryGetSpanConversion(TypeSymbol type, out MethodSymbol? asSpanMethod) { if (type is ArrayTypeSymbol { IsSZArray: true } arrayType && _factory.WellKnownMethod(WellKnownMember.System_Span_T__ctor_Array, isOptional: true) is { } spanCtorArray) { -#if DEBUG - // We expect the array element type to be usable as a type argument + // conversion to 'object' will fail if, for example, 'arrayType.ElementType' is a pointer. var useSiteInfo = CompoundUseSiteInfo.Discarded; - Debug.Assert(_compilation.Conversions.ClassifyConversionFromType(source: arrayType.ElementType, destination: _compilation.GetSpecialType(SpecialType.System_Object), isChecked: false, ref useSiteInfo).IsImplicit); -#endif - asSpanMethod = spanCtorArray.AsMember(spanCtorArray.ContainingType.Construct(arrayType.ElementType)); - return true; + if (_compilation.Conversions.ClassifyConversionFromType(source: arrayType.ElementType, destination: _compilation.GetSpecialType(SpecialType.System_Object), isChecked: false, ref useSiteInfo).IsImplicit) + { + asSpanMethod = spanCtorArray.AsMember(spanCtorArray.ContainingType.Construct(arrayType.ElementType)); + return true; + } } if (type is not NamedTypeSymbol namedType) diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs index 9890776e3a89d..a5914c7368cd4 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs @@ -4814,7 +4814,7 @@ unsafe static void Main() } } """; - CompileAndVerify(source, options: TestOptions.UnsafeReleaseExe, verify: Verification.Skipped, expectedOutput: "0, 2, "); + CompileAndVerify(source, options: TestOptions.UnsafeReleaseExe, targetFramework: TargetFramework.Net80, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("0, 2, ")); } [Fact] @@ -4839,7 +4839,7 @@ unsafe static void Main() } } """; - CompileAndVerify(new[] { source, s_collectionExtensions }, options: TestOptions.UnsafeReleaseExe, verify: Verification.Skipped, expectedOutput: "[0, 2], "); + CompileAndVerify(new[] { source, s_collectionExtensions }, options: TestOptions.UnsafeReleaseExe, targetFramework: TargetFramework.Net80, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("[0, 2], ")); } [Fact] @@ -4861,7 +4861,7 @@ static void Main() } } """; - var verifier = CompileAndVerify(new[] { source, s_collectionExtensions }, options: TestOptions.UnsafeReleaseExe, verify: Verification.Skipped, expectedOutput: "1010"); + var verifier = CompileAndVerify(new[] { source, s_collectionExtensions }, options: TestOptions.UnsafeReleaseExe, targetFramework: TargetFramework.Net80, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("1010")); verifier.VerifyIL("Program.Main", """ { // Code size 86 (0x56) @@ -4953,7 +4953,7 @@ static void Main() } } """; - var verifier = CompileAndVerify(new[] { source, s_collectionExtensions }, options: TestOptions.UnsafeReleaseExe, verify: Verification.Skipped, expectedOutput: "10200"); + var verifier = CompileAndVerify(new[] { source, s_collectionExtensions }, options: TestOptions.UnsafeReleaseExe, targetFramework: TargetFramework.Net80, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("10200")); verifier.VerifyIL("Program.Main", """ { // Code size 149 (0x95)