From ce62431882bd4ac54f4775b7f4a29c09cfcd8ff7 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Thu, 11 Jan 2024 11:50:54 +0300 Subject: [PATCH 01/71] Add well-known members --- .../Symbol/Symbols/MissingSpecialMember.cs | 5 ++ .../Core/Portable/WellKnownMember.cs | 6 ++ .../Core/Portable/WellKnownMembers.cs | 84 ++++++++++++++++++- .../WellKnownTypeValidationTests.vb | 11 ++- 4 files changed, 104 insertions(+), 2 deletions(-) diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs index e477803077b41..c15dbfc1841df 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs @@ -958,9 +958,14 @@ public void AllWellKnownTypeMembers() case WellKnownMember.System_ReadOnlySpan_T__ctor_Pointer: case WellKnownMember.System_ReadOnlySpan_T__ctor_Array: case WellKnownMember.System_ReadOnlySpan_T__ctor_Array_Start_Length: + case WellKnownMember.System_ReadOnlySpan_T__ctor_Reference: case WellKnownMember.System_ReadOnlySpan_T__get_Item: case WellKnownMember.System_ReadOnlySpan_T__get_Length: case WellKnownMember.System_ReadOnlySpan_T__Slice_Int_Int: + case WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar: + case WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan: + case WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan: + case WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan: case WellKnownMember.System_Index__ctor: case WellKnownMember.System_Index__GetOffset: case WellKnownMember.System_Range__ctor: diff --git a/src/Compilers/Core/Portable/WellKnownMember.cs b/src/Compilers/Core/Portable/WellKnownMember.cs index d3c38f6908576..cbc72af382ad3 100644 --- a/src/Compilers/Core/Portable/WellKnownMember.cs +++ b/src/Compilers/Core/Portable/WellKnownMember.cs @@ -431,6 +431,11 @@ internal enum WellKnownMember System_Runtime_CompilerServices_TupleElementNamesAttribute__ctorTransformNames, System_String__Format_IFormatProvider, + System_String__op_Implicit_ToReadOnlySpanOfChar, + + System_String__Concat_ReadOnlySpanReadOnlySpan, + System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan, + System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan, Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningSingleFile, Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningMultipleFiles, @@ -495,6 +500,7 @@ internal enum WellKnownMember System_ReadOnlySpan_T__ctor_Pointer, System_ReadOnlySpan_T__ctor_Array, System_ReadOnlySpan_T__ctor_Array_Start_Length, + System_ReadOnlySpan_T__ctor_Reference, System_ReadOnlySpan_T__get_Item, System_ReadOnlySpan_T__get_Length, System_ReadOnlySpan_T__Slice_Int_Int, diff --git a/src/Compilers/Core/Portable/WellKnownMembers.cs b/src/Compilers/Core/Portable/WellKnownMembers.cs index 3365e9caf63e1..6b352d1b83884 100644 --- a/src/Compilers/Core/Portable/WellKnownMembers.cs +++ b/src/Compilers/Core/Portable/WellKnownMembers.cs @@ -497,7 +497,7 @@ static WellKnownMembers() (byte)WellKnownType.System_Collections_Generic_EqualityComparer_T, // DeclaringTypeId 0, // Arity 0, // Method Signature - (byte)SignatureTypeCode.TypeHandle, (byte)WellKnownType.System_Collections_Generic_EqualityComparer_T,// Return Type + (byte)SignatureTypeCode.TypeHandle, (byte)WellKnownType.System_Collections_Generic_EqualityComparer_T, // Return Type // System_AttributeUsageAttribute__ctor (byte)MemberFlags.Constructor, // Flags @@ -2979,6 +2979,74 @@ static WellKnownMembers() (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, (byte)SignatureTypeCode.SZArray, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Object, + // System_String__op_Implicit_ToReadOnlySpanOfChar + (byte)(MemberFlags.Method | MemberFlags.Static), // Flags + (byte)SpecialType.System_String, // DeclaringTypeId + 0, // Arity + 1, // Method Signature + (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, // Return Type + (byte)WellKnownType.ExtSentinel, (WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), + 1, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, + + // System_String__Concat_ReadOnlySpanReadOnlySpan + (byte)(MemberFlags.Method | MemberFlags.Static), // Flags + (byte)SpecialType.System_String, // DeclaringTypeId + 0, // Arity + 2, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, // Return Type + (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, + (byte)WellKnownType.ExtSentinel, (WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), + 1, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, + (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, + (byte)WellKnownType.ExtSentinel, (WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), + 1, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, + + // System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan + (byte)(MemberFlags.Method | MemberFlags.Static), // Flags + (byte)SpecialType.System_String, // DeclaringTypeId + 0, // Arity + 3, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, // Return Type + (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, + (byte)WellKnownType.ExtSentinel, (WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), + 1, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, + (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, + (byte)WellKnownType.ExtSentinel, (WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), + 1, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, + (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, + (byte)WellKnownType.ExtSentinel, (WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), + 1, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, + + // System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan + (byte)(MemberFlags.Method | MemberFlags.Static), // Flags + (byte)SpecialType.System_String, // DeclaringTypeId + 0, // Arity + 4, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, // Return Type + (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, + (byte)WellKnownType.ExtSentinel, (WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), + 1, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, + (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, + (byte)WellKnownType.ExtSentinel, (WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), + 1, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, + (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, + (byte)WellKnownType.ExtSentinel, (WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), + 1, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, + (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, + (byte)WellKnownType.ExtSentinel, (WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), + 1, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, + // Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningSingleFile (byte)(MemberFlags.Method | MemberFlags.Static), // Flags (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.Microsoft_CodeAnalysis_Runtime_Instrumentation - WellKnownType.ExtSentinel), // DeclaringTypeId @@ -3439,6 +3507,14 @@ static WellKnownMembers() (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Int32, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Int32, + // System_ReadOnlySpan_T__ctor_Reference + (byte)(MemberFlags.Constructor), // 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.ByReference, (byte)SignatureTypeCode.GenericTypeParameter, 0, + // System_ReadOnlySpan_T__get_Item (byte)(MemberFlags.PropertyGet), // Flags (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), // DeclaringTypeId @@ -4812,6 +4888,11 @@ static WellKnownMembers() ".ctor", // System_Runtime_CompilerServices_TupleElementNamesAttribute__ctorTransformNames "Format", // System_String__Format_IFormatProvider + "op_Implicit", // System_String__op_Implicit_ToReadOnlySpanOfChar + + "Concat", // System_String__Concat_ReadOnlySpanReadOnlySpan + "Concat", // System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan + "Concat", // System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan "CreatePayload", // Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningSingleFile "CreatePayload", // Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningMultipleFiles @@ -4867,6 +4948,7 @@ static WellKnownMembers() ".ctor", // System_ReadOnlySpan_T__ctor_Pointer ".ctor", // System_ReadOnlySpan_T__ctor_Array ".ctor", // System_ReadOnlySpan_T__ctor_Array_Start_Length + ".ctor", // System_ReadOnlySpan_T__ctor_Reference "get_Item", // System_ReadOnlySpan_T__get_Item "get_Length", // System_ReadOnlySpan_T__get_Length "Slice", // System_ReadOnlySpan_T__Slice_Int_Int diff --git a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb index 8700f144d3848..3f56a5c215bbd 100644 --- a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb +++ b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb @@ -2,7 +2,6 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. -Imports System.Xml.Linq Imports Microsoft.CodeAnalysis.Test.Utilities Imports Roslyn.Test.Utilities @@ -703,9 +702,14 @@ End Namespace WellKnownMember.System_ReadOnlySpan_T__ctor_Pointer, WellKnownMember.System_ReadOnlySpan_T__ctor_Array, WellKnownMember.System_ReadOnlySpan_T__ctor_Array_Start_Length, + WellKnownMember.System_ReadOnlySpan_T__ctor_Reference, WellKnownMember.System_ReadOnlySpan_T__get_Item, WellKnownMember.System_ReadOnlySpan_T__get_Length, WellKnownMember.System_ReadOnlySpan_T__Slice_Int_Int, + WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar, + WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan, + WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan, + WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan, WellKnownMember.System_Runtime_CompilerServices_AsyncIteratorStateMachineAttribute__ctor, WellKnownMember.System_IAsyncDisposable__DisposeAsync, WellKnownMember.System_Collections_Generic_IAsyncEnumerable_T__GetAsyncEnumerator, @@ -908,9 +912,14 @@ End Namespace WellKnownMember.System_ReadOnlySpan_T__ctor_Pointer, WellKnownMember.System_ReadOnlySpan_T__ctor_Array, WellKnownMember.System_ReadOnlySpan_T__ctor_Array_Start_Length, + WellKnownMember.System_ReadOnlySpan_T__ctor_Reference, WellKnownMember.System_ReadOnlySpan_T__get_Item, WellKnownMember.System_ReadOnlySpan_T__get_Length, WellKnownMember.System_ReadOnlySpan_T__Slice_Int_Int, + WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar, + WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan, + WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan, + WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan, WellKnownMember.System_Runtime_CompilerServices_AsyncIteratorStateMachineAttribute__ctor, WellKnownMember.System_IAsyncDisposable__DisposeAsync, WellKnownMember.System_Collections_Generic_IAsyncEnumerable_T__GetAsyncEnumerator, From ed3da0e74d4b1aef880bdc9301be91edbcec02ee Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Thu, 11 Jan 2024 19:00:50 +0300 Subject: [PATCH 02/71] Better null-annotate `TryGetWellKnownTypeMember` --- .../Portable/Lowering/LocalRewriter/LocalRewriter.cs | 8 ++++---- .../Lowering/LocalRewriter/LocalRewriter_Conversion.cs | 2 +- .../Lowering/LocalRewriter/LocalRewriter_Event.cs | 8 ++++---- .../LocalRewriter/LocalRewriter_FixedStatement.cs | 4 ++-- .../Lowering/LocalRewriter/LocalRewriter_LockStatement.cs | 6 +++--- .../LocalRewriter_ObjectCreationExpression.cs | 2 +- .../Lowering/LocalRewriter/LocalRewriter_StackAlloc.cs | 2 +- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs index c7fda547b4e9b..bcadd43302ad3 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs @@ -558,11 +558,11 @@ private static BoundExpression BadExpression(SyntaxNode syntax, TypeSymbol resul return new BoundBadExpression(syntax, LookupResultKind.NotReferencable, ImmutableArray.Empty, children, resultType); } - private bool TryGetWellKnownTypeMember(SyntaxNode? syntax, WellKnownMember member, out TSymbol symbol, bool isOptional = false, Location? location = null) where TSymbol : Symbol + private bool TryGetWellKnownTypeMember(SyntaxNode? syntax, WellKnownMember member, [NotNullWhen(true)] out TSymbol? symbol, bool isOptional = false, Location? location = null) where TSymbol : Symbol { Debug.Assert((syntax != null) ^ (location != null)); - symbol = (TSymbol)Binder.GetWellKnownTypeMember(_compilation, member, _diagnostics, syntax: syntax, isOptional: isOptional, location: location); + symbol = (TSymbol?)Binder.GetWellKnownTypeMember(_compilation, member, _diagnostics, syntax: syntax, isOptional: isOptional, location: location); return symbol is { }; } @@ -617,7 +617,7 @@ public override BoundNode VisitTypeOfOperator(BoundTypeOfOperator node) var type = this.VisitType(node.Type); // Emit needs this helper - MethodSymbol getTypeFromHandle; + MethodSymbol? getTypeFromHandle; if (!TryGetWellKnownTypeMember(node.Syntax, WellKnownMember.System_Type__GetTypeFromHandle, out getTypeFromHandle)) { return new BoundTypeOfOperator(node.Syntax, sourceType, null, type, hasErrors: true); @@ -634,7 +634,7 @@ public override BoundNode VisitRefTypeOperator(BoundRefTypeOperator node) var type = this.VisitType(node.Type); // Emit needs this helper - MethodSymbol getTypeFromHandle; + MethodSymbol? getTypeFromHandle; if (!TryGetWellKnownTypeMember(node.Syntax, WellKnownMember.System_Type__GetTypeFromHandle, out getTypeFromHandle)) { return new BoundRefTypeOperator(node.Syntax, operand, null, type, hasErrors: true); diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs index f7971cb748228..09347672413b0 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Conversion.cs @@ -106,7 +106,7 @@ private BoundExpression MakeUtf8Span(BoundExpression node, IReadOnlyList? BadExpression(node.Syntax, byteArray, ImmutableArray.Empty) : MakeUnderlyingArrayForUtf8Span(node.Syntax, byteArray, bytes, out length); - if (!TryGetWellKnownTypeMember(node.Syntax, WellKnownMember.System_ReadOnlySpan_T__ctor_Array_Start_Length, out MethodSymbol ctor)) + if (!TryGetWellKnownTypeMember(node.Syntax, WellKnownMember.System_ReadOnlySpan_T__ctor_Array_Start_Length, out MethodSymbol? ctor)) { result = BadExpression(node.Syntax, node.Type, ImmutableArray.Empty); } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Event.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Event.cs index 2230f563e551a..0a9644a25fe6b 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Event.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Event.cs @@ -113,7 +113,7 @@ private BoundExpression RewriteWindowsRuntimeEventAssignmentOperator(SyntaxNode BoundExpression? clearCall = null; if (kind == EventAssignmentKind.Assignment) { - MethodSymbol clearMethod; + MethodSymbol? clearMethod; if (TryGetWellKnownTypeMember(syntax, WellKnownMember.System_Runtime_InteropServices_WindowsRuntime_WindowsRuntimeMarshal__RemoveAllEventHandlers, out clearMethod)) { clearCall = MakeCall( @@ -154,7 +154,7 @@ private BoundExpression RewriteWindowsRuntimeEventAssignmentOperator(SyntaxNode BoundExpression marshalCall; - MethodSymbol marshalMethod; + MethodSymbol? marshalMethod; if (TryGetWellKnownTypeMember(syntax, helper, out marshalMethod)) { marshalMethod = marshalMethod.Construct(eventType); @@ -248,7 +248,7 @@ private BoundExpression MakeEventAccess( BoundExpression getOrCreateCall; - MethodSymbol getOrCreateMethod; + MethodSymbol? getOrCreateMethod; if (TryGetWellKnownTypeMember(syntax, WellKnownMember.System_Runtime_InteropServices_WindowsRuntime_EventRegistrationTokenTable_T__GetOrCreateEventRegistrationTokenTable, out getOrCreateMethod)) { getOrCreateMethod = getOrCreateMethod.AsMember(fieldType); @@ -266,7 +266,7 @@ private BoundExpression MakeEventAccess( getOrCreateCall = new BoundBadExpression(syntax, LookupResultKind.NotInvocable, ImmutableArray.Empty, ImmutableArray.Create(fieldAccess), ErrorTypeSymbol.UnknownResultType); } - PropertySymbol invocationListProperty; + PropertySymbol? invocationListProperty; if (TryGetWellKnownTypeMember(syntax, WellKnownMember.System_Runtime_InteropServices_WindowsRuntime_EventRegistrationTokenTable_T__InvocationList, out invocationListProperty)) { MethodSymbol invocationListAccessor = invocationListProperty.GetMethod; diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_FixedStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_FixedStatement.cs index 3de6918805b93..669f89e230748 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_FixedStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_FixedStatement.cs @@ -473,7 +473,7 @@ private BoundStatement InitializeFixedStatementStringLocal( BoundExpression notNullCheck = _factory.MakeNullCheck(factory.Syntax, factory.Local(localSymbol), BinaryOperatorKind.NotEqual); BoundExpression helperCall; - MethodSymbol offsetMethod; + MethodSymbol? offsetMethod; if (TryGetWellKnownTypeMember(fixedInitializer.Syntax, WellKnownMember.System_Runtime_CompilerServices_RuntimeHelpers__get_OffsetToStringData, out offsetMethod)) { helperCall = factory.Call(receiver: null, method: offsetMethod); @@ -534,7 +534,7 @@ private BoundStatement InitializeFixedStatementArrayLocal( } else { - MethodSymbol lengthMethod; + MethodSymbol? lengthMethod; if (TryGetWellKnownTypeMember(fixedInitializer.Syntax, WellKnownMember.System_Array__get_Length, out lengthMethod)) { lengthCall = factory.Call(factory.Local(pinnedTemp), lengthMethod); diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_LockStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_LockStatement.cs index a12604751d8b0..10977601afa2e 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_LockStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_LockStatement.cs @@ -58,7 +58,7 @@ public override BoundNode VisitLockStatement(BoundLockStatement node) BoundStatement boundLockTempInit = new BoundExpressionStatement(lockSyntax, assignmentToLockTemp); BoundExpression exitCallExpr; - MethodSymbol exitMethod; + MethodSymbol? exitMethod; if (TryGetWellKnownTypeMember(lockSyntax, WellKnownMember.System_Threading_Monitor__Exit, out exitMethod)) { exitCallExpr = BoundCall.Synthesized( @@ -75,7 +75,7 @@ public override BoundNode VisitLockStatement(BoundLockStatement node) BoundStatement exitCall = new BoundExpressionStatement(lockSyntax, exitCallExpr); - MethodSymbol enterMethod; + MethodSymbol? enterMethod; if ((TryGetWellKnownTypeMember(lockSyntax, WellKnownMember.System_Threading_Monitor__Enter2, out enterMethod, isOptional: true) || TryGetWellKnownTypeMember(lockSyntax, WellKnownMember.System_Threading_Monitor__Enter, out enterMethod)) && // If we didn't find the overload introduced in .NET 4.0, then use the older one. @@ -153,7 +153,7 @@ public override BoundNode VisitLockStatement(BoundLockStatement node) BoundExpression enterCallExpr; - if ((object)enterMethod != null) + if ((object?)enterMethod != null) { Debug.Assert(enterMethod.ParameterCount == 1); diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectCreationExpression.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectCreationExpression.cs index 402d11f4a08a6..4dd6909c81168 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectCreationExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectCreationExpression.cs @@ -322,7 +322,7 @@ private BoundExpression MakeNewT(SyntaxNode syntax, TypeParameterSymbol typePara // if struct defines one. // Since we cannot know if T has a parameterless constructor statically, // we must call Activator.CreateInstance unconditionally. - MethodSymbol method; + MethodSymbol? method; if (!this.TryGetWellKnownTypeMember(syntax, WellKnownMember.System_Activator__CreateInstance_T, out method)) { diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StackAlloc.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StackAlloc.cs index 4ce952d314474..012a1bd23dd31 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StackAlloc.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StackAlloc.cs @@ -58,7 +58,7 @@ private BoundNode VisitStackAllocArrayCreationBase(BoundStackAllocArrayCreationB stackAllocNode.Syntax, elementType, stackSize, initializerOpt, _compilation.CreatePointerTypeSymbol(elementType)); BoundExpression constructorCall; - if (TryGetWellKnownTypeMember(stackAllocNode.Syntax, WellKnownMember.System_Span_T__ctor_Pointer, out MethodSymbol spanConstructor)) + if (TryGetWellKnownTypeMember(stackAllocNode.Syntax, WellKnownMember.System_Span_T__ctor_Pointer, out MethodSymbol? spanConstructor)) { constructorCall = _factory.New((MethodSymbol)spanConstructor.SymbolAsMember(spanType), stackAllocNode, countTemp); } From 6958b6127b87a158a248a5392ce6f7546efbf14d Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Thu, 11 Jan 2024 19:01:45 +0300 Subject: [PATCH 03/71] Optimize for 2 arguments --- .../LocalRewriter_StringConcat.cs | 104 ++++++ .../CodeGenSpanBasedStringConcatTests.cs | 308 ++++++++++++++++++ 2 files changed, 412 insertions(+) create mode 100644 src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index 62ac0d538b0b0..194778f89039c 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -79,6 +79,9 @@ private BoundExpression RewriteStringConcatenation(SyntaxNode syntax, BinaryOper leftFlattened.AddRange(rightFlattened); rightFlattened.Free(); + var objectToStringMethod = UnsafeGetSpecialTypeMethod(syntax, SpecialMember.System_Object__ToString); + NamedTypeSymbol? charType = null; + BoundExpression result; switch (leftFlattened.Count) @@ -96,6 +99,38 @@ private BoundExpression RewriteStringConcatenation(SyntaxNode syntax, BinaryOper case 2: var left = leftFlattened[0]; var right = leftFlattened[1]; + + var leftIsCharToString = isCharToString(left, objectToStringMethod, ref charType, out BoundExpression? unwrappedLeftChar); + var rightIsCharToString = isCharToString(right, objectToStringMethod, ref charType, out BoundExpression? unwrappedRightChar); + + if ((leftIsCharToString || rightIsCharToString) && + TryGetWellKnownTypeMember(syntax, WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan, out MethodSymbol? concat2Spans, isOptional: true) && + TryGetWellKnownTypeMember(syntax, WellKnownMember.System_ReadOnlySpan_T__ctor_Reference, out MethodSymbol? readOnlySpanCtorRefParamGeneric, isOptional: true)) + { + // One of args is a char (or rather `someChar.ToString()`, but we've unwrapped `someChar` back from that call). We can use span-based concatenation, which takes a bit more IL, but avoids allocation of an intermidiate string from char.ToString call. + // And since implicit conversion from string to span is a JIT intrinsic, the resulting code ends up being faster than the one generated from the "naive" case with char.ToString call + Debug.Assert(charType is not null); + var readOnlySpanOfChar = readOnlySpanCtorRefParamGeneric.ContainingType.Construct(charType); + var readOnlySpanCtorRefParamChar = readOnlySpanCtorRefParamGeneric.AsMember(readOnlySpanOfChar); + + MethodSymbol? stringImplicitConversionToReadOnlySpan = null; + + // Technically we can get `char1.ToString() + char2.ToString()` as a user input. In such case we don't need implicit conversion method `string -> ReadOnlySpan` + if ((leftIsCharToString && rightIsCharToString) || + TryGetWellKnownTypeMember(syntax, WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar, out stringImplicitConversionToReadOnlySpan, isOptional: true)) + { + result = RewriteStringConcatenationWithSpanBasedConcat( + syntax, + concat2Spans, + stringImplicitConversionToReadOnlySpan, + readOnlySpanCtorRefParamChar, + leftIsCharToString ? unwrappedLeftChar! : left, + rightIsCharToString ? unwrappedRightChar! : right); + + break; + } + } + result = RewriteStringConcatenationTwoExprs(syntax, left, right); break; @@ -125,6 +160,19 @@ private BoundExpression RewriteStringConcatenation(SyntaxNode syntax, BinaryOper leftFlattened.Free(); return result; + + static bool isCharToString(BoundExpression expr, MethodSymbol objectToStringMethod, ref NamedTypeSymbol? charType, out BoundExpression? unwrappedChar) + { + if (expr is not BoundCall { ReceiverOpt: { Type: NamedTypeSymbol { SpecialType: SpecialType.System_Char } receiverCharType } receiver, Method: var method }) + { + unwrappedChar = null; + return false; + } + + unwrappedChar = receiver; + charType = receiverCharType; + return (object)method.GetLeastOverriddenMethod(charType) == objectToStringMethod; + } } /// @@ -346,6 +394,62 @@ private BoundExpression RewriteStringConcatenationManyExprs(SyntaxNode syntax, I return BoundCall.Synthesized(syntax, receiverOpt: null, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, method, array); } + private BoundExpression RewriteStringConcatenationWithSpanBasedConcat(SyntaxNode syntax, MethodSymbol spanConcat, MethodSymbol? stringImplicitConversionToReadOnlySpan, MethodSymbol readOnlySpanCtorRefParamChar, params BoundExpression[] args) + { + var preparedArgsBuilder = ArrayBuilder.GetInstance(capacity: args.Length); + var localsBuilder = ArrayBuilder.GetInstance(capacity: args.Length - 1); + + foreach (var arg in args) + { + Debug.Assert(arg.Type is not null); + + if (arg.Type.SpecialType == SpecialType.System_Char) + { + var temp = _factory.StoreToTemp(arg, out var tempAssignment); + localsBuilder.Add(temp.LocalSymbol); + + var wrappedChar = new BoundObjectCreationExpression( + arg.Syntax, + readOnlySpanCtorRefParamChar, + [temp], + argumentNamesOpt: default, + argumentRefKindsOpt: [RefKindExtensions.StrictIn], + expanded: false, + argsToParamsOpt: default, + defaultArguments: default, + constantValueOpt: null, + initializerExpressionOpt: null, + type: readOnlySpanCtorRefParamChar.ContainingType); + + preparedArgsBuilder.Add(new BoundSequence( + arg.Syntax, + [], + [tempAssignment], + wrappedChar, + wrappedChar.Type)); + } + else + { + Debug.Assert(arg.Type.SpecialType == SpecialType.System_String); + Debug.Assert(stringImplicitConversionToReadOnlySpan is not null); + preparedArgsBuilder.Add(BoundCall.Synthesized(arg.Syntax, receiverOpt: null, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, stringImplicitConversionToReadOnlySpan, arg)); + } + } + + var concatCall = BoundCall.Synthesized(syntax, receiverOpt: null, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, spanConcat, preparedArgsBuilder.ToImmutableAndFree()); + + var oldSyntax = _factory.Syntax; + _factory.Syntax = syntax; + + var sequence = _factory.Sequence( + localsBuilder.ToImmutableAndFree(), + [], + concatCall); + + _factory.Syntax = oldSyntax; + return sequence; + } + /// /// Most of the above optimizations are not applicable in expression trees as the operator /// must stay a binary operator. We cannot do much beyond constant folding which is done in binder. diff --git a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs new file mode 100644 index 0000000000000..157de18f25d86 --- /dev/null +++ b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs @@ -0,0 +1,308 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable disable + +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen; + +public class CodeGenSpanBasedStringConcatTests : CSharpTestBase +{ + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatTwo_ReadOnlySpan1() + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s = "s"; + var c = 'c'; + Console.Write(M1(s, c)); + Console.Write(M2(s, c)); + } + + static string M1(string s, char c) => s + c; + static string M2(string s, char c) => c + s; + } + """; + + var comp = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "sccs" : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.M1", """ + { + // Code size 21 (0x15) + .maxstack 2 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.1 + IL_0007: stloc.0 + IL_0008: ldloca.s V_0 + IL_000a: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000f: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0014: ret + } + """); + comp.VerifyIL("Test.M2", """ + { + // Code size 21 (0x15) + .maxstack 2 + .locals init (char V_0) + IL_0000: ldarg.1 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0009: ldarg.0 + IL_000a: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000f: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0014: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatTwo_ReadOnlySpan2() + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s = "s"; + var c = 'C'; + Console.Write(M1(s, c)); + Console.Write(M2(s, c)); + } + + static string M1(string s, char c) => s + char.ToLowerInvariant(c); + static string M2(string s, char c) => char.ToLowerInvariant(c) + s; + } + """; + + var comp = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "sccs" : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.M1", """ + { + // Code size 26 (0x1a) + .maxstack 2 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.1 + IL_0007: call "char char.ToLowerInvariant(char)" + IL_000c: stloc.0 + IL_000d: ldloca.s V_0 + IL_000f: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0014: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0019: ret + } + """); + comp.VerifyIL("Test.M2", """ + { + // Code size 26 (0x1a) + .maxstack 2 + .locals init (char V_0) + IL_0000: ldarg.1 + IL_0001: call "char char.ToLowerInvariant(char)" + IL_0006: stloc.0 + IL_0007: ldloca.s V_0 + IL_0009: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000e: ldarg.0 + IL_000f: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0014: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0019: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatTwo_ReadOnlySpan_SideEffect() + { + var source = """ + using System; + + public class Test + { + private static int stringCounter; + private static int charCounterPlusOne = 1; + + static void Main() + { + Console.WriteLine(M1()); + Console.WriteLine(M2()); + } + + static string M1() => GetStringWithSideEffect() + GetCharWithSideEffect(); + static string M2() => GetCharWithSideEffect() + GetStringWithSideEffect(); + + private static string GetStringWithSideEffect() + { + Console.Write(stringCounter++); + return "s"; + } + + private static char GetCharWithSideEffect() + { + Console.Write(charCounterPlusOne++); + return 'c'; + } + } + """; + + var expectedOutput = """ + 01sc + 21cs + """; + + var comp = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? expectedOutput : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.M1", """ + { + // Code size 29 (0x1d) + .maxstack 2 + .locals init (char V_0) + IL_0000: call "string Test.GetStringWithSideEffect()" + IL_0005: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000a: call "char Test.GetCharWithSideEffect()" + IL_000f: stloc.0 + IL_0010: ldloca.s V_0 + IL_0012: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0017: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001c: ret + } + """); + comp.VerifyIL("Test.M2", """ + { + // Code size 29 (0x1d) + .maxstack 2 + .locals init (char V_0) + IL_0000: call "char Test.GetCharWithSideEffect()" + IL_0005: stloc.0 + IL_0006: ldloca.s V_0 + IL_0008: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000d: call "string Test.GetStringWithSideEffect()" + IL_0012: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0017: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001c: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + public void ConcatTwoCharToStrings(int? missingUnimportantWellKnownMember) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var c1 = 'a'; + var c2 = 'b'; + Console.Write(M(c1, c2)); + } + + static string M(char c1, char c2) => c1.ToString() + c2.ToString(); + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "ab" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Test.M", """ + { + // Code size 24 (0x18) + .maxstack 2 + .locals init (char V_0, + char V_1) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0009: ldarg.1 + IL_000a: stloc.1 + IL_000b: ldloca.s V_1 + IL_000d: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0012: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0017: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + public void ConcatTwo_ReadOnlySpan_MissingMemberForOptimization(int member) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s = "s"; + var c = 'c'; + Console.Write(M1(s, c)); + Console.Write(M2(s, c)); + } + + static string M1(string s, char c) => s + c; + static string M2(string s, char c) => c + s; + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + comp.MakeMemberMissing((WellKnownMember)member); + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "sccs" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Test.M1", """ + { + // Code size 14 (0xe) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldarga.s V_1 + IL_0003: call "string char.ToString()" + IL_0008: call "string string.Concat(string, string)" + IL_000d: ret + } + """); + verifier.VerifyIL("Test.M2", """ + { + // Code size 14 (0xe) + .maxstack 2 + IL_0000: ldarga.s V_1 + IL_0002: call "string char.ToString()" + IL_0007: ldarg.0 + IL_0008: call "string string.Concat(string, string)" + IL_000d: ret + } + """); + } +} From ba7663dacaa22b91f1ecb0846323444deb00f448 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Thu, 11 Jan 2024 22:46:08 +0300 Subject: [PATCH 04/71] Optimize for 3 arguments --- .../LocalRewriter_StringConcat.cs | 190 +++-- .../CodeGenSpanBasedStringConcatTests.cs | 671 ++++++++++++++++++ 2 files changed, 810 insertions(+), 51 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index 194778f89039c..c37143ef99414 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -97,41 +97,43 @@ private BoundExpression RewriteStringConcatenation(SyntaxNode syntax, BinaryOper break; case 2: - var left = leftFlattened[0]; - var right = leftFlattened[1]; - - var leftIsCharToString = isCharToString(left, objectToStringMethod, ref charType, out BoundExpression? unwrappedLeftChar); - var rightIsCharToString = isCharToString(right, objectToStringMethod, ref charType, out BoundExpression? unwrappedRightChar); - - if ((leftIsCharToString || rightIsCharToString) && - TryGetWellKnownTypeMember(syntax, WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan, out MethodSymbol? concat2Spans, isOptional: true) && - TryGetWellKnownTypeMember(syntax, WellKnownMember.System_ReadOnlySpan_T__ctor_Reference, out MethodSymbol? readOnlySpanCtorRefParamGeneric, isOptional: true)) { - // One of args is a char (or rather `someChar.ToString()`, but we've unwrapped `someChar` back from that call). We can use span-based concatenation, which takes a bit more IL, but avoids allocation of an intermidiate string from char.ToString call. - // And since implicit conversion from string to span is a JIT intrinsic, the resulting code ends up being faster than the one generated from the "naive" case with char.ToString call - Debug.Assert(charType is not null); - var readOnlySpanOfChar = readOnlySpanCtorRefParamGeneric.ContainingType.Construct(charType); - var readOnlySpanCtorRefParamChar = readOnlySpanCtorRefParamGeneric.AsMember(readOnlySpanOfChar); + var left = leftFlattened[0]; + var right = leftFlattened[1]; - MethodSymbol? stringImplicitConversionToReadOnlySpan = null; + var leftIsCharToString = isCharToString(left, objectToStringMethod, ref charType, out BoundExpression? unwrappedLeftChar); + var rightIsCharToString = isCharToString(right, objectToStringMethod, ref charType, out BoundExpression? unwrappedRightChar); - // Technically we can get `char1.ToString() + char2.ToString()` as a user input. In such case we don't need implicit conversion method `string -> ReadOnlySpan` - if ((leftIsCharToString && rightIsCharToString) || - TryGetWellKnownTypeMember(syntax, WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar, out stringImplicitConversionToReadOnlySpan, isOptional: true)) + if ((leftIsCharToString || rightIsCharToString) && + TryGetWellKnownTypeMember(syntax, WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan, out MethodSymbol? concat2Spans, isOptional: true) && + TryGetWellKnownTypeMember(syntax, WellKnownMember.System_ReadOnlySpan_T__ctor_Reference, out MethodSymbol? readOnlySpanCtorRefParamGeneric, isOptional: true)) { - result = RewriteStringConcatenationWithSpanBasedConcat( - syntax, - concat2Spans, - stringImplicitConversionToReadOnlySpan, - readOnlySpanCtorRefParamChar, - leftIsCharToString ? unwrappedLeftChar! : left, - rightIsCharToString ? unwrappedRightChar! : right); - - break; + // One of args is a char (or rather `someChar.ToString()`, but we've unwrapped `someChar` back from that call). We can use span-based concatenation, which takes a bit more IL, but avoids allocation of an intermidiate string from char.ToString call. + // And since implicit conversion from string to span is a JIT intrinsic, the resulting code ends up being faster than the one generated from the "naive" case with char.ToString call + Debug.Assert(charType is not null); + var readOnlySpanOfChar = readOnlySpanCtorRefParamGeneric.ContainingType.Construct(charType); + var readOnlySpanCtorRefParamChar = readOnlySpanCtorRefParamGeneric.AsMember(readOnlySpanOfChar); + + MethodSymbol? stringImplicitConversionToReadOnlySpan = null; + + // Technically we can get `char1.ToString() + char2.ToString()` as a user input. In such case we don't need implicit conversion method `string -> ReadOnlySpan` + if ((leftIsCharToString && rightIsCharToString) || + TryGetWellKnownTypeMember(syntax, WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar, out stringImplicitConversionToReadOnlySpan, isOptional: true)) + { + result = RewriteStringConcatenationWithSpanBasedConcat( + syntax, + concat2Spans, + stringImplicitConversionToReadOnlySpan, + readOnlySpanCtorRefParamChar, + leftIsCharToString ? unwrappedLeftChar! : left, + rightIsCharToString ? unwrappedRightChar! : right); + + break; + } } - } - result = RewriteStringConcatenationTwoExprs(syntax, left, right); + result = RewriteStringConcatenationTwoExprs(syntax, left, right); + } break; case 3: @@ -139,6 +141,40 @@ private BoundExpression RewriteStringConcatenation(SyntaxNode syntax, BinaryOper var first = leftFlattened[0]; var second = leftFlattened[1]; var third = leftFlattened[2]; + + var firstIsCharToString = isCharToString(first, objectToStringMethod, ref charType, out BoundExpression? unwrappedFirstChar); + var secondIsCharToString = isCharToString(second, objectToStringMethod, ref charType, out BoundExpression? unwrappedSecondChar); + var thirdIsCharToString = isCharToString(third, objectToStringMethod, ref charType, out BoundExpression? unwrappedThirdChar); + + if ((firstIsCharToString || secondIsCharToString || thirdIsCharToString) && + TryGetWellKnownTypeMember(syntax, WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan, out MethodSymbol? concat3Spans, isOptional: true) && + TryGetWellKnownTypeMember(syntax, WellKnownMember.System_ReadOnlySpan_T__ctor_Reference, out MethodSymbol? readOnlySpanCtorRefParamGeneric, isOptional: true)) + { + // One of args is a char (or rather `someChar.ToString()`, but we've unwrapped `someChar` back from that call). We can use span-based concatenation, which takes a bit more IL, but avoids allocation of an intermidiate string from char.ToString call. + // And since implicit conversion from string to span is a JIT intrinsic, the resulting code ends up being faster than the one generated from the "naive" case with char.ToString call + Debug.Assert(charType is not null); + var readOnlySpanOfChar = readOnlySpanCtorRefParamGeneric.ContainingType.Construct(charType); + var readOnlySpanCtorRefParamChar = readOnlySpanCtorRefParamGeneric.AsMember(readOnlySpanOfChar); + + MethodSymbol? stringImplicitConversionToReadOnlySpan = null; + + // Technically we can get `char1.ToString() + char2.ToString() + char3.ToString()` as a user input. In such case we don't need implicit conversion method `string -> ReadOnlySpan` + if ((firstIsCharToString && secondIsCharToString && thirdIsCharToString) || + TryGetWellKnownTypeMember(syntax, WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar, out stringImplicitConversionToReadOnlySpan, isOptional: true)) + { + result = RewriteStringConcatenationWithSpanBasedConcat( + syntax, + concat3Spans, + stringImplicitConversionToReadOnlySpan, + readOnlySpanCtorRefParamChar, + firstIsCharToString ? unwrappedFirstChar! : first, + secondIsCharToString ? unwrappedSecondChar! : second, + thirdIsCharToString ? unwrappedThirdChar! : third); + + break; + } + } + result = RewriteStringConcatenationThreeExprs(syntax, first, second, third); } break; @@ -198,13 +234,12 @@ private void FlattenConcatArg(BoundExpression lowered, ArrayBuilder - /// True if this is a call to a known string concat operator, false otherwise + /// True if this is a call to a known string concat operator and its arguments are successfully extracted, false otherwise private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableArray arguments) { - switch (lowered.Kind) + switch (lowered) { - case BoundKind.Call: - var boundCall = (BoundCall)lowered; + case BoundCall boundCall: var method = boundCall.Method; if (method.IsStatic && method.ContainingType.SpecialType == SpecialType.System_String) { @@ -229,12 +264,57 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr } } } + + if ((object)method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan) || + (object)method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan) || + (object)method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)) + { + // Faced a span-based string.Concat call. Since we can produce such call on the previous iterations ourselves, we need to unwrap it. + // The key thing is that we need not to only extract arguments, but also unwrap them from being spans and for chars also wrap them into `ToString` calls. + var wrappedArgs = boundCall.Arguments; + var unwrappedArgsBuilder = ArrayBuilder.GetInstance(capacity: wrappedArgs.Length); + + foreach (var wrappedArg in wrappedArgs) + { + // Check whether a call is an implicit `string -> ReadOnlySpan` conversion + if (wrappedArg is BoundCall { Method: var argMethod, Arguments: [var singleArgument] } && + (object)argMethod == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)) + { + unwrappedArgsBuilder.Add(singleArgument); + } + // This complicated check is for a sequence, which wraps a span around single char. + // The sequence needs to have this shape: `{ locals: , sideEffects: temp = , result: new ReadOnlySpan(in temp) }` + else if (wrappedArg is BoundSequence { Locals.Length: 0, SideEffects: [BoundAssignmentOperator { Right: var assignmentRight }], Value: BoundObjectCreationExpression { Constructor: var objectCreationConstructor, Arguments: [BoundLocal] } } && + (object)objectCreationConstructor.OriginalDefinition == _compilation.GetWellKnownTypeMember(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference) && + objectCreationConstructor.ReceiverType.IsReadOnlySpanChar()) + { + Debug.Assert(assignmentRight.Type?.IsCharType() == true); + var charToString = FindSpecificToStringOfStructType(assignmentRight.Type, UnsafeGetSpecialTypeMethod(wrappedArg.Syntax, SpecialMember.System_Object__ToString)); + if (charToString is null) + { + unwrappedArgsBuilder.Free(); + arguments = default; + return false; + } + unwrappedArgsBuilder.Add(BoundCall.Synthesized(wrappedArg.Syntax, assignmentRight, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, charToString)); + } + else + { + // If we got here we faced an argument which doesn't have a shape we produce during lowering ourselves. + // This means that the original concat call is a user input. Stop extracting arguments then + unwrappedArgsBuilder.Free(); + arguments = default; + return false; + } + } + + arguments = unwrappedArgsBuilder.ToImmutableAndFree(); + return true; + } } break; - case BoundKind.NullCoalescingOperator: - var boundCoalesce = (BoundNullCoalescingOperator)lowered; - + case BoundNullCoalescingOperator boundCoalesce: Debug.Assert(boundCoalesce.LeftPlaceholder is null); Debug.Assert(boundCoalesce.LeftConversion is null); @@ -251,6 +331,9 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr } break; + + case BoundSequence { SideEffects.Length: 0, Value: BoundCall sequenceCall }: + return TryExtractStringConcatArgs(sequenceCall, out arguments); } arguments = default; @@ -513,21 +596,7 @@ private BoundExpression ConvertConcatExprToString(SyntaxNode syntax, BoundExpres // If it's a struct which has overridden ToString, find that method. Note that we might fail to // find it, e.g. if object.ToString is missing - MethodSymbol? structToStringMethod = null; - if (expr.Type.IsValueType && !expr.Type.IsTypeParameter()) - { - var type = (NamedTypeSymbol)expr.Type; - var typeToStringMembers = type.GetMembers(objectToStringMethod.Name); - foreach (var member in typeToStringMembers) - { - if (member is MethodSymbol toStringMethod && - toStringMethod.GetLeastOverriddenMethod(type) == (object)objectToStringMethod) - { - structToStringMethod = toStringMethod; - break; - } - } - } + MethodSymbol? structToStringMethod = FindSpecificToStringOfStructType(expr.Type, objectToStringMethod); // If it's a special value type (and not a field of a MarshalByRef object), it should have its own ToString method (but we might fail to find // it if object.ToString is missing). Assume that this won't be removed, and emit a direct call rather @@ -610,5 +679,24 @@ static bool isFieldOfMarshalByRef(BoundExpression expr, CSharpCompilation compil return false; } } + + private static MethodSymbol? FindSpecificToStringOfStructType(TypeSymbol exprType, MethodSymbol objectToStringMethod) + { + if (exprType.IsValueType && !exprType.IsTypeParameter()) + { + var namedType = (NamedTypeSymbol)exprType; + var typeToStringMembers = namedType.GetMembers(objectToStringMethod.Name); + foreach (var member in typeToStringMembers) + { + if (member is MethodSymbol toStringMethod && + toStringMethod.GetLeastOverriddenMethod(namedType) == (object)objectToStringMethod) + { + return toStringMethod; + } + } + } + + return null; + } } } diff --git a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs index 157de18f25d86..1f14dce535ac4 100644 --- a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs @@ -305,4 +305,675 @@ .maxstack 2 } """); } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + public void ConcatThree_ReadOnlySpan1(int? missingUnimportantWellKnownMember) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s = "s"; + var c = 'c'; + Console.Write(M1(s, c)); + Console.Write(M2(s, c)); + Console.Write(M3(s, c)); + Console.Write(M4(s, c)); + } + + static string M1(string s, char c) => c + s + s; + static string M2(string s, char c) => s + c + s; + static string M3(string s, char c) => s + s + c; + static string M4(string s, char c) => c + s + c; + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "cssscsssccsc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Test.M1", """ + { + // Code size 27 (0x1b) + .maxstack 3 + .locals init (char V_0) + IL_0000: ldarg.1 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0009: ldarg.0 + IL_000a: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000f: ldarg.0 + IL_0010: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0015: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001a: ret + } + """); + verifier.VerifyIL("Test.M2", """ + { + // Code size 27 (0x1b) + .maxstack 3 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.1 + IL_0007: stloc.0 + IL_0008: ldloca.s V_0 + IL_000a: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000f: ldarg.0 + IL_0010: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0015: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001a: ret + } + """); + verifier.VerifyIL("Test.M3", """ + { + // Code size 27 (0x1b) + .maxstack 3 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.0 + IL_0007: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000c: ldarg.1 + IL_000d: stloc.0 + IL_000e: ldloca.s V_0 + IL_0010: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0015: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001a: ret + } + """); + verifier.VerifyIL("Test.M4", """ + { + // Code size 30 (0x1e) + .maxstack 3 + .locals init (char V_0, + char V_1) + IL_0000: ldarg.1 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0009: ldarg.0 + IL_000a: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000f: ldarg.1 + IL_0010: stloc.1 + IL_0011: ldloca.s V_1 + IL_0013: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0018: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001d: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + public void ConcatThree_ReadOnlySpan2(int? missingUnimportantWellKnownMember) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s = "s"; + var c = 'C'; + Console.Write(M1(s, c)); + Console.Write(M2(s, c)); + Console.Write(M3(s, c)); + Console.Write(M4(s, c)); + } + + static string M1(string s, char c) => char.ToLowerInvariant(c) + s + s; + static string M2(string s, char c) => s + char.ToLowerInvariant(c) + s; + static string M3(string s, char c) => s + s + char.ToLowerInvariant(c); + static string M4(string s, char c) => char.ToLowerInvariant(c) + s + char.ToLowerInvariant(c); + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "cssscsssccsc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Test.M1", """ + { + // Code size 32 (0x20) + .maxstack 3 + .locals init (char V_0) + IL_0000: ldarg.1 + IL_0001: call "char char.ToLowerInvariant(char)" + IL_0006: stloc.0 + IL_0007: ldloca.s V_0 + IL_0009: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000e: ldarg.0 + IL_000f: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0014: ldarg.0 + IL_0015: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_001a: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001f: ret + } + """); + verifier.VerifyIL("Test.M2", """ + { + // Code size 32 (0x20) + .maxstack 3 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.1 + IL_0007: call "char char.ToLowerInvariant(char)" + IL_000c: stloc.0 + IL_000d: ldloca.s V_0 + IL_000f: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0014: ldarg.0 + IL_0015: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_001a: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001f: ret + } + """); + verifier.VerifyIL("Test.M3", """ + { + // Code size 32 (0x20) + .maxstack 3 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.0 + IL_0007: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000c: ldarg.1 + IL_000d: call "char char.ToLowerInvariant(char)" + IL_0012: stloc.0 + IL_0013: ldloca.s V_0 + IL_0015: newobj "System.ReadOnlySpan..ctor(in char)" + IL_001a: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001f: ret + } + """); + verifier.VerifyIL("Test.M4", """ + { + // Code size 40 (0x28) + .maxstack 3 + .locals init (char V_0, + char V_1) + IL_0000: ldarg.1 + IL_0001: call "char char.ToLowerInvariant(char)" + IL_0006: stloc.0 + IL_0007: ldloca.s V_0 + IL_0009: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000e: ldarg.0 + IL_000f: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0014: ldarg.1 + IL_0015: call "char char.ToLowerInvariant(char)" + IL_001a: stloc.1 + IL_001b: ldloca.s V_1 + IL_001d: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0022: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0027: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData("(s + c) + s")] + [InlineData("s + (c + s)")] + [InlineData("string.Concat(s, c.ToString()) + s")] + [InlineData("s + string.Concat(c.ToString(), s)")] + public void ConcatThree_ReadOnlySpan_OperandGroupingAndUserInputOfStringBasedConcats(string expression) + { + var source = $$""" + using System; + + public class Test + { + static void Main() + { + var s = "s"; + var c = 'c'; + Console.Write(M(s, c)); + } + + static string M(string s, char c) => {{expression}}; + } + """; + + var comp = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "scs" : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.M", """ + { + // Code size 27 (0x1b) + .maxstack 3 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.1 + IL_0007: stloc.0 + IL_0008: ldloca.s V_0 + IL_000a: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000f: ldarg.0 + IL_0010: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0015: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001a: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + public void ConcatThree_ReadOnlySpan_SideEffect(int? missingUnimportantWellKnownMember) + { + var source = """ + using System; + + public class Test + { + private static int stringCounter; + private static int charCounterPlusOne = 1; + + static void Main() + { + Console.WriteLine(M1()); + Console.WriteLine(M2()); + Console.WriteLine(M3()); + Console.WriteLine(M4()); + } + + static string M1() => GetCharWithSideEffect() + GetStringWithSideEffect() + GetStringWithSideEffect(); + static string M2() => GetStringWithSideEffect() + GetCharWithSideEffect() + GetStringWithSideEffect(); + static string M3() => GetStringWithSideEffect() + GetStringWithSideEffect() + GetCharWithSideEffect(); + static string M4() => GetCharWithSideEffect() + GetStringWithSideEffect() + GetCharWithSideEffect(); + + private static string GetStringWithSideEffect() + { + Console.Write(stringCounter++); + return "s"; + } + + private static char GetCharWithSideEffect() + { + Console.Write(charCounterPlusOne++); + return 'c'; + } + } + """; + + var expectedOutput = """ + 101css + 223scs + 453ssc + 465csc + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? expectedOutput : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Test.M1", """ + { + // Code size 39 (0x27) + .maxstack 3 + .locals init (char V_0) + IL_0000: call "char Test.GetCharWithSideEffect()" + IL_0005: stloc.0 + IL_0006: ldloca.s V_0 + IL_0008: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000d: call "string Test.GetStringWithSideEffect()" + IL_0012: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0017: call "string Test.GetStringWithSideEffect()" + IL_001c: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0021: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0026: ret + } + """); + verifier.VerifyIL("Test.M2", """ + { + // Code size 39 (0x27) + .maxstack 3 + .locals init (char V_0) + IL_0000: call "string Test.GetStringWithSideEffect()" + IL_0005: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000a: call "char Test.GetCharWithSideEffect()" + IL_000f: stloc.0 + IL_0010: ldloca.s V_0 + IL_0012: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0017: call "string Test.GetStringWithSideEffect()" + IL_001c: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0021: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0026: ret + } + """); + verifier.VerifyIL("Test.M3", """ + { + // Code size 39 (0x27) + .maxstack 3 + .locals init (char V_0) + IL_0000: call "string Test.GetStringWithSideEffect()" + IL_0005: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000a: call "string Test.GetStringWithSideEffect()" + IL_000f: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0014: call "char Test.GetCharWithSideEffect()" + IL_0019: stloc.0 + IL_001a: ldloca.s V_0 + IL_001c: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0021: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0026: ret + } + """); + verifier.VerifyIL("Test.M4", """ + { + // Code size 42 (0x2a) + .maxstack 3 + .locals init (char V_0, + char V_1) + IL_0000: call "char Test.GetCharWithSideEffect()" + IL_0005: stloc.0 + IL_0006: ldloca.s V_0 + IL_0008: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000d: call "string Test.GetStringWithSideEffect()" + IL_0012: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0017: call "char Test.GetCharWithSideEffect()" + IL_001c: stloc.1 + IL_001d: ldloca.s V_1 + IL_001f: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0024: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0029: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + public void ConcatThree_ReadOnlySpan_ReferenceToSameLocation(int? missingUnimportantWellKnownMember) + { + var source = """ + using System; + + var c = new C(); + c.M(); + + class C + { + public char c = 'a'; + + public ref char GetC() + { + c = 'b'; + return ref c; + } + + public void M() + { + Console.Write("a" + c + GetC()); + } + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "aab" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C.M", """ + { + // Code size 50 (0x32) + .maxstack 3 + .locals init (char V_0, + char V_1) + IL_0000: ldstr "a" + IL_0005: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000a: ldarg.0 + IL_000b: ldfld "char C.c" + IL_0010: stloc.0 + IL_0011: ldloca.s V_0 + IL_0013: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0018: ldarg.0 + IL_0019: call "ref char C.GetC()" + IL_001e: ldind.u2 + IL_001f: stloc.1 + IL_0020: ldloca.s V_1 + IL_0022: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0027: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_002c: call "void System.Console.Write(string)" + IL_0031: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + public void ConcatThree_ReadOnlySpan_MutateLocal(int? missingUnimportantWellKnownMember) + { + var source = """ + using System; + + var c = new C(); + Console.WriteLine(c.M()); + + class C + { + public string M() + { + var c = 'a'; + return "a" + c + SneakyLocalChange(ref c); + } + + private char SneakyLocalChange(ref char local) + { + local = 'b'; + return 'b'; + } + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "aab" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C.M", """ + { + // Code size 44 (0x2c) + .maxstack 4 + .locals init (char V_0, //c + char V_1, + char V_2) + IL_0000: ldc.i4.s 97 + IL_0002: stloc.0 + IL_0003: ldstr "a" + IL_0008: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000d: ldloc.0 + IL_000e: stloc.1 + IL_000f: ldloca.s V_1 + IL_0011: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0016: ldarg.0 + IL_0017: ldloca.s V_0 + IL_0019: call "char C.SneakyLocalChange(ref char)" + IL_001e: stloc.2 + IL_001f: ldloca.s V_2 + IL_0021: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0026: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_002b: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + public void ConcatThreeCharToStrings(int? missingUnimportantWellKnownMember) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var c1 = 'a'; + var c2 = 'b'; + var c3 = 'c'; + Console.Write(M(c1, c2, c3)); + } + + static string M(char c1, char c2, char c3) => c1.ToString() + c2.ToString() + c3.ToString(); + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Test.M", """ + { + // Code size 33 (0x21) + .maxstack 3 + .locals init (char V_0, + char V_1, + char V_2) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0009: ldarg.1 + IL_000a: stloc.1 + IL_000b: ldloca.s V_1 + IL_000d: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0012: ldarg.2 + IL_0013: stloc.2 + IL_0014: ldloca.s V_2 + IL_0016: newobj "System.ReadOnlySpan..ctor(in char)" + IL_001b: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0020: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + public void ConcatThree_ReadOnlySpan_MissingMemberForOptimization(int member) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s = "s"; + var c = 'c'; + Console.Write(M1(s, c)); + Console.Write(M2(s, c)); + Console.Write(M3(s, c)); + Console.Write(M4(s, c)); + } + + static string M1(string s, char c) => c + s + s; + static string M2(string s, char c) => s + c + s; + static string M3(string s, char c) => s + s + c; + static string M4(string s, char c) => c + s + c; + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + comp.MakeMemberMissing((WellKnownMember)member); + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "cssscsssccsc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Test.M1", """ + { + // Code size 15 (0xf) + .maxstack 3 + IL_0000: ldarga.s V_1 + IL_0002: call "string char.ToString()" + IL_0007: ldarg.0 + IL_0008: ldarg.0 + IL_0009: call "string string.Concat(string, string, string)" + IL_000e: ret + } + """); + verifier.VerifyIL("Test.M2", """ + { + // Code size 15 (0xf) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: ldarga.s V_1 + IL_0003: call "string char.ToString()" + IL_0008: ldarg.0 + IL_0009: call "string string.Concat(string, string, string)" + IL_000e: ret + } + """); + verifier.VerifyIL("Test.M3", """ + { + // Code size 15 (0xf) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: ldarg.0 + IL_0002: ldarga.s V_1 + IL_0004: call "string char.ToString()" + IL_0009: call "string string.Concat(string, string, string)" + IL_000e: ret + } + """); + verifier.VerifyIL("Test.M4", """ + { + // Code size 21 (0x15) + .maxstack 3 + IL_0000: ldarga.s V_1 + IL_0002: call "string char.ToString()" + IL_0007: ldarg.0 + IL_0008: ldarga.s V_1 + IL_000a: call "string char.ToString()" + IL_000f: call "string string.Concat(string, string, string)" + IL_0014: ret + } + """); + } } From 9e10fe6acd235120574e97f0a20ded92c75de5aa Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Fri, 12 Jan 2024 00:14:05 +0300 Subject: [PATCH 05/71] Optimize for 4 arguments --- .../LocalRewriter_StringConcat.cs | 36 + .../CodeGenSpanBasedStringConcatTests.cs | 1012 ++++++++++++++++- 2 files changed, 1041 insertions(+), 7 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index c37143ef99414..91a184ce74042 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -185,6 +185,42 @@ private BoundExpression RewriteStringConcatenation(SyntaxNode syntax, BinaryOper var second = leftFlattened[1]; var third = leftFlattened[2]; var fourth = leftFlattened[3]; + + var firstIsCharToString = isCharToString(first, objectToStringMethod, ref charType, out BoundExpression? unwrappedFirstChar); + var secondIsCharToString = isCharToString(second, objectToStringMethod, ref charType, out BoundExpression? unwrappedSecondChar); + var thirdIsCharToString = isCharToString(third, objectToStringMethod, ref charType, out BoundExpression? unwrappedThirdChar); + var fourthIsCharToString = isCharToString(fourth, objectToStringMethod, ref charType, out BoundExpression? unwrappedFourthChar); + + if ((firstIsCharToString || secondIsCharToString || thirdIsCharToString || fourthIsCharToString) && + TryGetWellKnownTypeMember(syntax, WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan, out MethodSymbol? concat4Spans, isOptional: true) && + TryGetWellKnownTypeMember(syntax, WellKnownMember.System_ReadOnlySpan_T__ctor_Reference, out MethodSymbol? readOnlySpanCtorRefParamGeneric, isOptional: true)) + { + // One of args is a char (or rather `someChar.ToString()`, but we've unwrapped `someChar` back from that call). We can use span-based concatenation, which takes a bit more IL, but avoids allocation of an intermidiate string from char.ToString call. + // And since implicit conversion from string to span is a JIT intrinsic, the resulting code ends up being faster than the one generated from the "naive" case with char.ToString call + Debug.Assert(charType is not null); + var readOnlySpanOfChar = readOnlySpanCtorRefParamGeneric.ContainingType.Construct(charType); + var readOnlySpanCtorRefParamChar = readOnlySpanCtorRefParamGeneric.AsMember(readOnlySpanOfChar); + + MethodSymbol? stringImplicitConversionToReadOnlySpan = null; + + // Technically we can get `char1.ToString() + char2.ToString() + char3.ToString() + char4.ToString()` as a user input. In such case we don't need implicit conversion method `string -> ReadOnlySpan` + if ((firstIsCharToString && secondIsCharToString && thirdIsCharToString && fourthIsCharToString) || + TryGetWellKnownTypeMember(syntax, WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar, out stringImplicitConversionToReadOnlySpan, isOptional: true)) + { + result = RewriteStringConcatenationWithSpanBasedConcat( + syntax, + concat4Spans, + stringImplicitConversionToReadOnlySpan, + readOnlySpanCtorRefParamChar, + firstIsCharToString ? unwrappedFirstChar! : first, + secondIsCharToString ? unwrappedSecondChar! : second, + thirdIsCharToString ? unwrappedThirdChar! : third, + fourthIsCharToString ? unwrappedFourthChar! : fourth); + + break; + } + } + result = RewriteStringConcatenationFourExprs(syntax, first, second, third, fourth); } break; diff --git a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs index 1f14dce535ac4..30c629cea2f76 100644 --- a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs @@ -237,7 +237,7 @@ static void Main() // Code size 24 (0x18) .maxstack 2 .locals init (char V_0, - char V_1) + char V_1) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldloca.s V_0 @@ -399,7 +399,7 @@ .locals init (char V_0) // Code size 30 (0x1e) .maxstack 3 .locals init (char V_0, - char V_1) + char V_1) IL_0000: ldarg.1 IL_0001: stloc.0 IL_0002: ldloca.s V_0 @@ -512,7 +512,7 @@ .locals init (char V_0) // Code size 40 (0x28) .maxstack 3 .locals init (char V_0, - char V_1) + char V_1) IL_0000: ldarg.1 IL_0001: call "char char.ToLowerInvariant(char)" IL_0006: stloc.0 @@ -689,7 +689,7 @@ .locals init (char V_0) // Code size 42 (0x2a) .maxstack 3 .locals init (char V_0, - char V_1) + char V_1) IL_0000: call "char Test.GetCharWithSideEffect()" IL_0005: stloc.0 IL_0006: ldloca.s V_0 @@ -749,7 +749,7 @@ public void M() // Code size 50 (0x32) .maxstack 3 .locals init (char V_0, - char V_1) + char V_1) IL_0000: ldstr "a" IL_0005: call "System.ReadOnlySpan string.op_Implicit(string)" IL_000a: ldarg.0 @@ -812,8 +812,8 @@ private char SneakyLocalChange(ref char local) // Code size 44 (0x2c) .maxstack 4 .locals init (char V_0, //c - char V_1, - char V_2) + char V_1, + char V_2) IL_0000: ldc.i4.s 97 IL_0002: stloc.0 IL_0003: ldstr "a" @@ -976,4 +976,1002 @@ .maxstack 3 } """); } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + public void ConcatFour_ReadOnlySpan1(int? missingUnimportantWellKnownMember) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s = "s"; + var c = 'c'; + Console.Write(M1(s, c)); + Console.Write(M2(s, c)); + Console.Write(M3(s, c)); + Console.Write(M4(s, c)); + Console.Write(M5(s, c)); + Console.Write(M6(s, c)); + Console.Write(M7(s, c)); + } + + static string M1(string s, char c) => c + s + s + s; + static string M2(string s, char c) => s + c + s + s; + static string M3(string s, char c) => s + s + c + s; + static string M4(string s, char c) => s + s + s + c; + static string M5(string s, char c) => c + s + c + s; + static string M6(string s, char c) => s + c + s + c; + static string M7(string s, char c) => c + s + s + c; + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "csssscsssscssssccscsscsccssc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Test.M1", """ + { + // Code size 33 (0x21) + .maxstack 4 + .locals init (char V_0) + IL_0000: ldarg.1 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0009: ldarg.0 + IL_000a: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000f: ldarg.0 + IL_0010: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0015: ldarg.0 + IL_0016: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_001b: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0020: ret + } + """); + verifier.VerifyIL("Test.M2", """ + { + // Code size 33 (0x21) + .maxstack 4 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.1 + IL_0007: stloc.0 + IL_0008: ldloca.s V_0 + IL_000a: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000f: ldarg.0 + IL_0010: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0015: ldarg.0 + IL_0016: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_001b: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0020: ret + } + """); + verifier.VerifyIL("Test.M3", """ + { + // Code size 33 (0x21) + .maxstack 4 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.0 + IL_0007: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000c: ldarg.1 + IL_000d: stloc.0 + IL_000e: ldloca.s V_0 + IL_0010: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0015: ldarg.0 + IL_0016: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_001b: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0020: ret + } + """); + verifier.VerifyIL("Test.M4", """ + { + // Code size 33 (0x21) + .maxstack 4 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.0 + IL_0007: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000c: ldarg.0 + IL_000d: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0012: ldarg.1 + IL_0013: stloc.0 + IL_0014: ldloca.s V_0 + IL_0016: newobj "System.ReadOnlySpan..ctor(in char)" + IL_001b: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0020: ret + } + """); + verifier.VerifyIL("Test.M5", """ + { + // Code size 36 (0x24) + .maxstack 4 + .locals init (char V_0, + char V_1) + IL_0000: ldarg.1 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0009: ldarg.0 + IL_000a: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000f: ldarg.1 + IL_0010: stloc.1 + IL_0011: ldloca.s V_1 + IL_0013: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0018: ldarg.0 + IL_0019: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_001e: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0023: ret + } + """); + verifier.VerifyIL("Test.M6", """ + { + // Code size 36 (0x24) + .maxstack 4 + .locals init (char V_0, + char V_1) + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.1 + IL_0007: stloc.0 + IL_0008: ldloca.s V_0 + IL_000a: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000f: ldarg.0 + IL_0010: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0015: ldarg.1 + IL_0016: stloc.1 + IL_0017: ldloca.s V_1 + IL_0019: newobj "System.ReadOnlySpan..ctor(in char)" + IL_001e: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0023: ret + } + """); + verifier.VerifyIL("Test.M7", """ + { + // Code size 36 (0x24) + .maxstack 4 + .locals init (char V_0, + char V_1) + IL_0000: ldarg.1 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0009: ldarg.0 + IL_000a: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000f: ldarg.0 + IL_0010: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0015: ldarg.1 + IL_0016: stloc.1 + IL_0017: ldloca.s V_1 + IL_0019: newobj "System.ReadOnlySpan..ctor(in char)" + IL_001e: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0023: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + public void ConcatFour_ReadOnlySpan2(int? missingUnimportantWellKnownMember) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s = "s"; + var c = 'C'; + Console.Write(M1(s, c)); + Console.Write(M2(s, c)); + Console.Write(M3(s, c)); + Console.Write(M4(s, c)); + Console.Write(M5(s, c)); + Console.Write(M6(s, c)); + Console.Write(M7(s, c)); + } + + static string M1(string s, char c) => char.ToLowerInvariant(c) + s + s + s; + static string M2(string s, char c) => s + char.ToLowerInvariant(c) + s + s; + static string M3(string s, char c) => s + s + char.ToLowerInvariant(c) + s; + static string M4(string s, char c) => s + s + s + char.ToLowerInvariant(c); + static string M5(string s, char c) => char.ToLowerInvariant(c) + s + char.ToLowerInvariant(c) + s; + static string M6(string s, char c) => s + char.ToLowerInvariant(c) + s + char.ToLowerInvariant(c); + static string M7(string s, char c) => char.ToLowerInvariant(c) + s + s + char.ToLowerInvariant(c); + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "csssscsssscssssccscsscsccssc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Test.M1", """ + { + // Code size 38 (0x26) + .maxstack 4 + .locals init (char V_0) + IL_0000: ldarg.1 + IL_0001: call "char char.ToLowerInvariant(char)" + IL_0006: stloc.0 + IL_0007: ldloca.s V_0 + IL_0009: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000e: ldarg.0 + IL_000f: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0014: ldarg.0 + IL_0015: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_001a: ldarg.0 + IL_001b: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0020: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0025: ret + } + """); + verifier.VerifyIL("Test.M2", """ + { + // Code size 38 (0x26) + .maxstack 4 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.1 + IL_0007: call "char char.ToLowerInvariant(char)" + IL_000c: stloc.0 + IL_000d: ldloca.s V_0 + IL_000f: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0014: ldarg.0 + IL_0015: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_001a: ldarg.0 + IL_001b: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0020: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0025: ret + } + """); + verifier.VerifyIL("Test.M3", """ + { + // Code size 38 (0x26) + .maxstack 4 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.0 + IL_0007: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000c: ldarg.1 + IL_000d: call "char char.ToLowerInvariant(char)" + IL_0012: stloc.0 + IL_0013: ldloca.s V_0 + IL_0015: newobj "System.ReadOnlySpan..ctor(in char)" + IL_001a: ldarg.0 + IL_001b: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0020: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0025: ret + } + """); + verifier.VerifyIL("Test.M4", """ + { + // Code size 38 (0x26) + .maxstack 4 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.0 + IL_0007: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000c: ldarg.0 + IL_000d: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0012: ldarg.1 + IL_0013: call "char char.ToLowerInvariant(char)" + IL_0018: stloc.0 + IL_0019: ldloca.s V_0 + IL_001b: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0020: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0025: ret + } + """); + verifier.VerifyIL("Test.M5", """ + { + // Code size 46 (0x2e) + .maxstack 4 + .locals init (char V_0, + char V_1) + IL_0000: ldarg.1 + IL_0001: call "char char.ToLowerInvariant(char)" + IL_0006: stloc.0 + IL_0007: ldloca.s V_0 + IL_0009: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000e: ldarg.0 + IL_000f: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0014: ldarg.1 + IL_0015: call "char char.ToLowerInvariant(char)" + IL_001a: stloc.1 + IL_001b: ldloca.s V_1 + IL_001d: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0022: ldarg.0 + IL_0023: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0028: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_002d: ret + } + """); + verifier.VerifyIL("Test.M6", """ + { + // Code size 46 (0x2e) + .maxstack 4 + .locals init (char V_0, + char V_1) + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.1 + IL_0007: call "char char.ToLowerInvariant(char)" + IL_000c: stloc.0 + IL_000d: ldloca.s V_0 + IL_000f: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0014: ldarg.0 + IL_0015: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_001a: ldarg.1 + IL_001b: call "char char.ToLowerInvariant(char)" + IL_0020: stloc.1 + IL_0021: ldloca.s V_1 + IL_0023: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0028: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_002d: ret + } + """); + verifier.VerifyIL("Test.M7", """ + { + // Code size 46 (0x2e) + .maxstack 4 + .locals init (char V_0, + char V_1) + IL_0000: ldarg.1 + IL_0001: call "char char.ToLowerInvariant(char)" + IL_0006: stloc.0 + IL_0007: ldloca.s V_0 + IL_0009: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000e: ldarg.0 + IL_000f: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0014: ldarg.0 + IL_0015: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_001a: ldarg.1 + IL_001b: call "char char.ToLowerInvariant(char)" + IL_0020: stloc.1 + IL_0021: ldloca.s V_1 + IL_0023: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0028: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_002d: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData("(s + c) + s + s")] + [InlineData("s + (c + s) + s")] + [InlineData("s + c + (s + s)")] + [InlineData("(s + c + s) + s")] + [InlineData("s + (c + s + s)")] + [InlineData("string.Concat(s, c.ToString()) + s + s")] + [InlineData("s + string.Concat(c.ToString(), s) + s")] + [InlineData("s + c + string.Concat(s, s)")] + [InlineData("string.Concat(s, c.ToString(), s) + s")] + [InlineData("s + string.Concat(c.ToString(), s, s)")] + public void ConcatFour_ReadOnlySpan_OperandGroupingAndUserInputOfStringBasedConcats(string expression) + { + var source = $$""" + using System; + + public class Test + { + static void Main() + { + var s = "s"; + var c = 'c'; + Console.Write(M(s, c)); + } + + static string M(string s, char c) => {{expression}}; + } + """; + + var comp = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "scss" : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.M", """ + { + // Code size 33 (0x21) + .maxstack 4 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.1 + IL_0007: stloc.0 + IL_0008: ldloca.s V_0 + IL_000a: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000f: ldarg.0 + IL_0010: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0015: ldarg.0 + IL_0016: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_001b: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0020: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + public void ConcatFour_ReadOnlySpan_SideEffect(int? missingUnimportantWellKnownMember) + { + var source = """ + using System; + + public class Test + { + private static int stringCounter; + private static int charCounterPlusOne = 1; + + static void Main() + { + Console.WriteLine(M1()); + Console.WriteLine(M2()); + Console.WriteLine(M3()); + Console.WriteLine(M4()); + Console.WriteLine(M5()); + Console.WriteLine(M6()); + Console.WriteLine(M7()); + } + + static string M1() => GetCharWithSideEffect() + GetStringWithSideEffect() + GetStringWithSideEffect() + GetStringWithSideEffect(); + static string M2() => GetStringWithSideEffect() + GetCharWithSideEffect() + GetStringWithSideEffect() + GetStringWithSideEffect(); + static string M3() => GetStringWithSideEffect() + GetStringWithSideEffect() + GetCharWithSideEffect() + GetStringWithSideEffect(); + static string M4() => GetStringWithSideEffect() + GetStringWithSideEffect() + GetStringWithSideEffect() + GetCharWithSideEffect(); + static string M5() => GetCharWithSideEffect() + GetStringWithSideEffect() + GetCharWithSideEffect() + GetStringWithSideEffect(); + static string M6() => GetStringWithSideEffect() + GetCharWithSideEffect() + GetStringWithSideEffect() + GetCharWithSideEffect(); + static string M7() => GetCharWithSideEffect() + GetStringWithSideEffect() + GetStringWithSideEffect() + GetCharWithSideEffect(); + + private static string GetStringWithSideEffect() + { + Console.Write(stringCounter++); + return "s"; + } + + private static char GetCharWithSideEffect() + { + Console.Write(charCounterPlusOne++); + return 'c'; + } + } + """; + + var expectedOutput = """ + 1012csss + 3245scss + 6738sscs + 910114sssc + 512613cscs + 147158scsc + 9161710cssc + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? expectedOutput : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Test.M1", """ + { + // Code size 49 (0x31) + .maxstack 4 + .locals init (char V_0) + IL_0000: call "char Test.GetCharWithSideEffect()" + IL_0005: stloc.0 + IL_0006: ldloca.s V_0 + IL_0008: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000d: call "string Test.GetStringWithSideEffect()" + IL_0012: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0017: call "string Test.GetStringWithSideEffect()" + IL_001c: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0021: call "string Test.GetStringWithSideEffect()" + IL_0026: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_002b: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0030: ret + } + """); + verifier.VerifyIL("Test.M2", """ + { + // Code size 49 (0x31) + .maxstack 4 + .locals init (char V_0) + IL_0000: call "string Test.GetStringWithSideEffect()" + IL_0005: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000a: call "char Test.GetCharWithSideEffect()" + IL_000f: stloc.0 + IL_0010: ldloca.s V_0 + IL_0012: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0017: call "string Test.GetStringWithSideEffect()" + IL_001c: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0021: call "string Test.GetStringWithSideEffect()" + IL_0026: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_002b: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0030: ret + } + """); + verifier.VerifyIL("Test.M3", """ + { + // Code size 49 (0x31) + .maxstack 4 + .locals init (char V_0) + IL_0000: call "string Test.GetStringWithSideEffect()" + IL_0005: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000a: call "string Test.GetStringWithSideEffect()" + IL_000f: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0014: call "char Test.GetCharWithSideEffect()" + IL_0019: stloc.0 + IL_001a: ldloca.s V_0 + IL_001c: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0021: call "string Test.GetStringWithSideEffect()" + IL_0026: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_002b: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0030: ret + } + """); + verifier.VerifyIL("Test.M4", """ + { + // Code size 49 (0x31) + .maxstack 4 + .locals init (char V_0) + IL_0000: call "string Test.GetStringWithSideEffect()" + IL_0005: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000a: call "string Test.GetStringWithSideEffect()" + IL_000f: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0014: call "string Test.GetStringWithSideEffect()" + IL_0019: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_001e: call "char Test.GetCharWithSideEffect()" + IL_0023: stloc.0 + IL_0024: ldloca.s V_0 + IL_0026: newobj "System.ReadOnlySpan..ctor(in char)" + IL_002b: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0030: ret + } + """); + verifier.VerifyIL("Test.M5", """ + { + // Code size 52 (0x34) + .maxstack 4 + .locals init (char V_0, + char V_1) + IL_0000: call "char Test.GetCharWithSideEffect()" + IL_0005: stloc.0 + IL_0006: ldloca.s V_0 + IL_0008: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000d: call "string Test.GetStringWithSideEffect()" + IL_0012: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0017: call "char Test.GetCharWithSideEffect()" + IL_001c: stloc.1 + IL_001d: ldloca.s V_1 + IL_001f: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0024: call "string Test.GetStringWithSideEffect()" + IL_0029: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_002e: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0033: ret + } + """); + verifier.VerifyIL("Test.M6", """ + { + // Code size 52 (0x34) + .maxstack 4 + .locals init (char V_0, + char V_1) + IL_0000: call "string Test.GetStringWithSideEffect()" + IL_0005: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000a: call "char Test.GetCharWithSideEffect()" + IL_000f: stloc.0 + IL_0010: ldloca.s V_0 + IL_0012: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0017: call "string Test.GetStringWithSideEffect()" + IL_001c: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0021: call "char Test.GetCharWithSideEffect()" + IL_0026: stloc.1 + IL_0027: ldloca.s V_1 + IL_0029: newobj "System.ReadOnlySpan..ctor(in char)" + IL_002e: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0033: ret + } + """); + verifier.VerifyIL("Test.M7", """ + { + // Code size 52 (0x34) + .maxstack 4 + .locals init (char V_0, + char V_1) + IL_0000: call "char Test.GetCharWithSideEffect()" + IL_0005: stloc.0 + IL_0006: ldloca.s V_0 + IL_0008: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000d: call "string Test.GetStringWithSideEffect()" + IL_0012: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0017: call "string Test.GetStringWithSideEffect()" + IL_001c: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0021: call "char Test.GetCharWithSideEffect()" + IL_0026: stloc.1 + IL_0027: ldloca.s V_1 + IL_0029: newobj "System.ReadOnlySpan..ctor(in char)" + IL_002e: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0033: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + public void ConcatFour_ReadOnlySpan_ReferenceToSameLocation(int? missingUnimportantWellKnownMember) + { + var source = """ + using System; + + var c = new C(); + c.M(); + + class C + { + public char c = 'a'; + + public ref char GetC() => ref c; + + public ref char GetC2() + { + c = 'b'; + return ref c; + } + + public void M() + { + Console.Write("a" + c + GetC() + GetC2()); + } + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "aaab" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C.M", """ + { + // Code size 65 (0x41) + .maxstack 4 + .locals init (char V_0, + char V_1, + char V_2) + IL_0000: ldstr "a" + IL_0005: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000a: ldarg.0 + IL_000b: ldfld "char C.c" + IL_0010: stloc.0 + IL_0011: ldloca.s V_0 + IL_0013: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0018: ldarg.0 + IL_0019: call "ref char C.GetC()" + IL_001e: ldind.u2 + IL_001f: stloc.1 + IL_0020: ldloca.s V_1 + IL_0022: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0027: ldarg.0 + IL_0028: call "ref char C.GetC2()" + IL_002d: ldind.u2 + IL_002e: stloc.2 + IL_002f: ldloca.s V_2 + IL_0031: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0036: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_003b: call "void System.Console.Write(string)" + IL_0040: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + public void ConcatFour_ReadOnlySpan_MutateLocal(int? missingUnimportantWellKnownMember) + { + var source = """ + using System; + + var c = new C(); + Console.WriteLine(c.M()); + + class C + { + public string M() + { + var c1 = 'a'; + var c2 = 'a'; + return c1 + SneakyLocalChange(ref c1) + c2 + SneakyLocalChange(ref c2); + } + + private string SneakyLocalChange(ref char local) + { + local = 'b'; + return "b"; + } + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abab" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C.M", """ + { + // Code size 56 (0x38) + .maxstack 5 + .locals init (char V_0, //c1 + char V_1, //c2 + char V_2, + char V_3) + IL_0000: ldc.i4.s 97 + IL_0002: stloc.0 + IL_0003: ldc.i4.s 97 + IL_0005: stloc.1 + IL_0006: ldloc.0 + IL_0007: stloc.2 + IL_0008: ldloca.s V_2 + IL_000a: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000f: ldarg.0 + IL_0010: ldloca.s V_0 + IL_0012: call "string C.SneakyLocalChange(ref char)" + IL_0017: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_001c: ldloc.1 + IL_001d: stloc.3 + IL_001e: ldloca.s V_3 + IL_0020: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0025: ldarg.0 + IL_0026: ldloca.s V_1 + IL_0028: call "string C.SneakyLocalChange(ref char)" + IL_002d: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0032: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0037: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + public void ConcatFourCharToStrings(int? missingUnimportantWellKnownMember) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var c1 = 'a'; + var c2 = 'b'; + var c3 = 'c'; + var c4 = 'd'; + Console.Write(M(c1, c2, c3, c4)); + } + + static string M(char c1, char c2, char c3, char c4) => c1.ToString() + c2.ToString() + c3.ToString() + c4.ToString(); + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abcd" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Test.M", """ + { + // Code size 42 (0x2a) + .maxstack 4 + .locals init (char V_0, + char V_1, + char V_2, + char V_3) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0009: ldarg.1 + IL_000a: stloc.1 + IL_000b: ldloca.s V_1 + IL_000d: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0012: ldarg.2 + IL_0013: stloc.2 + IL_0014: ldloca.s V_2 + IL_0016: newobj "System.ReadOnlySpan..ctor(in char)" + IL_001b: ldarg.3 + IL_001c: stloc.3 + IL_001d: ldloca.s V_3 + IL_001f: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0024: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0029: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + public void ConcatFour_ReadOnlySpan_MissingMemberForOptimization(int member) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s = "s"; + var c = 'c'; + Console.Write(M1(s, c)); + Console.Write(M2(s, c)); + Console.Write(M3(s, c)); + Console.Write(M4(s, c)); + Console.Write(M5(s, c)); + Console.Write(M6(s, c)); + Console.Write(M7(s, c)); + } + + static string M1(string s, char c) => c + s + s + s; + static string M2(string s, char c) => s + c + s + s; + static string M3(string s, char c) => s + s + c + s; + static string M4(string s, char c) => s + s + s + c; + static string M5(string s, char c) => c + s + c + s; + static string M6(string s, char c) => s + c + s + c; + static string M7(string s, char c) => c + s + s + c; + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + comp.MakeMemberMissing((WellKnownMember)member); + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "csssscsssscssssccscsscsccssc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Test.M1", """ + { + // Code size 16 (0x10) + .maxstack 4 + IL_0000: ldarga.s V_1 + IL_0002: call "string char.ToString()" + IL_0007: ldarg.0 + IL_0008: ldarg.0 + IL_0009: ldarg.0 + IL_000a: call "string string.Concat(string, string, string, string)" + IL_000f: ret + } + """); + verifier.VerifyIL("Test.M2", """ + { + // Code size 16 (0x10) + .maxstack 4 + IL_0000: ldarg.0 + IL_0001: ldarga.s V_1 + IL_0003: call "string char.ToString()" + IL_0008: ldarg.0 + IL_0009: ldarg.0 + IL_000a: call "string string.Concat(string, string, string, string)" + IL_000f: ret + } + """); + verifier.VerifyIL("Test.M3", """ + { + // Code size 16 (0x10) + .maxstack 4 + IL_0000: ldarg.0 + IL_0001: ldarg.0 + IL_0002: ldarga.s V_1 + IL_0004: call "string char.ToString()" + IL_0009: ldarg.0 + IL_000a: call "string string.Concat(string, string, string, string)" + IL_000f: ret + } + """); + verifier.VerifyIL("Test.M4", """ + { + // Code size 16 (0x10) + .maxstack 4 + IL_0000: ldarg.0 + IL_0001: ldarg.0 + IL_0002: ldarg.0 + IL_0003: ldarga.s V_1 + IL_0005: call "string char.ToString()" + IL_000a: call "string string.Concat(string, string, string, string)" + IL_000f: ret + } + """); + verifier.VerifyIL("Test.M5", """ + { + // Code size 22 (0x16) + .maxstack 4 + IL_0000: ldarga.s V_1 + IL_0002: call "string char.ToString()" + IL_0007: ldarg.0 + IL_0008: ldarga.s V_1 + IL_000a: call "string char.ToString()" + IL_000f: ldarg.0 + IL_0010: call "string string.Concat(string, string, string, string)" + IL_0015: ret + } + """); + verifier.VerifyIL("Test.M6", """ + { + // Code size 22 (0x16) + .maxstack 4 + IL_0000: ldarg.0 + IL_0001: ldarga.s V_1 + IL_0003: call "string char.ToString()" + IL_0008: ldarg.0 + IL_0009: ldarga.s V_1 + IL_000b: call "string char.ToString()" + IL_0010: call "string string.Concat(string, string, string, string)" + IL_0015: ret + } + """); + verifier.VerifyIL("Test.M7", """ + { + // Code size 22 (0x16) + .maxstack 4 + IL_0000: ldarga.s V_1 + IL_0002: call "string char.ToString()" + IL_0007: ldarg.0 + IL_0008: ldarg.0 + IL_0009: ldarga.s V_1 + IL_000b: call "string char.ToString()" + IL_0010: call "string string.Concat(string, string, string, string)" + IL_0015: ret + } + """); + } } From db98d9f36e8039c1b6d34d62fa0f8c0127674ed6 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Fri, 12 Jan 2024 17:28:54 +0300 Subject: [PATCH 06/71] Test concat of 5 chars --- .../CodeGenSpanBasedStringConcatTests.cs | 264 ++++++++++++++++++ 1 file changed, 264 insertions(+) diff --git a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs index 30c629cea2f76..ce579989be721 100644 --- a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs @@ -1974,4 +1974,268 @@ .maxstack 4 } """); } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + public void ConcatFive_Char(int? missingUnimportantWellKnownMember) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s = "s"; + var c = 'c'; + Console.Write(CharInFirstFourArgs(s, c)); + Console.Write(CharAfterFirstFourArgs(s, c)); + } + + static string CharInFirstFourArgs(string s, char c) => s + c + s + s + s; + static string CharAfterFirstFourArgs(string s, char c) => s + s + s + s + c; + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "scsssssssc" : null, verify: ExecutionConditionUtil.IsCoreClr ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + + // When lengths of inputs are low it is actually more optimal to first concat 4 operands with span-based concat and then concat that result with the remaining operand. + // However when inputs become large enough cost of allocating a params array becomes lower than cost of creating an intermediate string. + // + code size for using params array is less than code size from intermediate concat approach, especially when the amount of operands is high. + // So in the end we always prefer overload with params array when there are 5+ operands + verifier.VerifyIL("Test.CharInFirstFourArgs", """ + { + // Code size 38 (0x26) + .maxstack 4 + IL_0000: ldc.i4.5 + IL_0001: newarr "string" + IL_0006: dup + IL_0007: ldc.i4.0 + IL_0008: ldarg.0 + IL_0009: stelem.ref + IL_000a: dup + IL_000b: ldc.i4.1 + IL_000c: ldarga.s V_1 + IL_000e: call "string char.ToString()" + IL_0013: stelem.ref + IL_0014: dup + IL_0015: ldc.i4.2 + IL_0016: ldarg.0 + IL_0017: stelem.ref + IL_0018: dup + IL_0019: ldc.i4.3 + IL_001a: ldarg.0 + IL_001b: stelem.ref + IL_001c: dup + IL_001d: ldc.i4.4 + IL_001e: ldarg.0 + IL_001f: stelem.ref + IL_0020: call "string string.Concat(params string[])" + IL_0025: ret + } + """); + verifier.VerifyIL("Test.CharAfterFirstFourArgs", """ + { + // Code size 38 (0x26) + .maxstack 4 + IL_0000: ldc.i4.5 + IL_0001: newarr "string" + IL_0006: dup + IL_0007: ldc.i4.0 + IL_0008: ldarg.0 + IL_0009: stelem.ref + IL_000a: dup + IL_000b: ldc.i4.1 + IL_000c: ldarg.0 + IL_000d: stelem.ref + IL_000e: dup + IL_000f: ldc.i4.2 + IL_0010: ldarg.0 + IL_0011: stelem.ref + IL_0012: dup + IL_0013: ldc.i4.3 + IL_0014: ldarg.0 + IL_0015: stelem.ref + IL_0016: dup + IL_0017: ldc.i4.4 + IL_0018: ldarga.s V_1 + IL_001a: call "string char.ToString()" + IL_001f: stelem.ref + IL_0020: call "string string.Concat(params string[])" + IL_0025: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData("(s + s) + c + s + s")] + [InlineData("s + (s + c) + s + s")] + [InlineData("s + s + (c + s) + s")] + [InlineData("s + s + c + (s + s)")] + [InlineData("(s + s + c) + s + s")] + [InlineData("s + (s + c + s) + s")] + [InlineData("s + s + (c + s + s)")] + [InlineData("(s + s + c + s) + s")] + [InlineData("s + (s + c + s + s)")] + [InlineData("(s + s) + (c + s) + s")] + [InlineData("(s + s) + c + (s + s)")] + [InlineData("s + (s + c) + (s + s)")] + [InlineData("(s + s + c) + (s + s)")] + [InlineData("(s + s) + (c + s + s)")] + [InlineData("string.Concat(s, s) + c + s + s")] + [InlineData("s + string.Concat(s, c.ToString()) + s + s")] + [InlineData("s + s + string.Concat(c.ToString(), s) + s")] + [InlineData("s + s + c + string.Concat(s, s)")] + [InlineData("string.Concat(s, s, c.ToString()) + s + s")] + [InlineData("s + string.Concat(s, c.ToString(), s) + s")] + [InlineData("s + s + string.Concat(c.ToString(), s, s)")] + [InlineData("string.Concat(s, s, c.ToString(), s) + s")] + [InlineData("s + string.Concat(s, c.ToString(), s, s)")] + [InlineData("string.Concat(s, s) + string.Concat(c.ToString(), s) + s")] + [InlineData("string.Concat(s, s) + c + string.Concat(s, s)")] + [InlineData("s + string.Concat(s, c.ToString()) + string.Concat(s, s)")] + [InlineData("string.Concat(s, s, c.ToString()) + string.Concat(s, s)")] + [InlineData("string.Concat(s, s) + string.Concat(c.ToString(), s, s)")] + public void ConcatFive_Char_OperandGroupingAndUserInputOfStringBasedConcats(string expression) + { + var source = $$""" + using System; + + public class Test + { + static void Main() + { + var s = "s"; + var c = 'c'; + Console.Write(M(s, c)); + } + + static string M(string s, char c) => {{expression}}; + } + """; + + var comp = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "sscss" : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.M", """ + { + // Code size 38 (0x26) + .maxstack 4 + IL_0000: ldc.i4.5 + IL_0001: newarr "string" + IL_0006: dup + IL_0007: ldc.i4.0 + IL_0008: ldarg.0 + IL_0009: stelem.ref + IL_000a: dup + IL_000b: ldc.i4.1 + IL_000c: ldarg.0 + IL_000d: stelem.ref + IL_000e: dup + IL_000f: ldc.i4.2 + IL_0010: ldarga.s V_1 + IL_0012: call "string char.ToString()" + IL_0017: stelem.ref + IL_0018: dup + IL_0019: ldc.i4.3 + IL_001a: ldarg.0 + IL_001b: stelem.ref + IL_001c: dup + IL_001d: ldc.i4.4 + IL_001e: ldarg.0 + IL_001f: stelem.ref + IL_0020: call "string string.Concat(params string[])" + IL_0025: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + public void ConcatFiveCharToStrings(int? missingUnimportantWellKnownMember) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var c1 = 'a'; + var c2 = 'b'; + var c3 = 'c'; + var c4 = 'd'; + var c5 = 'e'; + Console.Write(M(c1, c2, c3, c4, c5)); + } + + static string M(char c1, char c2, char c3, char c4, char c5) => c1.ToString() + c2.ToString() + c3.ToString() + c4.ToString() + c5.ToString(); + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abcde" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Test.M", """ + { + // Code size 62 (0x3e) + .maxstack 4 + IL_0000: ldc.i4.5 + IL_0001: newarr "string" + IL_0006: dup + IL_0007: ldc.i4.0 + IL_0008: ldarga.s V_0 + IL_000a: call "string char.ToString()" + IL_000f: stelem.ref + IL_0010: dup + IL_0011: ldc.i4.1 + IL_0012: ldarga.s V_1 + IL_0014: call "string char.ToString()" + IL_0019: stelem.ref + IL_001a: dup + IL_001b: ldc.i4.2 + IL_001c: ldarga.s V_2 + IL_001e: call "string char.ToString()" + IL_0023: stelem.ref + IL_0024: dup + IL_0025: ldc.i4.3 + IL_0026: ldarga.s V_3 + IL_0028: call "string char.ToString()" + IL_002d: stelem.ref + IL_002e: dup + IL_002f: ldc.i4.4 + IL_0030: ldarga.s V_4 + IL_0032: call "string char.ToString()" + IL_0037: stelem.ref + IL_0038: call "string string.Concat(params string[])" + IL_003d: ret + } + """); + } } From bfb7388a00572634cc0aa33d886e58205a6add43 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Fri, 12 Jan 2024 18:29:13 +0300 Subject: [PATCH 07/71] Add missing grouping cases for concat of 4 --- .../Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs index ce579989be721..711e7a44cbefb 100644 --- a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs @@ -1369,11 +1369,13 @@ .locals init (char V_0, [InlineData("s + c + (s + s)")] [InlineData("(s + c + s) + s")] [InlineData("s + (c + s + s)")] + [InlineData("(s + c) + (s + s)")] [InlineData("string.Concat(s, c.ToString()) + s + s")] [InlineData("s + string.Concat(c.ToString(), s) + s")] [InlineData("s + c + string.Concat(s, s)")] [InlineData("string.Concat(s, c.ToString(), s) + s")] [InlineData("s + string.Concat(c.ToString(), s, s)")] + [InlineData("string.Concat(s, c.ToString()) + string.Concat(s, s)")] public void ConcatFour_ReadOnlySpan_OperandGroupingAndUserInputOfStringBasedConcats(string expression) { var source = $$""" From ce042d0f5659a7b76e2ad25123f0262f781b0493 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Fri, 12 Jan 2024 21:51:06 +0300 Subject: [PATCH 08/71] Add missing tests for concat 2 arguments case --- .../CodeGenSpanBasedStringConcatTests.cs | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs index 711e7a44cbefb..843a077a275b4 100644 --- a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs @@ -201,6 +201,109 @@ .locals init (char V_0) """); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatTwo_ReadOnlySpan_ReferenceToSameLocation() + { + var source = """ + using System; + + var c = new C(); + c.M(); + + class C + { + public char c = 'a'; + + public ref char GetC() + { + c = 'b'; + return ref c; + } + + public void M() + { + Console.Write(c.ToString() + GetC()); + } + } + """; + + var comp = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "ab" : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + comp.VerifyDiagnostics(); + comp.VerifyIL("C.M", """ + { + // Code size 40 (0x28) + .maxstack 2 + .locals init (char V_0, + char V_1) + IL_0000: ldarg.0 + IL_0001: ldfld "char C.c" + IL_0006: stloc.0 + IL_0007: ldloca.s V_0 + IL_0009: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000e: ldarg.0 + IL_000f: call "ref char C.GetC()" + IL_0014: ldind.u2 + IL_0015: stloc.1 + IL_0016: ldloca.s V_1 + IL_0018: newobj "System.ReadOnlySpan..ctor(in char)" + IL_001d: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0022: call "void System.Console.Write(string)" + IL_0027: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatTwo_ReadOnlySpan_MutateLocal() + { + var source = """ + using System; + + var c = new C(); + Console.WriteLine(c.M()); + + class C + { + public string M() + { + var c = 'a'; + return c + SneakyLocalChange(ref c); + } + + private string SneakyLocalChange(ref char local) + { + local = 'b'; + return "b"; + } + } + """; + + var comp = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "ab" : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + comp.VerifyDiagnostics(); + comp.VerifyIL("C.M", """ + { + // Code size 31 (0x1f) + .maxstack 3 + .locals init (char V_0, //c + char V_1) + IL_0000: ldc.i4.s 97 + IL_0002: stloc.0 + IL_0003: ldloc.0 + IL_0004: stloc.1 + IL_0005: ldloca.s V_1 + IL_0007: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000c: ldarg.0 + IL_000d: ldloca.s V_0 + IL_000f: call "string C.SneakyLocalChange(ref char)" + IL_0014: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0019: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001e: ret + } + """); + } + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] From 05f8250e209a0ea4240ec8d8e69c154b4a4ede9b Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sat, 13 Jan 2024 15:54:12 +0300 Subject: [PATCH 09/71] Extract span-based rewrite into common helper and try to merge with user-provided span-based `string.Concat` when possible --- .../LocalRewriter_StringConcat.cs | 398 ++-- .../CodeGenSpanBasedStringConcatTests.cs | 2005 +++++++++++++++-- 2 files changed, 2092 insertions(+), 311 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index 91a184ce74042..dff7d2c6659e6 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -4,6 +4,7 @@ using System.Collections.Immutable; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.PooledObjects; @@ -79,10 +80,7 @@ private BoundExpression RewriteStringConcatenation(SyntaxNode syntax, BinaryOper leftFlattened.AddRange(rightFlattened); rightFlattened.Free(); - var objectToStringMethod = UnsafeGetSpecialTypeMethod(syntax, SpecialMember.System_Object__ToString); - NamedTypeSymbol? charType = null; - - BoundExpression result; + BoundExpression? result; switch (leftFlattened.Count) { @@ -97,41 +95,11 @@ private BoundExpression RewriteStringConcatenation(SyntaxNode syntax, BinaryOper break; case 2: - { - var left = leftFlattened[0]; - var right = leftFlattened[1]; - - var leftIsCharToString = isCharToString(left, objectToStringMethod, ref charType, out BoundExpression? unwrappedLeftChar); - var rightIsCharToString = isCharToString(right, objectToStringMethod, ref charType, out BoundExpression? unwrappedRightChar); - - if ((leftIsCharToString || rightIsCharToString) && - TryGetWellKnownTypeMember(syntax, WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan, out MethodSymbol? concat2Spans, isOptional: true) && - TryGetWellKnownTypeMember(syntax, WellKnownMember.System_ReadOnlySpan_T__ctor_Reference, out MethodSymbol? readOnlySpanCtorRefParamGeneric, isOptional: true)) - { - // One of args is a char (or rather `someChar.ToString()`, but we've unwrapped `someChar` back from that call). We can use span-based concatenation, which takes a bit more IL, but avoids allocation of an intermidiate string from char.ToString call. - // And since implicit conversion from string to span is a JIT intrinsic, the resulting code ends up being faster than the one generated from the "naive" case with char.ToString call - Debug.Assert(charType is not null); - var readOnlySpanOfChar = readOnlySpanCtorRefParamGeneric.ContainingType.Construct(charType); - var readOnlySpanCtorRefParamChar = readOnlySpanCtorRefParamGeneric.AsMember(readOnlySpanOfChar); - - MethodSymbol? stringImplicitConversionToReadOnlySpan = null; - - // Technically we can get `char1.ToString() + char2.ToString()` as a user input. In such case we don't need implicit conversion method `string -> ReadOnlySpan` - if ((leftIsCharToString && rightIsCharToString) || - TryGetWellKnownTypeMember(syntax, WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar, out stringImplicitConversionToReadOnlySpan, isOptional: true)) - { - result = RewriteStringConcatenationWithSpanBasedConcat( - syntax, - concat2Spans, - stringImplicitConversionToReadOnlySpan, - readOnlySpanCtorRefParamChar, - leftIsCharToString ? unwrappedLeftChar! : left, - rightIsCharToString ? unwrappedRightChar! : right); - - break; - } - } + var left = leftFlattened[0]; + var right = leftFlattened[1]; + if (!TryRewriteStringConcatenationWithSpanBasedConcat(syntax, leftFlattened.ToImmutable(), out result)) + { result = RewriteStringConcatenationTwoExprs(syntax, left, right); } break; @@ -142,40 +110,10 @@ private BoundExpression RewriteStringConcatenation(SyntaxNode syntax, BinaryOper var second = leftFlattened[1]; var third = leftFlattened[2]; - var firstIsCharToString = isCharToString(first, objectToStringMethod, ref charType, out BoundExpression? unwrappedFirstChar); - var secondIsCharToString = isCharToString(second, objectToStringMethod, ref charType, out BoundExpression? unwrappedSecondChar); - var thirdIsCharToString = isCharToString(third, objectToStringMethod, ref charType, out BoundExpression? unwrappedThirdChar); - - if ((firstIsCharToString || secondIsCharToString || thirdIsCharToString) && - TryGetWellKnownTypeMember(syntax, WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan, out MethodSymbol? concat3Spans, isOptional: true) && - TryGetWellKnownTypeMember(syntax, WellKnownMember.System_ReadOnlySpan_T__ctor_Reference, out MethodSymbol? readOnlySpanCtorRefParamGeneric, isOptional: true)) + if (!TryRewriteStringConcatenationWithSpanBasedConcat(syntax, leftFlattened.ToImmutable(), out result)) { - // One of args is a char (or rather `someChar.ToString()`, but we've unwrapped `someChar` back from that call). We can use span-based concatenation, which takes a bit more IL, but avoids allocation of an intermidiate string from char.ToString call. - // And since implicit conversion from string to span is a JIT intrinsic, the resulting code ends up being faster than the one generated from the "naive" case with char.ToString call - Debug.Assert(charType is not null); - var readOnlySpanOfChar = readOnlySpanCtorRefParamGeneric.ContainingType.Construct(charType); - var readOnlySpanCtorRefParamChar = readOnlySpanCtorRefParamGeneric.AsMember(readOnlySpanOfChar); - - MethodSymbol? stringImplicitConversionToReadOnlySpan = null; - - // Technically we can get `char1.ToString() + char2.ToString() + char3.ToString()` as a user input. In such case we don't need implicit conversion method `string -> ReadOnlySpan` - if ((firstIsCharToString && secondIsCharToString && thirdIsCharToString) || - TryGetWellKnownTypeMember(syntax, WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar, out stringImplicitConversionToReadOnlySpan, isOptional: true)) - { - result = RewriteStringConcatenationWithSpanBasedConcat( - syntax, - concat3Spans, - stringImplicitConversionToReadOnlySpan, - readOnlySpanCtorRefParamChar, - firstIsCharToString ? unwrappedFirstChar! : first, - secondIsCharToString ? unwrappedSecondChar! : second, - thirdIsCharToString ? unwrappedThirdChar! : third); - - break; - } + result = RewriteStringConcatenationThreeExprs(syntax, first, second, third); } - - result = RewriteStringConcatenationThreeExprs(syntax, first, second, third); } break; @@ -186,42 +124,10 @@ private BoundExpression RewriteStringConcatenation(SyntaxNode syntax, BinaryOper var third = leftFlattened[2]; var fourth = leftFlattened[3]; - var firstIsCharToString = isCharToString(first, objectToStringMethod, ref charType, out BoundExpression? unwrappedFirstChar); - var secondIsCharToString = isCharToString(second, objectToStringMethod, ref charType, out BoundExpression? unwrappedSecondChar); - var thirdIsCharToString = isCharToString(third, objectToStringMethod, ref charType, out BoundExpression? unwrappedThirdChar); - var fourthIsCharToString = isCharToString(fourth, objectToStringMethod, ref charType, out BoundExpression? unwrappedFourthChar); - - if ((firstIsCharToString || secondIsCharToString || thirdIsCharToString || fourthIsCharToString) && - TryGetWellKnownTypeMember(syntax, WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan, out MethodSymbol? concat4Spans, isOptional: true) && - TryGetWellKnownTypeMember(syntax, WellKnownMember.System_ReadOnlySpan_T__ctor_Reference, out MethodSymbol? readOnlySpanCtorRefParamGeneric, isOptional: true)) + if (!TryRewriteStringConcatenationWithSpanBasedConcat(syntax, leftFlattened.ToImmutable(), out result)) { - // One of args is a char (or rather `someChar.ToString()`, but we've unwrapped `someChar` back from that call). We can use span-based concatenation, which takes a bit more IL, but avoids allocation of an intermidiate string from char.ToString call. - // And since implicit conversion from string to span is a JIT intrinsic, the resulting code ends up being faster than the one generated from the "naive" case with char.ToString call - Debug.Assert(charType is not null); - var readOnlySpanOfChar = readOnlySpanCtorRefParamGeneric.ContainingType.Construct(charType); - var readOnlySpanCtorRefParamChar = readOnlySpanCtorRefParamGeneric.AsMember(readOnlySpanOfChar); - - MethodSymbol? stringImplicitConversionToReadOnlySpan = null; - - // Technically we can get `char1.ToString() + char2.ToString() + char3.ToString() + char4.ToString()` as a user input. In such case we don't need implicit conversion method `string -> ReadOnlySpan` - if ((firstIsCharToString && secondIsCharToString && thirdIsCharToString && fourthIsCharToString) || - TryGetWellKnownTypeMember(syntax, WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar, out stringImplicitConversionToReadOnlySpan, isOptional: true)) - { - result = RewriteStringConcatenationWithSpanBasedConcat( - syntax, - concat4Spans, - stringImplicitConversionToReadOnlySpan, - readOnlySpanCtorRefParamChar, - firstIsCharToString ? unwrappedFirstChar! : first, - secondIsCharToString ? unwrappedSecondChar! : second, - thirdIsCharToString ? unwrappedThirdChar! : third, - fourthIsCharToString ? unwrappedFourthChar! : fourth); - - break; - } + result = RewriteStringConcatenationFourExprs(syntax, first, second, third, fourth); } - - result = RewriteStringConcatenationFourExprs(syntax, first, second, third, fourth); } break; @@ -232,19 +138,6 @@ private BoundExpression RewriteStringConcatenation(SyntaxNode syntax, BinaryOper leftFlattened.Free(); return result; - - static bool isCharToString(BoundExpression expr, MethodSymbol objectToStringMethod, ref NamedTypeSymbol? charType, out BoundExpression? unwrappedChar) - { - if (expr is not BoundCall { ReceiverOpt: { Type: NamedTypeSymbol { SpecialType: SpecialType.System_Char } receiverCharType } receiver, Method: var method }) - { - unwrappedChar = null; - return false; - } - - unwrappedChar = receiver; - charType = receiverCharType; - return (object)method.GetLeastOverriddenMethod(charType) == objectToStringMethod; - } } /// @@ -307,11 +200,29 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr { // Faced a span-based string.Concat call. Since we can produce such call on the previous iterations ourselves, we need to unwrap it. // The key thing is that we need not to only extract arguments, but also unwrap them from being spans and for chars also wrap them into `ToString` calls. + // Another challenge is that we may have merged user-written span-based string.Concat with additional argument previously and we need to undo this change as well + // so that if at some point we exceed 3 or 4 span arguments we can undo all span-concat changes and use string.Concat(string[]) overload with the same arguments as the user provided. + // We do that by tracking which argument nodes have `WasCompilerGenerated` flag. If If a consecutive span of nodes is not compiler-generated, they are arguments from original user-written span.Concat call var wrappedArgs = boundCall.Arguments; var unwrappedArgsBuilder = ArrayBuilder.GetInstance(capacity: wrappedArgs.Length); - foreach (var wrappedArg in wrappedArgs) + var previousConcatArgsBuilder = ArrayBuilder.GetInstance(); + var previousConcatIndex = -1; + + for (var i = 0; i < wrappedArgs.Length; i++) { + var wrappedArg = wrappedArgs[i]; + + if (!wrappedArg.WasCompilerGenerated) + { + previousConcatArgsBuilder.Add(wrappedArg); + if (previousConcatIndex == -1) + { + previousConcatIndex = i; + } + continue; + } + // Check whether a call is an implicit `string -> ReadOnlySpan` conversion if (wrappedArg is BoundCall { Method: var argMethod, Arguments: [var singleArgument] } && (object)argMethod == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)) @@ -329,6 +240,7 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr if (charToString is null) { unwrappedArgsBuilder.Free(); + previousConcatArgsBuilder.Free(); arguments = default; return false; } @@ -336,15 +248,43 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr } else { - // If we got here we faced an argument which doesn't have a shape we produce during lowering ourselves. - // This means that the original concat call is a user input. Stop extracting arguments then unwrappedArgsBuilder.Free(); + previousConcatArgsBuilder.Free(); + arguments = default; + return false; + } + } + + if (previousConcatArgsBuilder.Count == boundCall.Arguments.Length) + { + unwrappedArgsBuilder.Free(); + previousConcatArgsBuilder.Free(); + arguments = default; + return false; + } + + var previousConcatMember = GetSpanConcatMemberByArgumentsCount(previousConcatArgsBuilder.Count); + + if (previousConcatMember.HasValue) + { + Debug.Assert(previousConcatIndex > -1); + + if (TryGetWellKnownTypeMember(lowered.Syntax, previousConcatMember.Value, out MethodSymbol? previousConcatMethod, isOptional: true)) + { + var reconstructedPreviousConcat = BoundCall.Synthesized(lowered.Syntax, receiverOpt: null, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, previousConcatMethod, previousConcatArgsBuilder.ToImmutable()); + unwrappedArgsBuilder.Insert(previousConcatIndex, reconstructedPreviousConcat); + } + else + { + unwrappedArgsBuilder.Free(); + previousConcatArgsBuilder.Free(); arguments = default; return false; } } arguments = unwrappedArgsBuilder.ToImmutableAndFree(); + previousConcatArgsBuilder.Free(); return true; } } @@ -513,62 +453,204 @@ private BoundExpression RewriteStringConcatenationManyExprs(SyntaxNode syntax, I return BoundCall.Synthesized(syntax, receiverOpt: null, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, method, array); } - private BoundExpression RewriteStringConcatenationWithSpanBasedConcat(SyntaxNode syntax, MethodSymbol spanConcat, MethodSymbol? stringImplicitConversionToReadOnlySpan, MethodSymbol readOnlySpanCtorRefParamChar, params BoundExpression[] args) + private bool TryRewriteStringConcatenationWithSpanBasedConcat(SyntaxNode syntax, ImmutableArray args, [NotNullWhen(true)] out BoundExpression? result) { - var preparedArgsBuilder = ArrayBuilder.GetInstance(capacity: args.Length); - var localsBuilder = ArrayBuilder.GetInstance(capacity: args.Length - 1); + // As we scan arguments we might face span-based string.Concat. If it got to this stage it must be a user-provided one (since we weren't able to unwrap it back to strings previously) + // We try our best to merge it with another argument to minimize amount of intermediate string allocations, + // e.g. turn `string.Concat(span1, span2) + string3` into `string.Concat(span1, span2, string3ConvertedToSpan)` + // instead of `string.Concat(string.Concat(span1, span2), string3)`. We do that by separately tracking arguments as is + // and arguments if we unwrap all user-defined `string.Concat`s. If at the end amount of arguments with unwrapped user-defined `string.Concat`s + // is < 5 and we have respective span-based concat member, we emit it, otherwise treat user-provided `string.Concat`s as ordinary arguments + var preparedArgs = ArrayBuilder.GetInstance(capacity: args.Length); + var preparedArgsIfUnwrapUserStringConcat = ArrayBuilder.GetInstance(capacity: args.Length); + + var needsSpanRefParamConstructor = false; + var needsImplicitConversionFromStringToSpan = false; + + var objectToStringMethod = UnsafeGetSpecialTypeMethod(syntax, SpecialMember.System_Object__ToString); + NamedTypeSymbol? charType = null; foreach (var arg in args) { - Debug.Assert(arg.Type is not null); + Debug.Assert(arg.Type?.IsStringType() == true); + + if (arg is BoundCall { ReceiverOpt: { Type: NamedTypeSymbol { SpecialType: SpecialType.System_Char } receiverCharType } receiver } potentialToStringCall && + (object)potentialToStringCall.Method.GetLeastOverriddenMethod(charType) == objectToStringMethod) + { + needsSpanRefParamConstructor = true; + charType = receiverCharType; + preparedArgs.Add(receiver); + preparedArgsIfUnwrapUserStringConcat.Add(receiver); + continue; + } + + preparedArgs.Add(arg); - if (arg.Type.SpecialType == SpecialType.System_Char) + if (arg is BoundCall spanConcatCall && + ((object)spanConcatCall.Method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan) || + (object)spanConcatCall.Method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan) || + (object)spanConcatCall.Method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan))) { - var temp = _factory.StoreToTemp(arg, out var tempAssignment); - localsBuilder.Add(temp.LocalSymbol); + preparedArgsIfUnwrapUserStringConcat.AddRange(spanConcatCall.Arguments); + continue; + } + + needsImplicitConversionFromStringToSpan = true; + preparedArgsIfUnwrapUserStringConcat.Add(arg); + } + + var concatMemberIfUnwrapUserSpanConcats = GetSpanConcatMemberByArgumentsCount(preparedArgsIfUnwrapUserStringConcat.Count); - var wrappedChar = new BoundObjectCreationExpression( - arg.Syntax, + MethodSymbol? spanConcat; + MethodSymbol? readOnlySpanCtorRefParamChar; + MethodSymbol? stringImplicitConversionToReadOnlySpan; + + if (preparedArgsIfUnwrapUserStringConcat.Count > preparedArgs.Count && concatMemberIfUnwrapUserSpanConcats.HasValue) + { + if (TryGetWellKnownTypeMember(syntax, concatMemberIfUnwrapUserSpanConcats.Value, out spanConcat, isOptional: true) && + tryGetNeededToSpanMembers(this, syntax, needsSpanRefParamConstructor, needsImplicitConversionFromStringToSpan, charType, out readOnlySpanCtorRefParamChar, out stringImplicitConversionToReadOnlySpan)) + { + result = rewriteStringConcatenationWithSpanBasedConcat( + syntax, + _factory, + spanConcat, + stringImplicitConversionToReadOnlySpan, readOnlySpanCtorRefParamChar, - [temp], - argumentNamesOpt: default, - argumentRefKindsOpt: [RefKindExtensions.StrictIn], - expanded: false, - argsToParamsOpt: default, - defaultArguments: default, - constantValueOpt: null, - initializerExpressionOpt: null, - type: readOnlySpanCtorRefParamChar.ContainingType); - - preparedArgsBuilder.Add(new BoundSequence( - arg.Syntax, - [], - [tempAssignment], - wrappedChar, - wrappedChar.Type)); + preparedArgsIfUnwrapUserStringConcat.ToImmutableAndFree()); + + preparedArgs.Free(); + return true; } - else + + needsImplicitConversionFromStringToSpan = true; + } + + var concatMember = GetSpanConcatMemberByArgumentsCount(preparedArgsIfUnwrapUserStringConcat.Count); + + if (concatMember.HasValue && + TryGetWellKnownTypeMember(syntax, concatMember.Value, out spanConcat, isOptional: true) && + tryGetNeededToSpanMembers(this, syntax, needsSpanRefParamConstructor, needsImplicitConversionFromStringToSpan, charType, out readOnlySpanCtorRefParamChar, out stringImplicitConversionToReadOnlySpan)) + { + result = rewriteStringConcatenationWithSpanBasedConcat( + syntax, + _factory, + spanConcat, + stringImplicitConversionToReadOnlySpan, + readOnlySpanCtorRefParamChar, + preparedArgs.ToImmutableAndFree()); + + preparedArgsIfUnwrapUserStringConcat.Free(); + return true; + } + + result = null; + return false; + + static bool tryGetNeededToSpanMembers(LocalRewriter self, SyntaxNode syntax, bool needsSpanRefParamConstructor, bool needsImplicitConversionFromStringToSpan, NamedTypeSymbol? charType, out MethodSymbol? readOnlySpanCtorRefParamChar, out MethodSymbol? stringImplicitConversionToReadOnlySpan) + { + readOnlySpanCtorRefParamChar = null; + stringImplicitConversionToReadOnlySpan = null; + + if (needsSpanRefParamConstructor) { - Debug.Assert(arg.Type.SpecialType == SpecialType.System_String); - Debug.Assert(stringImplicitConversionToReadOnlySpan is not null); - preparedArgsBuilder.Add(BoundCall.Synthesized(arg.Syntax, receiverOpt: null, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, stringImplicitConversionToReadOnlySpan, arg)); + if (self.TryGetWellKnownTypeMember(syntax, WellKnownMember.System_ReadOnlySpan_T__ctor_Reference, out MethodSymbol? readOnlySpanCtorRefParamGeneric, isOptional: true)) + { + Debug.Assert(charType is not null); + var readOnlySpanOfChar = readOnlySpanCtorRefParamGeneric.ContainingType.Construct(charType); + readOnlySpanCtorRefParamChar = readOnlySpanCtorRefParamGeneric.AsMember(readOnlySpanOfChar); + } + else + { + return false; + } } + + if (needsImplicitConversionFromStringToSpan) + { + return self.TryGetWellKnownTypeMember(syntax, WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar, out stringImplicitConversionToReadOnlySpan, isOptional: true); + } + + return true; } - var concatCall = BoundCall.Synthesized(syntax, receiverOpt: null, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, spanConcat, preparedArgsBuilder.ToImmutableAndFree()); + static BoundExpression rewriteStringConcatenationWithSpanBasedConcat( + SyntaxNode syntax, + SyntheticBoundNodeFactory factory, + MethodSymbol spanConcat, + MethodSymbol? stringImplicitConversionToReadOnlySpan, + MethodSymbol? readOnlySpanCtorRefParamChar, + ImmutableArray args) + { + var preparedArgsBuilder = ArrayBuilder.GetInstance(capacity: args.Length); + var localsBuilder = ArrayBuilder.GetInstance(); + + foreach (var arg in args) + { + Debug.Assert(arg.Type is not null); - var oldSyntax = _factory.Syntax; - _factory.Syntax = syntax; + if (arg.Type.IsReadOnlySpanChar()) + { + preparedArgsBuilder.Add(arg); + } + else if (arg.Type.SpecialType == SpecialType.System_Char) + { + Debug.Assert(readOnlySpanCtorRefParamChar is not null); + + var temp = factory.StoreToTemp(arg, out var tempAssignment); + localsBuilder.Add(temp.LocalSymbol); + + var wrappedChar = new BoundObjectCreationExpression( + arg.Syntax, + readOnlySpanCtorRefParamChar, + [temp], + argumentNamesOpt: default, + argumentRefKindsOpt: [RefKindExtensions.StrictIn], + expanded: false, + argsToParamsOpt: default, + defaultArguments: default, + constantValueOpt: null, + initializerExpressionOpt: null, + type: readOnlySpanCtorRefParamChar.ContainingType); + + preparedArgsBuilder.Add(new BoundSequence( + arg.Syntax, + [], + [tempAssignment], + wrappedChar, + wrappedChar.Type) + { WasCompilerGenerated = true }); + } + else + { + Debug.Assert(arg.Type.SpecialType == SpecialType.System_String); + Debug.Assert(stringImplicitConversionToReadOnlySpan is not null); + preparedArgsBuilder.Add(BoundCall.Synthesized(arg.Syntax, receiverOpt: null, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, stringImplicitConversionToReadOnlySpan, arg)); + } + } + + var concatCall = BoundCall.Synthesized(syntax, receiverOpt: null, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, spanConcat, preparedArgsBuilder.ToImmutableAndFree()); + + var oldSyntax = factory.Syntax; + factory.Syntax = syntax; - var sequence = _factory.Sequence( - localsBuilder.ToImmutableAndFree(), - [], - concatCall); + var sequence = factory.Sequence( + localsBuilder.ToImmutableAndFree(), + [], + concatCall); - _factory.Syntax = oldSyntax; - return sequence; + factory.Syntax = oldSyntax; + return sequence; + } } + private static WellKnownMember? GetSpanConcatMemberByArgumentsCount(int argumentCount) => argumentCount switch + { + 2 => WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan, + 3 => WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan, + 4 => WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan, + _ => null, + }; + /// /// Most of the above optimizations are not applicable in expression trees as the operator /// must stay a binary operator. We cannot do much beyond constant folding which is done in binder. diff --git a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs index 843a077a275b4..3fb7e65cae66e 100644 --- a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs @@ -1080,6 +1080,254 @@ .maxstack 3 """); } + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + public void ConcatThree_UserInputOfSpanBasedConcat_ConcatWithString(int? missingUnimportantWellKnownMember) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s1 = "a"; + var s2 = "b"; + var s3 = "c"; + Console.Write(M1(s1.AsSpan(), s2, s3)); + Console.Write(M2(s1.AsSpan(), s2, s3)); + } + + static string M1(ReadOnlySpan s1, string s2, string s3) => string.Concat(s1, s2.AsSpan()) + s3; + static string M2(ReadOnlySpan s1, string s2, string s3) => s3 + string.Concat(s1, s2.AsSpan()); + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abccab" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Test.M1", """ + { + // Code size 19 (0x13) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0007: ldarg.2 + IL_0008: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000d: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0012: ret + } + """); + verifier.VerifyIL("Test.M2", """ + { + // Code size 19 (0x13) + .maxstack 3 + IL_0000: ldarg.2 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.0 + IL_0007: ldarg.1 + IL_0008: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_000d: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0012: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + public void ConcatThree_UserInputOfSpanBasedConcat_ConcatWithChar(int? missingUnimportantWellKnownMember) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s1 = "a"; + var s2 = "b"; + var c = 'c'; + Console.Write(M1(s1.AsSpan(), s2, c)); + Console.Write(M2(s1.AsSpan(), s2, c)); + } + + static string M1(ReadOnlySpan s1, string s2, char c) => string.Concat(s1, s2.AsSpan()) + c; + static string M2(ReadOnlySpan s1, string s2, char c) => c + string.Concat(s1, s2.AsSpan()); + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abccab" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Test.M1", """ + { + // Code size 22 (0x16) + .maxstack 3 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0007: ldarg.2 + IL_0008: stloc.0 + IL_0009: ldloca.s V_0 + IL_000b: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0010: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0015: ret + } + """); + verifier.VerifyIL("Test.M2", """ + { + // Code size 22 (0x16) + .maxstack 3 + .locals init (char V_0) + IL_0000: ldarg.2 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0009: ldarg.0 + IL_000a: ldarg.1 + IL_000b: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0010: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0015: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + public void ConcatThree_UserInputOfSpanBasedConcat_ConcatWithString_MissingMemberToMergeIntoSingleConcat(int member) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s1 = "a"; + var s2 = "b"; + var s3 = "c"; + Console.Write(M1(s1.AsSpan(), s2, s3)); + Console.Write(M2(s1.AsSpan(), s2, s3)); + } + + static string M1(ReadOnlySpan s1, string s2, string s3) => string.Concat(s1, s2.AsSpan()) + s3; + static string M2(ReadOnlySpan s1, string s2, string s3) => s3 + string.Concat(s1, s2.AsSpan()); + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + comp.MakeMemberMissing((WellKnownMember)member); + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abccab" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Test.M1", """ + { + // Code size 19 (0x13) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0007: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_000c: ldarg.2 + IL_000d: call "string string.Concat(string, string)" + IL_0012: ret + } + """); + verifier.VerifyIL("Test.M2", """ + { + // Code size 19 (0x13) + .maxstack 3 + IL_0000: ldarg.2 + IL_0001: ldarg.0 + IL_0002: ldarg.1 + IL_0003: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0008: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_000d: call "string string.Concat(string, string)" + IL_0012: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + public void ConcatThree_UserInputOfSpanBasedConcat_ConcatWithChar_MissingMemberToMergeIntoSingleConcat(int member) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s1 = "a"; + var s2 = "b"; + var c = 'c'; + Console.Write(M1(s1.AsSpan(), s2, c)); + Console.Write(M2(s1.AsSpan(), s2, c)); + } + + static string M1(ReadOnlySpan s1, string s2, char c) => string.Concat(s1, s2.AsSpan()) + c; + static string M2(ReadOnlySpan s1, string s2, char c) => c + string.Concat(s1, s2.AsSpan()); + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + comp.MakeMemberMissing((WellKnownMember)member); + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abccab" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Test.M1", """ + { + // Code size 25 (0x19) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0007: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_000c: ldarga.s V_2 + IL_000e: call "string char.ToString()" + IL_0013: call "string string.Concat(string, string)" + IL_0018: ret + } + """); + verifier.VerifyIL("Test.M2", """ + { + // Code size 25 (0x19) + .maxstack 3 + IL_0000: ldarga.s V_2 + IL_0002: call "string char.ToString()" + IL_0007: ldarg.0 + IL_0008: ldarg.1 + IL_0009: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_000e: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0013: call "string string.Concat(string, string)" + IL_0018: ret + } + """); + } + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] @@ -2082,12 +2330,9 @@ .maxstack 4 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] - public void ConcatFive_Char(int? missingUnimportantWellKnownMember) + public void ConcatFour_UserInputOfSpanBasedConcatOf2_ConcatWithString(int? missingUnimportantWellKnownMember) { var source = """ using System; @@ -2096,14 +2341,17 @@ public class Test { static void Main() { - var s = "s"; - var c = 'c'; - Console.Write(CharInFirstFourArgs(s, c)); - Console.Write(CharAfterFirstFourArgs(s, c)); + var s1 = "a"; + var s2 = "b"; + var s3 = "c"; + Console.Write(M1(s1.AsSpan(), s2, s3)); + Console.Write(M2(s1.AsSpan(), s2, s3)); + Console.Write(M3(s1.AsSpan(), s2, s3)); } - static string CharInFirstFourArgs(string s, char c) => s + c + s + s + s; - static string CharAfterFirstFourArgs(string s, char c) => s + s + s + s + c; + static string M1(ReadOnlySpan s1, string s2, string s3) => string.Concat(s1, s2.AsSpan()) + s3 + s3; + static string M2(ReadOnlySpan s1, string s2, string s3) => s3 + s3 + string.Concat(s1, s2.AsSpan()); + static string M3(ReadOnlySpan s1, string s2, string s3) => s3 + string.Concat(s1, s2.AsSpan()) + s3; } """; @@ -2114,90 +2362,1082 @@ static void Main() comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); } - var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "scsssssssc" : null, verify: ExecutionConditionUtil.IsCoreClr ? default : Verification.Skipped); + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abccccabcabc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); verifier.VerifyDiagnostics(); - - // When lengths of inputs are low it is actually more optimal to first concat 4 operands with span-based concat and then concat that result with the remaining operand. - // However when inputs become large enough cost of allocating a params array becomes lower than cost of creating an intermediate string. - // + code size for using params array is less than code size from intermediate concat approach, especially when the amount of operands is high. - // So in the end we always prefer overload with params array when there are 5+ operands - verifier.VerifyIL("Test.CharInFirstFourArgs", """ + verifier.VerifyIL("Test.M1", """ { - // Code size 38 (0x26) + // Code size 25 (0x19) .maxstack 4 - IL_0000: ldc.i4.5 - IL_0001: newarr "string" - IL_0006: dup - IL_0007: ldc.i4.0 - IL_0008: ldarg.0 - IL_0009: stelem.ref - IL_000a: dup - IL_000b: ldc.i4.1 - IL_000c: ldarga.s V_1 - IL_000e: call "string char.ToString()" - IL_0013: stelem.ref - IL_0014: dup - IL_0015: ldc.i4.2 - IL_0016: ldarg.0 - IL_0017: stelem.ref - IL_0018: dup - IL_0019: ldc.i4.3 - IL_001a: ldarg.0 - IL_001b: stelem.ref - IL_001c: dup - IL_001d: ldc.i4.4 - IL_001e: ldarg.0 - IL_001f: stelem.ref - IL_0020: call "string string.Concat(params string[])" - IL_0025: ret + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0007: ldarg.2 + IL_0008: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000d: ldarg.2 + IL_000e: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0013: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0018: ret } """); - verifier.VerifyIL("Test.CharAfterFirstFourArgs", """ + verifier.VerifyIL("Test.M2", """ { - // Code size 38 (0x26) + // Code size 25 (0x19) .maxstack 4 - IL_0000: ldc.i4.5 - IL_0001: newarr "string" - IL_0006: dup - IL_0007: ldc.i4.0 - IL_0008: ldarg.0 - IL_0009: stelem.ref - IL_000a: dup - IL_000b: ldc.i4.1 + IL_0000: ldarg.2 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.2 + IL_0007: call "System.ReadOnlySpan string.op_Implicit(string)" IL_000c: ldarg.0 - IL_000d: stelem.ref - IL_000e: dup - IL_000f: ldc.i4.2 - IL_0010: ldarg.0 - IL_0011: stelem.ref - IL_0012: dup - IL_0013: ldc.i4.3 - IL_0014: ldarg.0 - IL_0015: stelem.ref - IL_0016: dup - IL_0017: ldc.i4.4 - IL_0018: ldarga.s V_1 - IL_001a: call "string char.ToString()" - IL_001f: stelem.ref - IL_0020: call "string string.Concat(params string[])" - IL_0025: ret + IL_000d: ldarg.1 + IL_000e: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0013: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0018: ret + } + """); + verifier.VerifyIL("Test.M3", """ + { + // Code size 25 (0x19) + .maxstack 4 + IL_0000: ldarg.2 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.0 + IL_0007: ldarg.1 + IL_0008: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_000d: ldarg.2 + IL_000e: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0013: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0018: ret } """); } [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] - [InlineData("(s + s) + c + s + s")] - [InlineData("s + (s + c) + s + s")] - [InlineData("s + s + (c + s) + s")] - [InlineData("s + s + c + (s + s)")] - [InlineData("(s + s + c) + s + s")] - [InlineData("s + (s + c + s) + s")] - [InlineData("s + s + (c + s + s)")] - [InlineData("(s + s + c + s) + s")] - [InlineData("s + (s + c + s + s)")] - [InlineData("(s + s) + (c + s) + s")] - [InlineData("(s + s) + c + (s + s)")] + [InlineData(null)] + [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + public void ConcatFour_UserInputOfSpanBasedConcatOf2_ConcatWithChar(int? missingUnimportantWellKnownMember) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s1 = "a"; + var s2 = "b"; + var c = 'c'; + Console.Write(M1(s1.AsSpan(), s2, c)); + Console.Write(M2(s1.AsSpan(), s2, c)); + Console.Write(M3(s1.AsSpan(), s2, c)); + } + + static string M1(ReadOnlySpan s1, string s2, char c) => string.Concat(s1, s2.AsSpan()) + c + c; + static string M2(ReadOnlySpan s1, string s2, char c) => c.ToString() + c + string.Concat(s1, s2.AsSpan()); + static string M3(ReadOnlySpan s1, string s2, char c) => c + string.Concat(s1, s2.AsSpan()) + c; + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abccccabcabc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Test.M1", """ + { + // Code size 31 (0x1f) + .maxstack 4 + .locals init (char V_0, + char V_1) + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0007: ldarg.2 + IL_0008: stloc.0 + IL_0009: ldloca.s V_0 + IL_000b: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0010: ldarg.2 + IL_0011: stloc.1 + IL_0012: ldloca.s V_1 + IL_0014: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0019: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001e: ret + } + """); + verifier.VerifyIL("Test.M2", """ + { + // Code size 31 (0x1f) + .maxstack 4 + .locals init (char V_0, + char V_1) + IL_0000: ldarg.2 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0009: ldarg.2 + IL_000a: stloc.1 + IL_000b: ldloca.s V_1 + IL_000d: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0012: ldarg.0 + IL_0013: ldarg.1 + IL_0014: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0019: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001e: ret + } + """); + verifier.VerifyIL("Test.M3", """ + { + // Code size 31 (0x1f) + .maxstack 4 + .locals init (char V_0, + char V_1) + IL_0000: ldarg.2 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0009: ldarg.0 + IL_000a: ldarg.1 + IL_000b: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0010: ldarg.2 + IL_0011: stloc.1 + IL_0012: ldloca.s V_1 + IL_0014: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0019: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001e: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + public void ConcatFour_UserInputOfSpanBasedConcatOf2_ConcatWithStringAndChar(int? missingUnimportantWellKnownMember) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s1 = "a"; + var s2 = "b"; + var s3 = "c"; + var c = 'd'; + Console.Write(M1(s1.AsSpan(), s2, s3, c)); + Console.Write(M2(s1.AsSpan(), s2, s3, c)); + Console.Write(M3(s1.AsSpan(), s2, s3, c)); + Console.Write(M4(s1.AsSpan(), s2, s3, c)); + Console.Write(M5(s1.AsSpan(), s2, s3, c)); + Console.Write(M6(s1.AsSpan(), s2, s3, c)); + } + + static string M1(ReadOnlySpan s1, string s2, string s3, char c) => string.Concat(s1, s2.AsSpan()) + s3 + c; + static string M2(ReadOnlySpan s1, string s2, string s3, char c) => string.Concat(s1, s2.AsSpan()) + c + s3; + static string M3(ReadOnlySpan s1, string s2, string s3, char c) => s3 + c + string.Concat(s1, s2.AsSpan()); + static string M4(ReadOnlySpan s1, string s2, string s3, char c) => c + s3 + string.Concat(s1, s2.AsSpan()); + static string M5(ReadOnlySpan s1, string s2, string s3, char c) => s3 + string.Concat(s1, s2.AsSpan()) + c; + static string M6(ReadOnlySpan s1, string s2, string s3, char c) => c + string.Concat(s1, s2.AsSpan()) + s3; + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abcdabdccdabdcabcabddabc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Test.M1", """ + { + // Code size 28 (0x1c) + .maxstack 4 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0007: ldarg.2 + IL_0008: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000d: ldarg.3 + IL_000e: stloc.0 + IL_000f: ldloca.s V_0 + IL_0011: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0016: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001b: ret + } + """); + verifier.VerifyIL("Test.M2", """ + { + // Code size 28 (0x1c) + .maxstack 4 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0007: ldarg.3 + IL_0008: stloc.0 + IL_0009: ldloca.s V_0 + IL_000b: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0010: ldarg.2 + IL_0011: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0016: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001b: ret + } + """); + verifier.VerifyIL("Test.M3", """ + { + // Code size 28 (0x1c) + .maxstack 4 + .locals init (char V_0) + IL_0000: ldarg.2 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.3 + IL_0007: stloc.0 + IL_0008: ldloca.s V_0 + IL_000a: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000f: ldarg.0 + IL_0010: ldarg.1 + IL_0011: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0016: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001b: ret + } + """); + verifier.VerifyIL("Test.M4", """ + { + // Code size 28 (0x1c) + .maxstack 4 + .locals init (char V_0) + IL_0000: ldarg.3 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0009: ldarg.2 + IL_000a: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000f: ldarg.0 + IL_0010: ldarg.1 + IL_0011: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0016: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001b: ret + } + """); + verifier.VerifyIL("Test.M5", """ + { + // Code size 28 (0x1c) + .maxstack 4 + .locals init (char V_0) + IL_0000: ldarg.2 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.0 + IL_0007: ldarg.1 + IL_0008: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_000d: ldarg.3 + IL_000e: stloc.0 + IL_000f: ldloca.s V_0 + IL_0011: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0016: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001b: ret + } + """); + verifier.VerifyIL("Test.M6", """ + { + // Code size 28 (0x1c) + .maxstack 4 + .locals init (char V_0) + IL_0000: ldarg.3 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0009: ldarg.0 + IL_000a: ldarg.1 + IL_000b: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0010: ldarg.2 + IL_0011: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0016: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001b: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + public void ConcatFour_TwoUserInputsOfSpanBasedConcatOf2(int? missingUnimportantWellKnownMember) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s1 = "a"; + var s2 = "b"; + var s3 = "c"; + var s4 = "d"; + Console.Write(M(s1.AsSpan(), s2.AsSpan(), s3.AsSpan(), s4.AsSpan())); + } + + static string M(ReadOnlySpan s1, ReadOnlySpan s2, ReadOnlySpan s3, ReadOnlySpan s4) => string.Concat(s1, s2) + string.Concat(s3, s4); + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abcd" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Test.M", """ + { + // Code size 10 (0xa) + .maxstack 4 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: ldarg.2 + IL_0003: ldarg.3 + IL_0004: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0009: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + public void ConcatFour_UserInputOfSpanBasedConcatOf3_ConcatWithString(int? missingUnimportantWellKnownMember) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s1 = "a"; + var s2 = "b"; + var s3 = "c"; + var s4 = "d"; + Console.Write(M1(s1.AsSpan(), s2, s3.AsSpan(), s4)); + Console.Write(M2(s1.AsSpan(), s2, s3.AsSpan(), s4)); + } + + static string M1(ReadOnlySpan s1, string s2, ReadOnlySpan s3, string s4) => string.Concat(s1, s2.AsSpan(), s3) + s4; + static string M2(ReadOnlySpan s1, string s2, ReadOnlySpan s3, string s4) => s4 + string.Concat(s1, s2.AsSpan(), s3); + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abcddabc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Test.M1", """ + { + // Code size 20 (0x14) + .maxstack 4 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0007: ldarg.2 + IL_0008: ldarg.3 + IL_0009: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000e: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0013: ret + } + """); + verifier.VerifyIL("Test.M2", """ + { + // Code size 20 (0x14) + .maxstack 4 + IL_0000: ldarg.3 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.0 + IL_0007: ldarg.1 + IL_0008: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_000d: ldarg.2 + IL_000e: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0013: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + public void ConcatFour_UserInputOfSpanBasedConcatOf3_ConcatWithChar(int? missingUnimportantWellKnownMember) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s1 = "a"; + var s2 = "b"; + var s3 = "c"; + var c = 'd'; + Console.Write(M1(s1.AsSpan(), s2, s3.AsSpan(), c)); + Console.Write(M2(s1.AsSpan(), s2, s3.AsSpan(), c)); + } + + static string M1(ReadOnlySpan s1, string s2, ReadOnlySpan s3, char c) => string.Concat(s1, s2.AsSpan(), s3) + c; + static string M2(ReadOnlySpan s1, string s2, ReadOnlySpan s3, char c) => c + string.Concat(s1, s2.AsSpan(), s3); + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abcddabc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Test.M1", """ + { + // Code size 23 (0x17) + .maxstack 4 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0007: ldarg.2 + IL_0008: ldarg.3 + IL_0009: stloc.0 + IL_000a: ldloca.s V_0 + IL_000c: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0011: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0016: ret + } + """); + verifier.VerifyIL("Test.M2", """ + { + // Code size 23 (0x17) + .maxstack 4 + .locals init (char V_0) + IL_0000: ldarg.3 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0009: ldarg.0 + IL_000a: ldarg.1 + IL_000b: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0010: ldarg.2 + IL_0011: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0016: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan, (int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + public void ConcatFour_UserInputOfSpanBasedConcatOf2_ConcatWithString_MissingMembersToMergeIntoSingleConcat(params int[] members) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s1 = "a"; + var s2 = "b"; + var s3 = "c"; + Console.Write(M1(s1.AsSpan(), s2, s3)); + Console.Write(M2(s1.AsSpan(), s2, s3)); + Console.Write(M3(s1.AsSpan(), s2, s3)); + } + + static string M1(ReadOnlySpan s1, string s2, string s3) => string.Concat(s1, s2.AsSpan()) + s3 + s3; + static string M2(ReadOnlySpan s1, string s2, string s3) => s3 + s3 + string.Concat(s1, s2.AsSpan()); + static string M3(ReadOnlySpan s1, string s2, string s3) => s3 + string.Concat(s1, s2.AsSpan()) + s3; + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + foreach (var member in members) + { + comp.MakeMemberMissing((WellKnownMember)member); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abccccabcabc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Test.M1", """ + { + // Code size 20 (0x14) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0007: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_000c: ldarg.2 + IL_000d: ldarg.2 + IL_000e: call "string string.Concat(string, string, string)" + IL_0013: ret + } + """); + verifier.VerifyIL("Test.M2", """ + { + // Code size 20 (0x14) + .maxstack 4 + IL_0000: ldarg.2 + IL_0001: ldarg.2 + IL_0002: ldarg.0 + IL_0003: ldarg.1 + IL_0004: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0009: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_000e: call "string string.Concat(string, string, string)" + IL_0013: ret + } + """); + verifier.VerifyIL("Test.M3", """ + { + // Code size 20 (0x14) + .maxstack 3 + IL_0000: ldarg.2 + IL_0001: ldarg.0 + IL_0002: ldarg.1 + IL_0003: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0008: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_000d: ldarg.2 + IL_000e: call "string string.Concat(string, string, string)" + IL_0013: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan, (int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + public void ConcatFour_UserInputOfSpanBasedConcatOf2_ConcatWithChar_MissingMembersToMergeIntoSingleConcat(params int[] members) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s1 = "a"; + var s2 = "b"; + var c = 'c'; + Console.Write(M1(s1.AsSpan(), s2, c)); + Console.Write(M2(s1.AsSpan(), s2, c)); + Console.Write(M3(s1.AsSpan(), s2, c)); + } + + static string M1(ReadOnlySpan s1, string s2, char c) => string.Concat(s1, s2.AsSpan()) + c + c; + static string M2(ReadOnlySpan s1, string s2, char c) => c.ToString() + c + string.Concat(s1, s2.AsSpan()); + static string M3(ReadOnlySpan s1, string s2, char c) => c + string.Concat(s1, s2.AsSpan()) + c; + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + foreach (var member in members) + { + comp.MakeMemberMissing((WellKnownMember)member); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abccccabcabc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Test.M1", """ + { + // Code size 32 (0x20) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0007: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_000c: ldarga.s V_2 + IL_000e: call "string char.ToString()" + IL_0013: ldarga.s V_2 + IL_0015: call "string char.ToString()" + IL_001a: call "string string.Concat(string, string, string)" + IL_001f: ret + } + """); + verifier.VerifyIL("Test.M2", """ + { + // Code size 32 (0x20) + .maxstack 4 + IL_0000: ldarga.s V_2 + IL_0002: call "string char.ToString()" + IL_0007: ldarga.s V_2 + IL_0009: call "string char.ToString()" + IL_000e: ldarg.0 + IL_000f: ldarg.1 + IL_0010: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0015: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001a: call "string string.Concat(string, string, string)" + IL_001f: ret + } + """); + verifier.VerifyIL("Test.M3", """ + { + // Code size 32 (0x20) + .maxstack 3 + IL_0000: ldarga.s V_2 + IL_0002: call "string char.ToString()" + IL_0007: ldarg.0 + IL_0008: ldarg.1 + IL_0009: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_000e: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0013: ldarga.s V_2 + IL_0015: call "string char.ToString()" + IL_001a: call "string string.Concat(string, string, string)" + IL_001f: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference, (int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan, (int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan, (int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan, (int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + public void ConcatFour_UserInputOfSpanBasedConcatOf2_ConcatWithStringAndChar_MissingMembersToMergeIntoSingleConcat(params int[] members) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s1 = "a"; + var s2 = "b"; + var s3 = "c"; + var c = 'd'; + Console.Write(M1(s1.AsSpan(), s2, s3, c)); + Console.Write(M2(s1.AsSpan(), s2, s3, c)); + Console.Write(M3(s1.AsSpan(), s2, s3, c)); + Console.Write(M4(s1.AsSpan(), s2, s3, c)); + Console.Write(M5(s1.AsSpan(), s2, s3, c)); + Console.Write(M6(s1.AsSpan(), s2, s3, c)); + } + + static string M1(ReadOnlySpan s1, string s2, string s3, char c) => string.Concat(s1, s2.AsSpan()) + s3 + c; + static string M2(ReadOnlySpan s1, string s2, string s3, char c) => string.Concat(s1, s2.AsSpan()) + c + s3; + static string M3(ReadOnlySpan s1, string s2, string s3, char c) => s3 + c + string.Concat(s1, s2.AsSpan()); + static string M4(ReadOnlySpan s1, string s2, string s3, char c) => c + s3 + string.Concat(s1, s2.AsSpan()); + static string M5(ReadOnlySpan s1, string s2, string s3, char c) => s3 + string.Concat(s1, s2.AsSpan()) + c; + static string M6(ReadOnlySpan s1, string s2, string s3, char c) => c + string.Concat(s1, s2.AsSpan()) + s3; + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + foreach (var member in members) + { + comp.MakeMemberMissing((WellKnownMember)member); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abcdabdccdabdcabcabddabc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Test.M1", """ + { + // Code size 26 (0x1a) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0007: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_000c: ldarg.2 + IL_000d: ldarga.s V_3 + IL_000f: call "string char.ToString()" + IL_0014: call "string string.Concat(string, string, string)" + IL_0019: ret + } + """); + verifier.VerifyIL("Test.M2", """ + { + // Code size 26 (0x1a) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0007: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_000c: ldarga.s V_3 + IL_000e: call "string char.ToString()" + IL_0013: ldarg.2 + IL_0014: call "string string.Concat(string, string, string)" + IL_0019: ret + } + """); + verifier.VerifyIL("Test.M3", """ + { + // Code size 26 (0x1a) + .maxstack 4 + IL_0000: ldarg.2 + IL_0001: ldarga.s V_3 + IL_0003: call "string char.ToString()" + IL_0008: ldarg.0 + IL_0009: ldarg.1 + IL_000a: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_000f: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0014: call "string string.Concat(string, string, string)" + IL_0019: ret + } + """); + verifier.VerifyIL("Test.M4", """ + { + // Code size 26 (0x1a) + .maxstack 4 + IL_0000: ldarga.s V_3 + IL_0002: call "string char.ToString()" + IL_0007: ldarg.2 + IL_0008: ldarg.0 + IL_0009: ldarg.1 + IL_000a: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_000f: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0014: call "string string.Concat(string, string, string)" + IL_0019: ret + } + """); + verifier.VerifyIL("Test.M5", """ + { + // Code size 26 (0x1a) + .maxstack 3 + IL_0000: ldarg.2 + IL_0001: ldarg.0 + IL_0002: ldarg.1 + IL_0003: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0008: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_000d: ldarga.s V_3 + IL_000f: call "string char.ToString()" + IL_0014: call "string string.Concat(string, string, string)" + IL_0019: ret + } + """); + verifier.VerifyIL("Test.M6", """ + { + // Code size 26 (0x1a) + .maxstack 3 + IL_0000: ldarga.s V_3 + IL_0002: call "string char.ToString()" + IL_0007: ldarg.0 + IL_0008: ldarg.1 + IL_0009: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_000e: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0013: ldarg.2 + IL_0014: call "string string.Concat(string, string, string)" + IL_0019: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + public void ConcatFour_TwoUserInputsOfSpanBasedConcatOf2_MissingMemberToMergeIntoSingleConcat(int member) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s1 = "a"; + var s2 = "b"; + var s3 = "c"; + var s4 = "d"; + Console.Write(M(s1.AsSpan(), s2.AsSpan(), s3.AsSpan(), s4.AsSpan())); + } + + static string M(ReadOnlySpan s1, ReadOnlySpan s2, ReadOnlySpan s3, ReadOnlySpan s4) => string.Concat(s1, s2) + string.Concat(s3, s4); + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + comp.MakeMemberMissing((WellKnownMember)member); + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abcd" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Test.M", """ + { + // Code size 20 (0x14) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0007: ldarg.2 + IL_0008: ldarg.3 + IL_0009: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_000e: call "string string.Concat(string, string)" + IL_0013: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + public void ConcatFour_UserInputOfSpanBasedConcatOf3_ConcatWithString_MissingMemberToMergeIntoSingleConcat(int member) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s1 = "a"; + var s2 = "b"; + var s3 = "c"; + var s4 = "d"; + Console.Write(M1(s1.AsSpan(), s2, s3.AsSpan(), s4)); + Console.Write(M2(s1.AsSpan(), s2, s3.AsSpan(), s4)); + } + + static string M1(ReadOnlySpan s1, string s2, ReadOnlySpan s3, string s4) => string.Concat(s1, s2.AsSpan(), s3) + s4; + static string M2(ReadOnlySpan s1, string s2, ReadOnlySpan s3, string s4) => s4 + string.Concat(s1, s2.AsSpan(), s3); + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + comp.MakeMemberMissing((WellKnownMember)member); + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abcddabc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Test.M1", """ + { + // Code size 20 (0x14) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0007: ldarg.2 + IL_0008: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_000d: ldarg.3 + IL_000e: call "string string.Concat(string, string)" + IL_0013: ret + } + """); + verifier.VerifyIL("Test.M2", """ + { + // Code size 20 (0x14) + .maxstack 4 + IL_0000: ldarg.3 + IL_0001: ldarg.0 + IL_0002: ldarg.1 + IL_0003: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0008: ldarg.2 + IL_0009: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_000e: call "string string.Concat(string, string)" + IL_0013: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + public void ConcatFour_UserInputOfSpanBasedConcatOf3_ConcatWithChar_MissingMemberToMergeIntoSingleConcat(int member) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s1 = "a"; + var s2 = "b"; + var s3 = "c"; + var c = 'd'; + Console.Write(M1(s1.AsSpan(), s2, s3.AsSpan(), c)); + Console.Write(M2(s1.AsSpan(), s2, s3.AsSpan(), c)); + } + + static string M1(ReadOnlySpan s1, string s2, ReadOnlySpan s3, char c) => string.Concat(s1, s2.AsSpan(), s3) + c; + static string M2(ReadOnlySpan s1, string s2, ReadOnlySpan s3, char c) => c + string.Concat(s1, s2.AsSpan(), s3); + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + comp.MakeMemberMissing((WellKnownMember)member); + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abcddabc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Test.M1", """ + { + // Code size 26 (0x1a) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0007: ldarg.2 + IL_0008: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_000d: ldarga.s V_3 + IL_000f: call "string char.ToString()" + IL_0014: call "string string.Concat(string, string)" + IL_0019: ret + } + """); + verifier.VerifyIL("Test.M2", """ + { + // Code size 26 (0x1a) + .maxstack 4 + IL_0000: ldarga.s V_3 + IL_0002: call "string char.ToString()" + IL_0007: ldarg.0 + IL_0008: ldarg.1 + IL_0009: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_000e: ldarg.2 + IL_000f: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0014: call "string string.Concat(string, string)" + IL_0019: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + public void ConcatFive_Char(int? missingUnimportantWellKnownMember) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s = "s"; + var c = 'c'; + Console.Write(CharInFirstFourArgs(s, c)); + Console.Write(CharAfterFirstFourArgs(s, c)); + } + + static string CharInFirstFourArgs(string s, char c) => s + c + s + s + s; + static string CharAfterFirstFourArgs(string s, char c) => s + s + s + s + c; + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "scsssssssc" : null, verify: ExecutionConditionUtil.IsCoreClr ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + + // When lengths of inputs are low it is actually more optimal to first concat 4 operands with span-based concat and then concat that result with the remaining operand. + // However when inputs become large enough cost of allocating a params array becomes lower than cost of creating an intermediate string. + // + code size for using params array is less than code size from intermediate concat approach, especially when the amount of operands is high. + // So in the end we always prefer overload with params array when there are 5+ operands + verifier.VerifyIL("Test.CharInFirstFourArgs", """ + { + // Code size 38 (0x26) + .maxstack 4 + IL_0000: ldc.i4.5 + IL_0001: newarr "string" + IL_0006: dup + IL_0007: ldc.i4.0 + IL_0008: ldarg.0 + IL_0009: stelem.ref + IL_000a: dup + IL_000b: ldc.i4.1 + IL_000c: ldarga.s V_1 + IL_000e: call "string char.ToString()" + IL_0013: stelem.ref + IL_0014: dup + IL_0015: ldc.i4.2 + IL_0016: ldarg.0 + IL_0017: stelem.ref + IL_0018: dup + IL_0019: ldc.i4.3 + IL_001a: ldarg.0 + IL_001b: stelem.ref + IL_001c: dup + IL_001d: ldc.i4.4 + IL_001e: ldarg.0 + IL_001f: stelem.ref + IL_0020: call "string string.Concat(params string[])" + IL_0025: ret + } + """); + verifier.VerifyIL("Test.CharAfterFirstFourArgs", """ + { + // Code size 38 (0x26) + .maxstack 4 + IL_0000: ldc.i4.5 + IL_0001: newarr "string" + IL_0006: dup + IL_0007: ldc.i4.0 + IL_0008: ldarg.0 + IL_0009: stelem.ref + IL_000a: dup + IL_000b: ldc.i4.1 + IL_000c: ldarg.0 + IL_000d: stelem.ref + IL_000e: dup + IL_000f: ldc.i4.2 + IL_0010: ldarg.0 + IL_0011: stelem.ref + IL_0012: dup + IL_0013: ldc.i4.3 + IL_0014: ldarg.0 + IL_0015: stelem.ref + IL_0016: dup + IL_0017: ldc.i4.4 + IL_0018: ldarga.s V_1 + IL_001a: call "string char.ToString()" + IL_001f: stelem.ref + IL_0020: call "string string.Concat(params string[])" + IL_0025: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData("(s + s) + c + s + s")] + [InlineData("s + (s + c) + s + s")] + [InlineData("s + s + (c + s) + s")] + [InlineData("s + s + c + (s + s)")] + [InlineData("(s + s + c) + s + s")] + [InlineData("s + (s + c + s) + s")] + [InlineData("s + s + (c + s + s)")] + [InlineData("(s + s + c + s) + s")] + [InlineData("s + (s + c + s + s)")] + [InlineData("(s + s) + (c + s) + s")] + [InlineData("(s + s) + c + (s + s)")] [InlineData("s + (s + c) + (s + s)")] [InlineData("(s + s + c) + (s + s)")] [InlineData("(s + s) + (c + s + s)")] @@ -2217,7 +3457,258 @@ .maxstack 4 [InlineData("string.Concat(s, s) + string.Concat(c.ToString(), s, s)")] public void ConcatFive_Char_OperandGroupingAndUserInputOfStringBasedConcats(string expression) { - var source = $$""" + var source = $$""" + using System; + + public class Test + { + static void Main() + { + var s = "s"; + var c = 'c'; + Console.Write(M(s, c)); + } + + static string M(string s, char c) => {{expression}}; + } + """; + + var comp = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "sscss" : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.M", """ + { + // Code size 38 (0x26) + .maxstack 4 + IL_0000: ldc.i4.5 + IL_0001: newarr "string" + IL_0006: dup + IL_0007: ldc.i4.0 + IL_0008: ldarg.0 + IL_0009: stelem.ref + IL_000a: dup + IL_000b: ldc.i4.1 + IL_000c: ldarg.0 + IL_000d: stelem.ref + IL_000e: dup + IL_000f: ldc.i4.2 + IL_0010: ldarga.s V_1 + IL_0012: call "string char.ToString()" + IL_0017: stelem.ref + IL_0018: dup + IL_0019: ldc.i4.3 + IL_001a: ldarg.0 + IL_001b: stelem.ref + IL_001c: dup + IL_001d: ldc.i4.4 + IL_001e: ldarg.0 + IL_001f: stelem.ref + IL_0020: call "string string.Concat(params string[])" + IL_0025: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + public void ConcatFiveCharToStrings(int? missingUnimportantWellKnownMember) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var c1 = 'a'; + var c2 = 'b'; + var c3 = 'c'; + var c4 = 'd'; + var c5 = 'e'; + Console.Write(M(c1, c2, c3, c4, c5)); + } + + static string M(char c1, char c2, char c3, char c4, char c5) => c1.ToString() + c2.ToString() + c3.ToString() + c4.ToString() + c5.ToString(); + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abcde" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Test.M", """ + { + // Code size 62 (0x3e) + .maxstack 4 + IL_0000: ldc.i4.5 + IL_0001: newarr "string" + IL_0006: dup + IL_0007: ldc.i4.0 + IL_0008: ldarga.s V_0 + IL_000a: call "string char.ToString()" + IL_000f: stelem.ref + IL_0010: dup + IL_0011: ldc.i4.1 + IL_0012: ldarga.s V_1 + IL_0014: call "string char.ToString()" + IL_0019: stelem.ref + IL_001a: dup + IL_001b: ldc.i4.2 + IL_001c: ldarga.s V_2 + IL_001e: call "string char.ToString()" + IL_0023: stelem.ref + IL_0024: dup + IL_0025: ldc.i4.3 + IL_0026: ldarga.s V_3 + IL_0028: call "string char.ToString()" + IL_002d: stelem.ref + IL_002e: dup + IL_002f: ldc.i4.4 + IL_0030: ldarga.s V_4 + IL_0032: call "string char.ToString()" + IL_0037: stelem.ref + IL_0038: call "string string.Concat(params string[])" + IL_003d: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + public void ConcatTotalOfFive_UserInputOfSpanBasedConcatOf2(int? missingUnimportantWellKnownMember) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s = "s"; + var c = 'c'; + Console.Write(M1(s, c)); + Console.Write(M2(s, c)); + Console.Write(M3(s, c)); + Console.Write(M4(s, c)); + } + + static string M1(string s, char c) => string.Concat(s.AsSpan(), s.AsSpan()) + c + s + s; + static string M2(string s, char c) => s + string.Concat(s.AsSpan(), s.AsSpan()) + s + c; + static string M3(string s, char c) => s + s + c + string.Concat(s.AsSpan(), s.AsSpan()); + static string M4(string s, char c) => string.Concat(s.AsSpan(), s.AsSpan()) + c + string.Concat(s.AsSpan(), s.AsSpan()); + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "sscsssssscsscsssscss" : null, verify: ExecutionConditionUtil.IsCoreClr ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + + // Since combined length of arguments if we unwrap all user-provided span-based concats is >4, + // we just lower outer concat as is without unwrapping any user-provided ones. + verifier.VerifyIL("Test.M1", """ + { + // Code size 32 (0x20) + .maxstack 4 + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0006: ldarg.0 + IL_0007: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_000c: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0011: ldarga.s V_1 + IL_0013: call "string char.ToString()" + IL_0018: ldarg.0 + IL_0019: ldarg.0 + IL_001a: call "string string.Concat(string, string, string, string)" + IL_001f: ret + } + """); + verifier.VerifyIL("Test.M2", """ + { + // Code size 32 (0x20) + .maxstack 4 + IL_0000: ldarg.0 + IL_0001: ldarg.0 + IL_0002: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0007: ldarg.0 + IL_0008: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_000d: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0012: ldarg.0 + IL_0013: ldarga.s V_1 + IL_0015: call "string char.ToString()" + IL_001a: call "string string.Concat(string, string, string, string)" + IL_001f: ret + } + """); + verifier.VerifyIL("Test.M3", """ + { + // Code size 32 (0x20) + .maxstack 5 + IL_0000: ldarg.0 + IL_0001: ldarg.0 + IL_0002: ldarga.s V_1 + IL_0004: call "string char.ToString()" + IL_0009: ldarg.0 + IL_000a: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_000f: ldarg.0 + IL_0010: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0015: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001a: call "string string.Concat(string, string, string, string)" + IL_001f: ret + } + """); + verifier.VerifyIL("Test.M4", """ + { + // Code size 47 (0x2f) + .maxstack 4 + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0006: ldarg.0 + IL_0007: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_000c: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0011: ldarga.s V_1 + IL_0013: call "string char.ToString()" + IL_0018: ldarg.0 + IL_0019: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_001e: ldarg.0 + IL_001f: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0024: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0029: call "string string.Concat(string, string, string)" + IL_002e: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + public void ConcatTotalOfFive_UserInputOfSpanBasedConcatOf3(int? missingUnimportantWellKnownMember) + { + var source = """ using System; public class Test @@ -2226,45 +3717,82 @@ static void Main() { var s = "s"; var c = 'c'; - Console.Write(M(s, c)); + Console.Write(M1(s, c)); + Console.Write(M2(s, c)); + Console.Write(M3(s, c)); } - static string M(string s, char c) => {{expression}}; + static string M1(string s, char c) => string.Concat(s.AsSpan(), s.AsSpan(), s.AsSpan()) + c + s; + static string M2(string s, char c) => s + string.Concat(s.AsSpan(), s.AsSpan(), s.AsSpan()) + c; + static string M3(string s, char c) => s + c + string.Concat(s.AsSpan(), s.AsSpan(), s.AsSpan()); } """; - var comp = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "sscss" : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - comp.VerifyDiagnostics(); - comp.VerifyIL("Test.M", """ + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "ssscssssscscsss" : null, verify: ExecutionConditionUtil.IsCoreClr ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + + // Since combined length of arguments if we unwrap all user-provided span-based concats is >4, + // we just lower outer concat as is without unwrapping any user-provided ones. + verifier.VerifyIL("Test.M1", """ { - // Code size 38 (0x26) - .maxstack 4 - IL_0000: ldc.i4.5 - IL_0001: newarr "string" - IL_0006: dup - IL_0007: ldc.i4.0 - IL_0008: ldarg.0 - IL_0009: stelem.ref - IL_000a: dup - IL_000b: ldc.i4.1 + // Code size 37 (0x25) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0006: ldarg.0 + IL_0007: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" IL_000c: ldarg.0 - IL_000d: stelem.ref - IL_000e: dup - IL_000f: ldc.i4.2 - IL_0010: ldarga.s V_1 - IL_0012: call "string char.ToString()" - IL_0017: stelem.ref - IL_0018: dup - IL_0019: ldc.i4.3 - IL_001a: ldarg.0 - IL_001b: stelem.ref - IL_001c: dup - IL_001d: ldc.i4.4 + IL_000d: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0012: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0017: ldarga.s V_1 + IL_0019: call "string char.ToString()" IL_001e: ldarg.0 - IL_001f: stelem.ref - IL_0020: call "string string.Concat(params string[])" - IL_0025: ret + IL_001f: call "string string.Concat(string, string, string)" + IL_0024: ret + } + """); + verifier.VerifyIL("Test.M2", """ + { + // Code size 37 (0x25) + .maxstack 4 + IL_0000: ldarg.0 + IL_0001: ldarg.0 + IL_0002: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0007: ldarg.0 + IL_0008: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_000d: ldarg.0 + IL_000e: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0013: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0018: ldarga.s V_1 + IL_001a: call "string char.ToString()" + IL_001f: call "string string.Concat(string, string, string)" + IL_0024: ret + } + """); + verifier.VerifyIL("Test.M3", """ + { + // Code size 37 (0x25) + .maxstack 5 + IL_0000: ldarg.0 + IL_0001: ldarga.s V_1 + IL_0003: call "string char.ToString()" + IL_0008: ldarg.0 + IL_0009: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_000e: ldarg.0 + IL_000f: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0014: ldarg.0 + IL_0015: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_001a: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001f: call "string string.Concat(string, string, string)" + IL_0024: ret } """); } @@ -2273,10 +3801,8 @@ .maxstack 4 [InlineData(null)] [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] - public void ConcatFiveCharToStrings(int? missingUnimportantWellKnownMember) + public void ConcatTotalOfFive_UserInputOfMixedSpanBasedConcatsOf2And3(int? missingUnimportantWellKnownMember) { var source = """ using System; @@ -2285,15 +3811,13 @@ public class Test { static void Main() { - var c1 = 'a'; - var c2 = 'b'; - var c3 = 'c'; - var c4 = 'd'; - var c5 = 'e'; - Console.Write(M(c1, c2, c3, c4, c5)); + var s = "s"; + Console.Write(M1(s)); + Console.Write(M2(s)); } - static string M(char c1, char c2, char c3, char c4, char c5) => c1.ToString() + c2.ToString() + c3.ToString() + c4.ToString() + c5.ToString(); + static string M1(string s) => string.Concat(s.AsSpan(), s.AsSpan()) + string.Concat(s.AsSpan(), s.AsSpan(), s.AsSpan()); + static string M2(string s) => string.Concat(s.AsSpan(), s.AsSpan(), s.AsSpan()) + string.Concat(s.AsSpan(), s.AsSpan()); } """; @@ -2304,42 +3828,217 @@ static void Main() comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); } - var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abcde" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "ssssssssss" : null, verify: ExecutionConditionUtil.IsCoreClr ? default : Verification.Skipped); verifier.VerifyDiagnostics(); - verifier.VerifyIL("Test.M", """ + + // Since combined length of arguments if we unwrap all user-provided span-based concats is >4, + // we just lower outer concat as is without unwrapping any user-provided ones. + verifier.VerifyIL("Test.M1", """ { - // Code size 62 (0x3e) + // Code size 46 (0x2e) .maxstack 4 - IL_0000: ldc.i4.5 - IL_0001: newarr "string" - IL_0006: dup - IL_0007: ldc.i4.0 - IL_0008: ldarga.s V_0 - IL_000a: call "string char.ToString()" - IL_000f: stelem.ref - IL_0010: dup - IL_0011: ldc.i4.1 - IL_0012: ldarga.s V_1 - IL_0014: call "string char.ToString()" - IL_0019: stelem.ref - IL_001a: dup - IL_001b: ldc.i4.2 - IL_001c: ldarga.s V_2 - IL_001e: call "string char.ToString()" - IL_0023: stelem.ref - IL_0024: dup - IL_0025: ldc.i4.3 - IL_0026: ldarga.s V_3 - IL_0028: call "string char.ToString()" - IL_002d: stelem.ref - IL_002e: dup - IL_002f: ldc.i4.4 - IL_0030: ldarga.s V_4 - IL_0032: call "string char.ToString()" - IL_0037: stelem.ref - IL_0038: call "string string.Concat(params string[])" - IL_003d: ret + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0006: ldarg.0 + IL_0007: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_000c: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0011: ldarg.0 + IL_0012: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0017: ldarg.0 + IL_0018: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_001d: ldarg.0 + IL_001e: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0023: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0028: call "string string.Concat(string, string)" + IL_002d: ret + } + """); + verifier.VerifyIL("Test.M2", """ + { + // Code size 46 (0x2e) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0006: ldarg.0 + IL_0007: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_000c: ldarg.0 + IL_000d: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0012: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0017: ldarg.0 + IL_0018: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_001d: ldarg.0 + IL_001e: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0023: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0028: call "string string.Concat(string, string)" + IL_002d: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + public void ConcatTotalOfMoreThanFive_UserInputOf2SpanBasedConcatsOf2InARow(int? missingUnimportantWellKnownMember) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s = "s"; + Console.Write(M1(s)); + Console.Write(M2(s)); + } + + static string M1(string s) => string.Concat(s.AsSpan(), s.AsSpan()) + string.Concat(s.AsSpan(), s.AsSpan()) + string.Concat(s.AsSpan(), s.AsSpan(), s.AsSpan()); + static string M2(string s) => string.Concat(s.AsSpan(), s.AsSpan(), s.AsSpan()) + string.Concat(s.AsSpan(), s.AsSpan()) + string.Concat(s.AsSpan(), s.AsSpan()); + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "ssssssssssssss" : null, verify: ExecutionConditionUtil.IsCoreClr ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + + // This is an edge case where due to order of processing arguments during rewriting we manage to merge 2 concats of 2 into a single concat of 4 in the first case (which changes what we get in the end), + // but in the second case we leave things untouched. Both lowerings produce correct output in the end, so this isn't much to worry about + verifier.VerifyIL("Test.M1", """ + { + // Code size 58 (0x3a) + .maxstack 4 + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0006: ldarg.0 + IL_0007: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_000c: ldarg.0 + IL_000d: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0012: ldarg.0 + IL_0013: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0018: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001d: ldarg.0 + IL_001e: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0023: ldarg.0 + IL_0024: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0029: ldarg.0 + IL_002a: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_002f: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0034: call "string string.Concat(string, string)" + IL_0039: ret + } + """); + verifier.VerifyIL("Test.M2", """ + { + // Code size 63 (0x3f) + .maxstack 4 + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0006: ldarg.0 + IL_0007: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_000c: ldarg.0 + IL_000d: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0012: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0017: ldarg.0 + IL_0018: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_001d: ldarg.0 + IL_001e: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0023: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0028: ldarg.0 + IL_0029: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_002e: ldarg.0 + IL_002f: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0034: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0039: call "string string.Concat(string, string, string)" + IL_003e: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatTotalOfMoreThanFive_UserInputOf2SpanBasedConcatsOf2InARow_MissingSpanBasedConcatOf4() + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s = "s"; + Console.Write(M1(s)); + Console.Write(M2(s)); + } + + static string M1(string s) => string.Concat(s.AsSpan(), s.AsSpan()) + string.Concat(s.AsSpan(), s.AsSpan()) + string.Concat(s.AsSpan(), s.AsSpan(), s.AsSpan()); + static string M2(string s) => string.Concat(s.AsSpan(), s.AsSpan(), s.AsSpan()) + string.Concat(s.AsSpan(), s.AsSpan()) + string.Concat(s.AsSpan(), s.AsSpan()); + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + comp.MakeMemberMissing(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan); + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "ssssssssssssss" : null, verify: ExecutionConditionUtil.IsCoreClr ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + + // If span-based concat of 4 is missing we produce Il equivalent to what user has written without any changes + verifier.VerifyIL("Test.M1", """ + { + // Code size 63 (0x3f) + .maxstack 5 + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0006: ldarg.0 + IL_0007: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_000c: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0011: ldarg.0 + IL_0012: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0017: ldarg.0 + IL_0018: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_001d: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0022: ldarg.0 + IL_0023: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0028: ldarg.0 + IL_0029: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_002e: ldarg.0 + IL_002f: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0034: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0039: call "string string.Concat(string, string, string)" + IL_003e: ret + } + """); + verifier.VerifyIL("Test.M2", """ + { + // Code size 63 (0x3f) + .maxstack 4 + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0006: ldarg.0 + IL_0007: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_000c: ldarg.0 + IL_000d: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0012: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0017: ldarg.0 + IL_0018: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_001d: ldarg.0 + IL_001e: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0023: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0028: ldarg.0 + IL_0029: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_002e: ldarg.0 + IL_002f: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0034: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0039: call "string string.Concat(string, string, string)" + IL_003e: ret } """); } From a288d21442aa643a8a6d5e324dfd7977b5d1a6c3 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Wed, 24 Jan 2024 19:04:21 +0300 Subject: [PATCH 10/71] Handle missing `object.ToString` --- .../Lowering/LocalRewriter/LocalRewriter.cs | 8 +- .../LocalRewriter_StringConcat.cs | 10 +- .../CodeGenSpanBasedStringConcatTests.cs | 152 ++++++++++++++++++ .../Symbol/Symbols/MissingSpecialMember.cs | 2 +- 4 files changed, 163 insertions(+), 9 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs index bcadd43302ad3..d00b23dfa9561 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs @@ -571,9 +571,9 @@ private bool TryGetWellKnownTypeMember(SyntaxNode? syntax, WellKnownMem /// Recommendation: Do not use, use instead! /// If used, a unit-test with a missing member is absolutely a must have. /// - private MethodSymbol UnsafeGetSpecialTypeMethod(SyntaxNode syntax, SpecialMember specialMember) + private MethodSymbol UnsafeGetSpecialTypeMethod(SyntaxNode syntax, SpecialMember specialMember, bool isOptional = false) { - return UnsafeGetSpecialTypeMethod(syntax, specialMember, _compilation, _diagnostics); + return UnsafeGetSpecialTypeMethod(syntax, specialMember, _compilation, _diagnostics, isOptional); } /// @@ -581,10 +581,10 @@ private MethodSymbol UnsafeGetSpecialTypeMethod(SyntaxNode syntax, SpecialMember /// Recommendation: Do not use, use instead! /// If used, a unit-test with a missing member is absolutely a must have. /// - private static MethodSymbol UnsafeGetSpecialTypeMethod(SyntaxNode syntax, SpecialMember specialMember, CSharpCompilation compilation, BindingDiagnosticBag diagnostics) + private static MethodSymbol UnsafeGetSpecialTypeMethod(SyntaxNode syntax, SpecialMember specialMember, CSharpCompilation compilation, BindingDiagnosticBag diagnostics, bool isOptional = false) { MethodSymbol method; - if (TryGetSpecialTypeMethod(syntax, specialMember, compilation, diagnostics, out method)) + if (TryGetSpecialTypeMethod(syntax, specialMember, compilation, diagnostics, out method, isOptional)) { return method; } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index dff7d2c6659e6..593b1b6cd3124 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -467,12 +467,14 @@ private bool TryRewriteStringConcatenationWithSpanBasedConcat(SyntaxNode syntax, var needsSpanRefParamConstructor = false; var needsImplicitConversionFromStringToSpan = false; - var objectToStringMethod = UnsafeGetSpecialTypeMethod(syntax, SpecialMember.System_Object__ToString); + // When we get here we 100% already queried for object.ToString in `ConvertConcatExprToString`. + // Thus we can pass `isOptional` flag to avoid duplicate "missing member" diagnostic in case we are actually missing object.ToString + var objectToStringMethod = UnsafeGetSpecialTypeMethod(syntax, SpecialMember.System_Object__ToString, isOptional: true); NamedTypeSymbol? charType = null; foreach (var arg in args) { - Debug.Assert(arg.Type?.IsStringType() == true); + Debug.Assert(arg.HasAnyErrors || arg.Type?.IsStringType() == true); if (arg is BoundCall { ReceiverOpt: { Type: NamedTypeSymbol { SpecialType: SpecialType.System_Char } receiverCharType } receiver } potentialToStringCall && (object)potentialToStringCall.Method.GetLeastOverriddenMethod(charType) == objectToStringMethod) @@ -622,7 +624,7 @@ static BoundExpression rewriteStringConcatenationWithSpanBasedConcat( } else { - Debug.Assert(arg.Type.SpecialType == SpecialType.System_String); + Debug.Assert(arg.HasAnyErrors || arg.Type.SpecialType == SpecialType.System_String); Debug.Assert(stringImplicitConversionToReadOnlySpan is not null); preparedArgsBuilder.Add(BoundCall.Synthesized(arg.Syntax, receiverOpt: null, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, stringImplicitConversionToReadOnlySpan, arg)); } @@ -710,7 +712,7 @@ private BoundExpression ConvertConcatExprToString(SyntaxNode syntax, BoundExpres // Evaluate toString at the last possible moment, to avoid spurious diagnostics if it's missing. // All code paths below here use it. - var objectToStringMethod = UnsafeGetSpecialTypeMethod(syntax, SpecialMember.System_Object__ToString); + var objectToStringMethod = UnsafeGetSpecialTypeMethod(expr.Syntax, SpecialMember.System_Object__ToString); // If it's a struct which has overridden ToString, find that method. Note that we might fail to // find it, e.g. if object.ToString is missing diff --git a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs index 3fb7e65cae66e..5ab92532efd7c 100644 --- a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs @@ -409,6 +409,41 @@ .maxstack 2 """); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatTwo_MissingObjectToString() + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s = "s"; + var c = 'c'; + Console.Write(M1(s, c)); + Console.Write(M2(s, c)); + } + + static string M1(string s, char c) => s + c; + static string M2(string s, char c) => c + s; + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + comp.MakeMemberMissing(SpecialMember.System_Object__ToString); + + // Although we don't use object.ToString() or char.ToString() in the final codegen we still need object.ToString() during lowering. + // Moreover, we previously reported these errors anyway, so this is not a behavioral change + comp.VerifyEmitDiagnostics( + // (13,47): error CS0656: Missing compiler required member 'System.Object.ToString' + // static string M1(string s, char c) => s + c; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "c").WithArguments("System.Object", "ToString").WithLocation(13, 47), + // (14,43): error CS0656: Missing compiler required member 'System.Object.ToString' + // static string M2(string s, char c) => c + s; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "c").WithArguments("System.Object", "ToString").WithLocation(14, 43)); + } + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] @@ -1328,6 +1363,54 @@ .maxstack 3 """); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatThree_MissingObjectToString() + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s = "s"; + var c = 'c'; + Console.Write(M1(s, c)); + Console.Write(M2(s, c)); + Console.Write(M3(s, c)); + Console.Write(M4(s, c)); + } + + static string M1(string s, char c) => c + s + s; + static string M2(string s, char c) => s + c + s; + static string M3(string s, char c) => s + s + c; + static string M4(string s, char c) => c + s + c; + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + comp.MakeMemberMissing(SpecialMember.System_Object__ToString); + + // Although we don't use object.ToString() or char.ToString() in the final codegen we still need object.ToString() during lowering. + // Moreover, we previously reported these errors anyway, so this is not a behavioral change + comp.VerifyEmitDiagnostics( + // (15,43): error CS0656: Missing compiler required member 'System.Object.ToString' + // static string M1(string s, char c) => c + s + s; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "c").WithArguments("System.Object", "ToString").WithLocation(15, 43), + // (16,47): error CS0656: Missing compiler required member 'System.Object.ToString' + // static string M2(string s, char c) => s + c + s; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "c").WithArguments("System.Object", "ToString").WithLocation(16, 47), + // (17,51): error CS0656: Missing compiler required member 'System.Object.ToString' + // static string M3(string s, char c) => s + s + c; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "c").WithArguments("System.Object", "ToString").WithLocation(17, 51), + // (18,43): error CS0656: Missing compiler required member 'System.Object.ToString' + // static string M4(string s, char c) => c + s + c; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "c").WithArguments("System.Object", "ToString").WithLocation(18, 43), + // (18,51): error CS0656: Missing compiler required member 'System.Object.ToString' + // static string M4(string s, char c) => c + s + c; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "c").WithArguments("System.Object", "ToString").WithLocation(18, 51)); + } + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] @@ -3320,6 +3403,75 @@ .maxstack 4 """); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatFour_MissingObjectToString() + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s = "s"; + var c = 'c'; + Console.Write(M1(s, c)); + Console.Write(M2(s, c)); + Console.Write(M3(s, c)); + Console.Write(M4(s, c)); + Console.Write(M5(s, c)); + Console.Write(M6(s, c)); + Console.Write(M7(s, c)); + } + + static string M1(string s, char c) => c + s + s + s; + static string M2(string s, char c) => s + c + s + s; + static string M3(string s, char c) => s + s + c + s; + static string M4(string s, char c) => s + s + s + c; + static string M5(string s, char c) => c + s + c + s; + static string M6(string s, char c) => s + c + s + c; + static string M7(string s, char c) => c + s + s + c; + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + comp.MakeMemberMissing(SpecialMember.System_Object__ToString); + + // Although we don't use object.ToString() or char.ToString() in the final codegen we still need object.ToString() during lowering. + // Moreover, we previously reported these errors anyway, so this is not a behavioral change + comp.VerifyEmitDiagnostics( + // (18,43): error CS0656: Missing compiler required member 'System.Object.ToString' + // static string M1(string s, char c) => c + s + s + s; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "c").WithArguments("System.Object", "ToString").WithLocation(18, 43), + // (19,47): error CS0656: Missing compiler required member 'System.Object.ToString' + // static string M2(string s, char c) => s + c + s + s; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "c").WithArguments("System.Object", "ToString").WithLocation(19, 47), + // (20,51): error CS0656: Missing compiler required member 'System.Object.ToString' + // static string M3(string s, char c) => s + s + c + s; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "c").WithArguments("System.Object", "ToString").WithLocation(20, 51), + // (21,55): error CS0656: Missing compiler required member 'System.Object.ToString' + // static string M4(string s, char c) => s + s + s + c; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "c").WithArguments("System.Object", "ToString").WithLocation(21, 55), + // (22,43): error CS0656: Missing compiler required member 'System.Object.ToString' + // static string M5(string s, char c) => c + s + c + s; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "c").WithArguments("System.Object", "ToString").WithLocation(22, 43), + // (22,51): error CS0656: Missing compiler required member 'System.Object.ToString' + // static string M5(string s, char c) => c + s + c + s; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "c").WithArguments("System.Object", "ToString").WithLocation(22, 51), + // (23,47): error CS0656: Missing compiler required member 'System.Object.ToString' + // static string M6(string s, char c) => s + c + s + c; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "c").WithArguments("System.Object", "ToString").WithLocation(23, 47), + // (23,55): error CS0656: Missing compiler required member 'System.Object.ToString' + // static string M6(string s, char c) => s + c + s + c; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "c").WithArguments("System.Object", "ToString").WithLocation(23, 55), + // (24,43): error CS0656: Missing compiler required member 'System.Object.ToString' + // static string M7(string s, char c) => c + s + s + c; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "c").WithArguments("System.Object", "ToString").WithLocation(24, 43), + // (24,55): error CS0656: Missing compiler required member 'System.Object.ToString' + // static string M7(string s, char c) => c + s + s + c; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "c").WithArguments("System.Object", "ToString").WithLocation(24, 55)); + } + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs index c15dbfc1841df..56001ba2a9581 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs @@ -2408,7 +2408,7 @@ static void Main() compilation.VerifyEmitDiagnostics( // (9,27): error CS0656: Missing compiler required member 'System.Object.ToString' // Console.WriteLine(c + "3"); - Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"c + ""3""").WithArguments("System.Object", "ToString").WithLocation(9, 27) + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "c").WithArguments("System.Object", "ToString").WithLocation(9, 27) ); } From 176f3e61fd14861fef1a85219fc922205de81dbf Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Wed, 24 Jan 2024 22:31:28 +0300 Subject: [PATCH 11/71] Handle `constantChar.ToString()` case --- .../LocalRewriter_StringConcat.cs | 23 +- .../CodeGenSpanBasedStringConcatTests.cs | 414 ++++++++++++++++++ 2 files changed, 434 insertions(+), 3 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index 593b1b6cd3124..22ca497a15cf4 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -467,8 +467,8 @@ private bool TryRewriteStringConcatenationWithSpanBasedConcat(SyntaxNode syntax, var needsSpanRefParamConstructor = false; var needsImplicitConversionFromStringToSpan = false; - // When we get here we 100% already queried for object.ToString in `ConvertConcatExprToString`. - // Thus we can pass `isOptional` flag to avoid duplicate "missing member" diagnostic in case we are actually missing object.ToString + // When we get here we either 100% already queried for object.ToString in `ConvertConcatExprToString` (if at least 1 operand is not a string) or don't need it (if all operands are strings). + // Thus we can pass `isOptional` flag to avoid duplicate or unintentional "missing member" diagnostic in case we are actually missing `object.ToString()` var objectToStringMethod = UnsafeGetSpecialTypeMethod(syntax, SpecialMember.System_Object__ToString, isOptional: true); NamedTypeSymbol? charType = null; @@ -529,7 +529,11 @@ private bool TryRewriteStringConcatenationWithSpanBasedConcat(SyntaxNode syntax, var concatMember = GetSpanConcatMemberByArgumentsCount(preparedArgsIfUnwrapUserStringConcat.Count); - if (concatMember.HasValue && + // If we got here it only makes sense to lower using span-based concat if at least one operand is a char. + // Because otherwise we will just unnecessarily wrap every string operand into span conversion and use span-based concat + // which is unnecessary IL bloat. Thus we require `needsSpanRefParamConstructor` to be true + if (needsSpanRefParamConstructor && + concatMember.HasValue && TryGetWellKnownTypeMember(syntax, concatMember.Value, out spanConcat, isOptional: true) && tryGetNeededToSpanMembers(this, syntax, needsSpanRefParamConstructor, needsImplicitConversionFromStringToSpan, charType, out readOnlySpanCtorRefParamChar, out stringImplicitConversionToReadOnlySpan)) { @@ -704,6 +708,19 @@ private BoundExpression ConvertConcatExprToString(SyntaxNode syntax, BoundExpres } } + // If expression is of form `constantChar.ToString()` then rewrite it here to a string literal so we can choose better options for lowering later (e.g. fold it with another constant instead of performing concatenation) + // NOTE: We request `object.ToString()` as an optional member here to avoid reporting "missing member" diagnostic earlier than we should. + // Because if in the end it turns out that we have a simple concatenation of n strings there is no need to report missing `object.ToString()` member + if (TryGetSpecialTypeMethod(expr.Syntax, SpecialMember.System_Object__ToString, out MethodSymbol? optionalObjectToString, isOptional: true) && + expr is BoundCall { ReceiverOpt: { Type: NamedTypeSymbol { SpecialType: SpecialType.System_Char } charType, ConstantValueOpt: { IsChar: true } charConstant } } call && + call.Method.GetLeastOverriddenMember(charType) == optionalObjectToString) + { + var oldSyntax = _factory.Syntax; + _factory.Syntax = expr.Syntax; + expr = _factory.Literal(charConstant.CharValue.ToString()); + _factory.Syntax = oldSyntax; + } + // If it's a string already, just return it if (expr.Type.IsStringType()) { diff --git a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs index 5ab92532efd7c..070ec2673698d 100644 --- a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs @@ -304,6 +304,108 @@ .locals init (char V_0, //c """); } + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + public void ConcatTwo_ConstantCharToString(int? missingUnimportantWellKnownMember) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s = "s"; + Console.Write(M1(s)); + Console.Write(M2(s)); + } + + static string M1(string s) => s + 'c'.ToString(); + static string M2(string s) => 'c'.ToString() + s; + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "sccs" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + + // Instead of emitting this as a span-based concat of string and char we recognize "constantChar.ToString()" pattern and lower that argument to a constant string + verifier.VerifyIL("Test.M1", """ + { + // Code size 12 (0xc) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldstr "c" + IL_0006: call "string string.Concat(string, string)" + IL_000b: ret + } + """); + verifier.VerifyIL("Test.M2", """ + { + // Code size 12 (0xc) + .maxstack 2 + IL_0000: ldstr "c" + IL_0005: ldarg.0 + IL_0006: call "string string.Concat(string, string)" + IL_000b: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + public void ConcatTwo_AllConstantCharToStrings(int? missingUnimportantWellKnownMember) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + Console.Write(M()); + } + + static string M() => 'a'.ToString() + 'b'.ToString(); + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "ab" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + + // Instead of emitting this as a span-based concat of 2 chars we recognize "constantChar.ToString()" pattern and lower both arguments to a constant string + // which we can then fold into a single constant string and avoid concatenation entirely + verifier.VerifyIL("Test.M", """ + { + // Code size 6 (0x6) + .maxstack 1 + IL_0000: ldstr "ab" + IL_0005: ret + } + """); + } + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] @@ -972,6 +1074,138 @@ .locals init (char V_0, //c """); } + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + public void ConcatThree_ConstantCharToString(int? missingUnimportantWellKnownMember) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s = "s"; + Console.Write(M1(s)); + Console.Write(M2(s)); + Console.Write(M3(s)); + Console.Write(M4(s)); + } + + static string M1(string s) => 'c'.ToString() + s + s; + static string M2(string s) => s + 'c'.ToString() + s; + static string M3(string s) => s + s + 'c'.ToString(); + static string M4(string s) => 'c'.ToString() + s + 'c'.ToString(); + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "cssscsssccsc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + + // Instead of emitting this as a span-based concat of strings and chars we recognize "constantChar.ToString()" pattern and lower that arguments to constant strings + verifier.VerifyIL("Test.M1", """ + { + // Code size 13 (0xd) + .maxstack 3 + IL_0000: ldstr "c" + IL_0005: ldarg.0 + IL_0006: ldarg.0 + IL_0007: call "string string.Concat(string, string, string)" + IL_000c: ret + } + """); + verifier.VerifyIL("Test.M2", """ + { + // Code size 13 (0xd) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: ldstr "c" + IL_0006: ldarg.0 + IL_0007: call "string string.Concat(string, string, string)" + IL_000c: ret + } + """); + verifier.VerifyIL("Test.M3", """ + { + // Code size 13 (0xd) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: ldarg.0 + IL_0002: ldstr "c" + IL_0007: call "string string.Concat(string, string, string)" + IL_000c: ret + } + """); + verifier.VerifyIL("Test.M4", """ + { + // Code size 17 (0x11) + .maxstack 3 + IL_0000: ldstr "c" + IL_0005: ldarg.0 + IL_0006: ldstr "c" + IL_000b: call "string string.Concat(string, string, string)" + IL_0010: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + public void ConcatThree_AllConstantCharToStrings(int? missingUnimportantWellKnownMember) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + Console.Write(M()); + } + + static string M() => 'a'.ToString() + 'b'.ToString() + 'c'.ToString(); + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + + // Instead of emitting this as a span-based concat of 3 chars we recognize "constantChar.ToString()" pattern and lower all arguments to a constant string + // which we can then fold into a single constant string and avoid concatenation entirely + verifier.VerifyIL("Test.M", """ + { + // Code size 6 (0x6) + .maxstack 1 + IL_0000: ldstr "abc" + IL_0005: ret + } + """); + } + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] @@ -2210,6 +2444,186 @@ .locals init (char V_0, //c1 """); } + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + public void ConcatFour_ConstantCharToString(int? missingUnimportantWellKnownMember) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s = "s"; + Console.Write(M1(s)); + Console.Write(M2(s)); + Console.Write(M3(s)); + Console.Write(M4(s)); + Console.Write(M5(s)); + Console.Write(M6(s)); + Console.Write(M7(s)); + } + + static string M1(string s) => 'c'.ToString() + s + s + s; + static string M2(string s) => s + 'c'.ToString() + s + s; + static string M3(string s) => s + s + 'c'.ToString() + s; + static string M4(string s) => s + s + s + 'c'.ToString(); + static string M5(string s) => 'c'.ToString() + s + 'c'.ToString() + s; + static string M6(string s) => s + 'c'.ToString() + s + 'c'.ToString(); + static string M7(string s) => 'c'.ToString() + s + s + 'c'.ToString(); + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "csssscsssscssssccscsscsccssc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + + // Instead of emitting this as a span-based concat of strings and chars we recognize "constantChar.ToString()" pattern and lower that arguments to constant strings + verifier.VerifyIL("Test.M1", """ + { + // Code size 14 (0xe) + .maxstack 4 + IL_0000: ldstr "c" + IL_0005: ldarg.0 + IL_0006: ldarg.0 + IL_0007: ldarg.0 + IL_0008: call "string string.Concat(string, string, string, string)" + IL_000d: ret + } + """); + verifier.VerifyIL("Test.M2", """ + { + // Code size 14 (0xe) + .maxstack 4 + IL_0000: ldarg.0 + IL_0001: ldstr "c" + IL_0006: ldarg.0 + IL_0007: ldarg.0 + IL_0008: call "string string.Concat(string, string, string, string)" + IL_000d: ret + } + """); + verifier.VerifyIL("Test.M3", """ + { + // Code size 14 (0xe) + .maxstack 4 + IL_0000: ldarg.0 + IL_0001: ldarg.0 + IL_0002: ldstr "c" + IL_0007: ldarg.0 + IL_0008: call "string string.Concat(string, string, string, string)" + IL_000d: ret + } + """); + verifier.VerifyIL("Test.M4", """ + { + // Code size 14 (0xe) + .maxstack 4 + IL_0000: ldarg.0 + IL_0001: ldarg.0 + IL_0002: ldarg.0 + IL_0003: ldstr "c" + IL_0008: call "string string.Concat(string, string, string, string)" + IL_000d: ret + } + """); + verifier.VerifyIL("Test.M5", """ + { + // Code size 18 (0x12) + .maxstack 4 + IL_0000: ldstr "c" + IL_0005: ldarg.0 + IL_0006: ldstr "c" + IL_000b: ldarg.0 + IL_000c: call "string string.Concat(string, string, string, string)" + IL_0011: ret + } + """); + verifier.VerifyIL("Test.M6", """ + { + // Code size 18 (0x12) + .maxstack 4 + IL_0000: ldarg.0 + IL_0001: ldstr "c" + IL_0006: ldarg.0 + IL_0007: ldstr "c" + IL_000c: call "string string.Concat(string, string, string, string)" + IL_0011: ret + } + """); + verifier.VerifyIL("Test.M7", """ + { + // Code size 18 (0x12) + .maxstack 4 + IL_0000: ldstr "c" + IL_0005: ldarg.0 + IL_0006: ldarg.0 + IL_0007: ldstr "c" + IL_000c: call "string string.Concat(string, string, string, string)" + IL_0011: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + public void ConcatFour_AllConstantCharToStrings(int? missingUnimportantWellKnownMember) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + Console.Write(M()); + } + + static string M() => 'a'.ToString() + 'b'.ToString() + 'c'.ToString() + 'd'.ToString(); + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abcd" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + + // Instead of emitting this as a span-based concat of 4 chars we recognize "constantChar.ToString()" pattern and lower all arguments to a constant string + // which we can then fold into a single constant string and avoid concatenation entirely + verifier.VerifyIL("Test.M", """ + { + // Code size 6 (0x6) + .maxstack 1 + IL_0000: ldstr "abcd" + IL_0005: ret + } + """); + } + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] From e1766d33936a89de5b6edb21e22ff9b273a24781 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Wed, 24 Jan 2024 22:40:40 +0300 Subject: [PATCH 12/71] Fix tests on .NET Framework --- .../CodeGenSpanBasedStringConcatTests.cs | 160 +++++++++--------- 1 file changed, 80 insertions(+), 80 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs index 070ec2673698d..5406feb84ea7e 100644 --- a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs @@ -306,9 +306,9 @@ .locals init (char V_0, //c [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] public void ConcatTwo_ConstantCharToString(int? missingUnimportantWellKnownMember) { var source = """ @@ -364,9 +364,9 @@ .maxstack 2 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] public void ConcatTwo_AllConstantCharToStrings(int? missingUnimportantWellKnownMember) { var source = """ @@ -408,7 +408,7 @@ .maxstack 1 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] public void ConcatTwoCharToStrings(int? missingUnimportantWellKnownMember) { var source = """ @@ -548,7 +548,7 @@ static void Main() [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] public void ConcatThree_ReadOnlySpan1(int? missingUnimportantWellKnownMember) { var source = """ @@ -658,7 +658,7 @@ .locals init (char V_0, [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] public void ConcatThree_ReadOnlySpan2(int? missingUnimportantWellKnownMember) { var source = """ @@ -818,7 +818,7 @@ .locals init (char V_0) [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] public void ConcatThree_ReadOnlySpan_SideEffect(int? missingUnimportantWellKnownMember) { var source = """ @@ -948,7 +948,7 @@ .locals init (char V_0, [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] public void ConcatThree_ReadOnlySpan_ReferenceToSameLocation(int? missingUnimportantWellKnownMember) { var source = """ @@ -1012,7 +1012,7 @@ .locals init (char V_0, [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] public void ConcatThree_ReadOnlySpan_MutateLocal(int? missingUnimportantWellKnownMember) { var source = """ @@ -1076,10 +1076,10 @@ .locals init (char V_0, //c [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] public void ConcatThree_ConstantCharToString(int? missingUnimportantWellKnownMember) { var source = """ @@ -1163,10 +1163,10 @@ .maxstack 3 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] public void ConcatThree_AllConstantCharToStrings(int? missingUnimportantWellKnownMember) { var source = """ @@ -1208,8 +1208,8 @@ .maxstack 1 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] public void ConcatThreeCharToStrings(int? missingUnimportantWellKnownMember) { var source = """ @@ -1351,7 +1351,7 @@ .maxstack 3 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] public void ConcatThree_UserInputOfSpanBasedConcat_ConcatWithString(int? missingUnimportantWellKnownMember) { var source = """ @@ -1413,7 +1413,7 @@ .maxstack 3 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] public void ConcatThree_UserInputOfSpanBasedConcat_ConcatWithChar(int? missingUnimportantWellKnownMember) { var source = """ @@ -1647,8 +1647,8 @@ static void Main() [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] public void ConcatFour_ReadOnlySpan1(int? missingUnimportantWellKnownMember) { var source = """ @@ -1835,8 +1835,8 @@ .locals init (char V_0, [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] public void ConcatFour_ReadOnlySpan2(int? missingUnimportantWellKnownMember) { var source = """ @@ -2088,8 +2088,8 @@ .locals init (char V_0) [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] public void ConcatFour_ReadOnlySpan_SideEffect(int? missingUnimportantWellKnownMember) { var source = """ @@ -2299,8 +2299,8 @@ .locals init (char V_0, [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] public void ConcatFour_ReadOnlySpan_ReferenceToSameLocation(int? missingUnimportantWellKnownMember) { var source = """ @@ -2373,8 +2373,8 @@ .locals init (char V_0, [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] public void ConcatFour_ReadOnlySpan_MutateLocal(int? missingUnimportantWellKnownMember) { var source = """ @@ -2446,11 +2446,11 @@ .locals init (char V_0, //c1 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] public void ConcatFour_ConstantCharToString(int? missingUnimportantWellKnownMember) { var source = """ @@ -2580,11 +2580,11 @@ .maxstack 4 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] public void ConcatFour_AllConstantCharToStrings(int? missingUnimportantWellKnownMember) { var source = """ @@ -2626,9 +2626,9 @@ .maxstack 1 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] public void ConcatFourCharToStrings(int? missingUnimportantWellKnownMember) { var source = """ @@ -2827,8 +2827,8 @@ .maxstack 4 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] public void ConcatFour_UserInputOfSpanBasedConcatOf2_ConcatWithString(int? missingUnimportantWellKnownMember) { var source = """ @@ -2911,8 +2911,8 @@ .maxstack 4 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] public void ConcatFour_UserInputOfSpanBasedConcatOf2_ConcatWithChar(int? missingUnimportantWellKnownMember) { var source = """ @@ -3013,7 +3013,7 @@ .locals init (char V_0, [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] public void ConcatFour_UserInputOfSpanBasedConcatOf2_ConcatWithStringAndChar(int? missingUnimportantWellKnownMember) { var source = """ @@ -3166,9 +3166,9 @@ .locals init (char V_0) [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] public void ConcatFour_TwoUserInputsOfSpanBasedConcatOf2(int? missingUnimportantWellKnownMember) { var source = """ @@ -3215,7 +3215,7 @@ .maxstack 4 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] public void ConcatFour_UserInputOfSpanBasedConcatOf3_ConcatWithString(int? missingUnimportantWellKnownMember) { var source = """ @@ -3280,7 +3280,7 @@ .maxstack 4 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] public void ConcatFour_UserInputOfSpanBasedConcatOf3_ConcatWithChar(int? missingUnimportantWellKnownMember) { var source = """ @@ -3888,11 +3888,11 @@ static void Main() [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] public void ConcatFive_Char(int? missingUnimportantWellKnownMember) { var source = """ @@ -4077,11 +4077,11 @@ .maxstack 4 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] public void ConcatFiveCharToStrings(int? missingUnimportantWellKnownMember) { var source = """ @@ -4152,10 +4152,10 @@ .maxstack 4 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] public void ConcatTotalOfFive_UserInputOfSpanBasedConcatOf2(int? missingUnimportantWellKnownMember) { var source = """ @@ -4268,10 +4268,10 @@ .maxstack 4 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] public void ConcatTotalOfFive_UserInputOfSpanBasedConcatOf3(int? missingUnimportantWellKnownMember) { var source = """ @@ -4365,9 +4365,9 @@ .maxstack 5 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] public void ConcatTotalOfFive_UserInputOfMixedSpanBasedConcatsOf2And3(int? missingUnimportantWellKnownMember) { var source = """ @@ -4444,8 +4444,8 @@ .maxstack 3 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] public void ConcatTotalOfMoreThanFive_UserInputOf2SpanBasedConcatsOf2InARow(int? missingUnimportantWellKnownMember) { var source = """ From e4e26f1e80639d3894cbd0405ded29a61ac0f4e9 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Wed, 24 Jan 2024 23:22:19 +0300 Subject: [PATCH 13/71] Move assert to avoid nullability warning --- .../Lowering/LocalRewriter/LocalRewriter_StringConcat.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index 22ca497a15cf4..1e830db706f24 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -690,8 +690,6 @@ private BoundExpression ConvertConcatExprToString(SyntaxNode syntax, BoundExpres } } - Debug.Assert(expr.Type is { }); - // Is the expression a constant char? If so, we can // simply make it a literal string instead and avoid any // allocations for converting the char to a string at run time. @@ -721,6 +719,8 @@ private BoundExpression ConvertConcatExprToString(SyntaxNode syntax, BoundExpres _factory.Syntax = oldSyntax; } + Debug.Assert(expr.Type is not null); + // If it's a string already, just return it if (expr.Type.IsStringType()) { From 2c4bf14631640b23445a4d286681dfb8f675912b Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Mon, 29 Jan 2024 15:04:12 +0300 Subject: [PATCH 14/71] Rename --- .../LocalRewriter_StringConcat.cs | 18 +-- .../CodeGenSpanBasedStringConcatTests.cs | 118 +++++++++--------- .../Symbol/Symbols/MissingSpecialMember.cs | 6 +- .../Core/Portable/WellKnownMember.cs | 6 +- .../Core/Portable/WellKnownMembers.cs | 12 +- .../WellKnownTypeValidationTests.vb | 12 +- 6 files changed, 86 insertions(+), 86 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index 1e830db706f24..6cbe40d470d96 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -194,9 +194,9 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr } } - if ((object)method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan) || - (object)method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan) || - (object)method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)) + if ((object)method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_2ReadOnlySpans) || + (object)method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_3ReadOnlySpans) || + (object)method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_4ReadOnlySpans)) { // Faced a span-based string.Concat call. Since we can produce such call on the previous iterations ourselves, we need to unwrap it. // The key thing is that we need not to only extract arguments, but also unwrap them from being spans and for chars also wrap them into `ToString` calls. @@ -489,9 +489,9 @@ private bool TryRewriteStringConcatenationWithSpanBasedConcat(SyntaxNode syntax, preparedArgs.Add(arg); if (arg is BoundCall spanConcatCall && - ((object)spanConcatCall.Method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan) || - (object)spanConcatCall.Method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan) || - (object)spanConcatCall.Method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan))) + ((object)spanConcatCall.Method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_2ReadOnlySpans) || + (object)spanConcatCall.Method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_3ReadOnlySpans) || + (object)spanConcatCall.Method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_4ReadOnlySpans))) { preparedArgsIfUnwrapUserStringConcat.AddRange(spanConcatCall.Arguments); continue; @@ -651,9 +651,9 @@ static BoundExpression rewriteStringConcatenationWithSpanBasedConcat( private static WellKnownMember? GetSpanConcatMemberByArgumentsCount(int argumentCount) => argumentCount switch { - 2 => WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan, - 3 => WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan, - 4 => WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan, + 2 => WellKnownMember.System_String__Concat_2ReadOnlySpans, + 3 => WellKnownMember.System_String__Concat_3ReadOnlySpans, + 4 => WellKnownMember.System_String__Concat_4ReadOnlySpans, _ => null, }; diff --git a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs index 5406feb84ea7e..0106206659751 100644 --- a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs @@ -308,7 +308,7 @@ .locals init (char V_0, //c [InlineData(null)] [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] public void ConcatTwo_ConstantCharToString(int? missingUnimportantWellKnownMember) { var source = """ @@ -366,7 +366,7 @@ .maxstack 2 [InlineData(null)] [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] public void ConcatTwo_AllConstantCharToStrings(int? missingUnimportantWellKnownMember) { var source = """ @@ -458,7 +458,7 @@ .locals init (char V_0, } [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] public void ConcatTwo_ReadOnlySpan_MissingMemberForOptimization(int member) @@ -548,7 +548,7 @@ static void Main() [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] public void ConcatThree_ReadOnlySpan1(int? missingUnimportantWellKnownMember) { var source = """ @@ -658,7 +658,7 @@ .locals init (char V_0, [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] public void ConcatThree_ReadOnlySpan2(int? missingUnimportantWellKnownMember) { var source = """ @@ -818,7 +818,7 @@ .locals init (char V_0) [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] public void ConcatThree_ReadOnlySpan_SideEffect(int? missingUnimportantWellKnownMember) { var source = """ @@ -948,7 +948,7 @@ .locals init (char V_0, [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] public void ConcatThree_ReadOnlySpan_ReferenceToSameLocation(int? missingUnimportantWellKnownMember) { var source = """ @@ -1012,7 +1012,7 @@ .locals init (char V_0, [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] public void ConcatThree_ReadOnlySpan_MutateLocal(int? missingUnimportantWellKnownMember) { var source = """ @@ -1078,8 +1078,8 @@ .locals init (char V_0, //c [InlineData(null)] [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] + [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] public void ConcatThree_ConstantCharToString(int? missingUnimportantWellKnownMember) { var source = """ @@ -1165,8 +1165,8 @@ .maxstack 3 [InlineData(null)] [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] + [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] public void ConcatThree_AllConstantCharToStrings(int? missingUnimportantWellKnownMember) { var source = """ @@ -1209,7 +1209,7 @@ .maxstack 1 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] public void ConcatThreeCharToStrings(int? missingUnimportantWellKnownMember) { var source = """ @@ -1265,7 +1265,7 @@ .locals init (char V_0, } [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] public void ConcatThree_ReadOnlySpan_MissingMemberForOptimization(int member) @@ -1481,7 +1481,7 @@ .locals init (char V_0) [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] public void ConcatThree_UserInputOfSpanBasedConcat_ConcatWithString_MissingMemberToMergeIntoSingleConcat(int member) { var source = """ @@ -1539,7 +1539,7 @@ .maxstack 3 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] public void ConcatThree_UserInputOfSpanBasedConcat_ConcatWithChar_MissingMemberToMergeIntoSingleConcat(int member) { var source = """ @@ -1647,8 +1647,8 @@ static void Main() [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] + [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] public void ConcatFour_ReadOnlySpan1(int? missingUnimportantWellKnownMember) { var source = """ @@ -1835,8 +1835,8 @@ .locals init (char V_0, [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] + [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] public void ConcatFour_ReadOnlySpan2(int? missingUnimportantWellKnownMember) { var source = """ @@ -2088,8 +2088,8 @@ .locals init (char V_0) [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] + [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] public void ConcatFour_ReadOnlySpan_SideEffect(int? missingUnimportantWellKnownMember) { var source = """ @@ -2299,8 +2299,8 @@ .locals init (char V_0, [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] + [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] public void ConcatFour_ReadOnlySpan_ReferenceToSameLocation(int? missingUnimportantWellKnownMember) { var source = """ @@ -2373,8 +2373,8 @@ .locals init (char V_0, [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] + [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] public void ConcatFour_ReadOnlySpan_MutateLocal(int? missingUnimportantWellKnownMember) { var source = """ @@ -2448,9 +2448,9 @@ .locals init (char V_0, //c1 [InlineData(null)] [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] + [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] + [InlineData((int)WellKnownMember.System_String__Concat_4ReadOnlySpans)] public void ConcatFour_ConstantCharToString(int? missingUnimportantWellKnownMember) { var source = """ @@ -2582,9 +2582,9 @@ .maxstack 4 [InlineData(null)] [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] + [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] + [InlineData((int)WellKnownMember.System_String__Concat_4ReadOnlySpans)] public void ConcatFour_AllConstantCharToStrings(int? missingUnimportantWellKnownMember) { var source = """ @@ -2627,8 +2627,8 @@ .maxstack 1 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] + [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] public void ConcatFourCharToStrings(int? missingUnimportantWellKnownMember) { var source = """ @@ -2690,7 +2690,7 @@ .locals init (char V_0, } [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_4ReadOnlySpans)] [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] public void ConcatFour_ReadOnlySpan_MissingMemberForOptimization(int member) @@ -2828,7 +2828,7 @@ .maxstack 4 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] public void ConcatFour_UserInputOfSpanBasedConcatOf2_ConcatWithString(int? missingUnimportantWellKnownMember) { var source = """ @@ -2912,7 +2912,7 @@ .maxstack 4 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] public void ConcatFour_UserInputOfSpanBasedConcatOf2_ConcatWithChar(int? missingUnimportantWellKnownMember) { var source = """ @@ -3013,7 +3013,7 @@ .locals init (char V_0, [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] public void ConcatFour_UserInputOfSpanBasedConcatOf2_ConcatWithStringAndChar(int? missingUnimportantWellKnownMember) { var source = """ @@ -3168,7 +3168,7 @@ .locals init (char V_0) [InlineData(null)] [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] public void ConcatFour_TwoUserInputsOfSpanBasedConcatOf2(int? missingUnimportantWellKnownMember) { var source = """ @@ -3351,7 +3351,7 @@ .locals init (char V_0) [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan, (int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans, (int)WellKnownMember.System_String__Concat_4ReadOnlySpans)] public void ConcatFour_UserInputOfSpanBasedConcatOf2_ConcatWithString_MissingMembersToMergeIntoSingleConcat(params int[] members) { var source = """ @@ -3430,7 +3430,7 @@ .maxstack 3 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan, (int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans, (int)WellKnownMember.System_String__Concat_4ReadOnlySpans)] public void ConcatFour_UserInputOfSpanBasedConcatOf2_ConcatWithChar_MissingMembersToMergeIntoSingleConcat(params int[] members) { var source = """ @@ -3515,9 +3515,9 @@ .maxstack 3 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference, (int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan, (int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan, (int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan, (int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans, (int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans, (int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans, (int)WellKnownMember.System_String__Concat_4ReadOnlySpans)] public void ConcatFour_UserInputOfSpanBasedConcatOf2_ConcatWithStringAndChar_MissingMembersToMergeIntoSingleConcat(params int[] members) { var source = """ @@ -3650,7 +3650,7 @@ .maxstack 3 } [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_4ReadOnlySpans)] public void ConcatFour_TwoUserInputsOfSpanBasedConcatOf2_MissingMemberToMergeIntoSingleConcat(int member) { var source = """ @@ -3695,7 +3695,7 @@ .maxstack 3 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_4ReadOnlySpans)] public void ConcatFour_UserInputOfSpanBasedConcatOf3_ConcatWithString_MissingMemberToMergeIntoSingleConcat(int member) { var source = """ @@ -3756,7 +3756,7 @@ .maxstack 4 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_4ReadOnlySpans)] public void ConcatFour_UserInputOfSpanBasedConcatOf3_ConcatWithChar_MissingMemberToMergeIntoSingleConcat(int member) { var source = """ @@ -3890,9 +3890,9 @@ static void Main() [InlineData(null)] [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] + [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] + [InlineData((int)WellKnownMember.System_String__Concat_4ReadOnlySpans)] public void ConcatFive_Char(int? missingUnimportantWellKnownMember) { var source = """ @@ -4079,9 +4079,9 @@ .maxstack 4 [InlineData(null)] [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] + [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] + [InlineData((int)WellKnownMember.System_String__Concat_4ReadOnlySpans)] public void ConcatFiveCharToStrings(int? missingUnimportantWellKnownMember) { var source = """ @@ -4154,8 +4154,8 @@ .maxstack 4 [InlineData(null)] [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] + [InlineData((int)WellKnownMember.System_String__Concat_4ReadOnlySpans)] public void ConcatTotalOfFive_UserInputOfSpanBasedConcatOf2(int? missingUnimportantWellKnownMember) { var source = """ @@ -4270,8 +4270,8 @@ .maxstack 4 [InlineData(null)] [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] + [InlineData((int)WellKnownMember.System_String__Concat_4ReadOnlySpans)] public void ConcatTotalOfFive_UserInputOfSpanBasedConcatOf3(int? missingUnimportantWellKnownMember) { var source = """ @@ -4367,7 +4367,7 @@ .maxstack 5 [InlineData(null)] [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_4ReadOnlySpans)] public void ConcatTotalOfFive_UserInputOfMixedSpanBasedConcatsOf2And3(int? missingUnimportantWellKnownMember) { var source = """ @@ -4550,7 +4550,7 @@ static void Main() """; var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - comp.MakeMemberMissing(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan); + comp.MakeMemberMissing(WellKnownMember.System_String__Concat_4ReadOnlySpans); var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "ssssssssssssss" : null, verify: ExecutionConditionUtil.IsCoreClr ? default : Verification.Skipped); diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs index 56001ba2a9581..6f1c75d6bcbd4 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs @@ -963,9 +963,9 @@ public void AllWellKnownTypeMembers() case WellKnownMember.System_ReadOnlySpan_T__get_Length: case WellKnownMember.System_ReadOnlySpan_T__Slice_Int_Int: case WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar: - case WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan: - case WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan: - case WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan: + case WellKnownMember.System_String__Concat_2ReadOnlySpans: + case WellKnownMember.System_String__Concat_3ReadOnlySpans: + case WellKnownMember.System_String__Concat_4ReadOnlySpans: case WellKnownMember.System_Index__ctor: case WellKnownMember.System_Index__GetOffset: case WellKnownMember.System_Range__ctor: diff --git a/src/Compilers/Core/Portable/WellKnownMember.cs b/src/Compilers/Core/Portable/WellKnownMember.cs index cbc72af382ad3..b4afdcd32ec9c 100644 --- a/src/Compilers/Core/Portable/WellKnownMember.cs +++ b/src/Compilers/Core/Portable/WellKnownMember.cs @@ -433,9 +433,9 @@ internal enum WellKnownMember System_String__Format_IFormatProvider, System_String__op_Implicit_ToReadOnlySpanOfChar, - System_String__Concat_ReadOnlySpanReadOnlySpan, - System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan, - System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan, + System_String__Concat_2ReadOnlySpans, + System_String__Concat_3ReadOnlySpans, + System_String__Concat_4ReadOnlySpans, Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningSingleFile, Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningMultipleFiles, diff --git a/src/Compilers/Core/Portable/WellKnownMembers.cs b/src/Compilers/Core/Portable/WellKnownMembers.cs index 6b352d1b83884..1047fdf76a5d6 100644 --- a/src/Compilers/Core/Portable/WellKnownMembers.cs +++ b/src/Compilers/Core/Portable/WellKnownMembers.cs @@ -2990,7 +2990,7 @@ static WellKnownMembers() (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, - // System_String__Concat_ReadOnlySpanReadOnlySpan + // System_String__Concat_2ReadOnlySpans (byte)(MemberFlags.Method | MemberFlags.Static), // Flags (byte)SpecialType.System_String, // DeclaringTypeId 0, // Arity @@ -3005,7 +3005,7 @@ static WellKnownMembers() 1, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, - // System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan + // System_String__Concat_3ReadOnlySpans (byte)(MemberFlags.Method | MemberFlags.Static), // Flags (byte)SpecialType.System_String, // DeclaringTypeId 0, // Arity @@ -3024,7 +3024,7 @@ static WellKnownMembers() 1, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, - // System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan + // System_String__Concat_4ReadOnlySpans (byte)(MemberFlags.Method | MemberFlags.Static), // Flags (byte)SpecialType.System_String, // DeclaringTypeId 0, // Arity @@ -4890,9 +4890,9 @@ static WellKnownMembers() "Format", // System_String__Format_IFormatProvider "op_Implicit", // System_String__op_Implicit_ToReadOnlySpanOfChar - "Concat", // System_String__Concat_ReadOnlySpanReadOnlySpan - "Concat", // System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan - "Concat", // System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan + "Concat", // System_String__Concat_2ReadOnlySpans + "Concat", // System_String__Concat_3ReadOnlySpans + "Concat", // System_String__Concat_4ReadOnlySpans "CreatePayload", // Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningSingleFile "CreatePayload", // Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningMultipleFiles diff --git a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb index 3f56a5c215bbd..bcdf8da06faa1 100644 --- a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb +++ b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb @@ -707,9 +707,9 @@ End Namespace WellKnownMember.System_ReadOnlySpan_T__get_Length, WellKnownMember.System_ReadOnlySpan_T__Slice_Int_Int, WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar, - WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan, - WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan, - WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan, + WellKnownMember.System_String__Concat_2ReadOnlySpans, + WellKnownMember.System_String__Concat_3ReadOnlySpans, + WellKnownMember.System_String__Concat_4ReadOnlySpans, WellKnownMember.System_Runtime_CompilerServices_AsyncIteratorStateMachineAttribute__ctor, WellKnownMember.System_IAsyncDisposable__DisposeAsync, WellKnownMember.System_Collections_Generic_IAsyncEnumerable_T__GetAsyncEnumerator, @@ -917,9 +917,9 @@ End Namespace WellKnownMember.System_ReadOnlySpan_T__get_Length, WellKnownMember.System_ReadOnlySpan_T__Slice_Int_Int, WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar, - WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan, - WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan, - WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan, + WellKnownMember.System_String__Concat_2ReadOnlySpans, + WellKnownMember.System_String__Concat_3ReadOnlySpans, + WellKnownMember.System_String__Concat_4ReadOnlySpans, WellKnownMember.System_Runtime_CompilerServices_AsyncIteratorStateMachineAttribute__ctor, WellKnownMember.System_IAsyncDisposable__DisposeAsync, WellKnownMember.System_Collections_Generic_IAsyncEnumerable_T__GetAsyncEnumerator, From 70656d51018ab1b3ab298768026d5ef93e099596 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Tue, 30 Jan 2024 21:39:24 +0300 Subject: [PATCH 15/71] Use array builder --- .../LocalRewriter/LocalRewriter_StringConcat.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index 6cbe40d470d96..74f3436ff3aa8 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -98,7 +98,7 @@ private BoundExpression RewriteStringConcatenation(SyntaxNode syntax, BinaryOper var left = leftFlattened[0]; var right = leftFlattened[1]; - if (!TryRewriteStringConcatenationWithSpanBasedConcat(syntax, leftFlattened.ToImmutable(), out result)) + if (!TryRewriteStringConcatenationWithSpanBasedConcat(syntax, leftFlattened, out result)) { result = RewriteStringConcatenationTwoExprs(syntax, left, right); } @@ -110,7 +110,7 @@ private BoundExpression RewriteStringConcatenation(SyntaxNode syntax, BinaryOper var second = leftFlattened[1]; var third = leftFlattened[2]; - if (!TryRewriteStringConcatenationWithSpanBasedConcat(syntax, leftFlattened.ToImmutable(), out result)) + if (!TryRewriteStringConcatenationWithSpanBasedConcat(syntax, leftFlattened, out result)) { result = RewriteStringConcatenationThreeExprs(syntax, first, second, third); } @@ -124,7 +124,7 @@ private BoundExpression RewriteStringConcatenation(SyntaxNode syntax, BinaryOper var third = leftFlattened[2]; var fourth = leftFlattened[3]; - if (!TryRewriteStringConcatenationWithSpanBasedConcat(syntax, leftFlattened.ToImmutable(), out result)) + if (!TryRewriteStringConcatenationWithSpanBasedConcat(syntax, leftFlattened, out result)) { result = RewriteStringConcatenationFourExprs(syntax, first, second, third, fourth); } @@ -453,7 +453,7 @@ private BoundExpression RewriteStringConcatenationManyExprs(SyntaxNode syntax, I return BoundCall.Synthesized(syntax, receiverOpt: null, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, method, array); } - private bool TryRewriteStringConcatenationWithSpanBasedConcat(SyntaxNode syntax, ImmutableArray args, [NotNullWhen(true)] out BoundExpression? result) + private bool TryRewriteStringConcatenationWithSpanBasedConcat(SyntaxNode syntax, ArrayBuilder args, [NotNullWhen(true)] out BoundExpression? result) { // As we scan arguments we might face span-based string.Concat. If it got to this stage it must be a user-provided one (since we weren't able to unwrap it back to strings previously) // We try our best to merge it with another argument to minimize amount of intermediate string allocations, @@ -461,8 +461,8 @@ private bool TryRewriteStringConcatenationWithSpanBasedConcat(SyntaxNode syntax, // instead of `string.Concat(string.Concat(span1, span2), string3)`. We do that by separately tracking arguments as is // and arguments if we unwrap all user-defined `string.Concat`s. If at the end amount of arguments with unwrapped user-defined `string.Concat`s // is < 5 and we have respective span-based concat member, we emit it, otherwise treat user-provided `string.Concat`s as ordinary arguments - var preparedArgs = ArrayBuilder.GetInstance(capacity: args.Length); - var preparedArgsIfUnwrapUserStringConcat = ArrayBuilder.GetInstance(capacity: args.Length); + var preparedArgs = ArrayBuilder.GetInstance(capacity: args.Count); + var preparedArgsIfUnwrapUserStringConcat = ArrayBuilder.GetInstance(capacity: args.Count); var needsSpanRefParamConstructor = false; var needsImplicitConversionFromStringToSpan = false; From da90a6ba19cb8c56cd0f94d3c2701c900c2c0d70 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Tue, 30 Jan 2024 21:39:47 +0300 Subject: [PATCH 16/71] Use block body --- .../LocalRewriter/LocalRewriter_StringConcat.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index 74f3436ff3aa8..ae7576eb67557 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -649,13 +649,16 @@ static BoundExpression rewriteStringConcatenationWithSpanBasedConcat( } } - private static WellKnownMember? GetSpanConcatMemberByArgumentsCount(int argumentCount) => argumentCount switch + private static WellKnownMember? GetSpanConcatMemberByArgumentsCount(int argumentCount) { - 2 => WellKnownMember.System_String__Concat_2ReadOnlySpans, - 3 => WellKnownMember.System_String__Concat_3ReadOnlySpans, - 4 => WellKnownMember.System_String__Concat_4ReadOnlySpans, - _ => null, - }; + return argumentCount switch + { + 2 => WellKnownMember.System_String__Concat_2ReadOnlySpans, + 3 => WellKnownMember.System_String__Concat_3ReadOnlySpans, + 4 => WellKnownMember.System_String__Concat_4ReadOnlySpans, + _ => null, + }; + } /// /// Most of the above optimizations are not applicable in expression trees as the operator From f5f7efec992741baf25012122420033a21044754 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Tue, 30 Jan 2024 21:41:15 +0300 Subject: [PATCH 17/71] Revert naming --- .../Lowering/LocalRewriter/LocalRewriter_StringConcat.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index ae7576eb67557..3de7b815d8ddf 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -824,12 +824,12 @@ static bool isFieldOfMarshalByRef(BoundExpression expr, CSharpCompilation compil { if (exprType.IsValueType && !exprType.IsTypeParameter()) { - var namedType = (NamedTypeSymbol)exprType; - var typeToStringMembers = namedType.GetMembers(objectToStringMethod.Name); + var type = (NamedTypeSymbol)exprType; + var typeToStringMembers = type.GetMembers(objectToStringMethod.Name); foreach (var member in typeToStringMembers) { if (member is MethodSymbol toStringMethod && - toStringMethod.GetLeastOverriddenMethod(namedType) == (object)objectToStringMethod) + toStringMethod.GetLeastOverriddenMethod(type) == (object)objectToStringMethod) { return toStringMethod; } From e2ea475893ad8525dbad7fdbe952e9b0c3cf6935 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Tue, 30 Jan 2024 22:21:59 +0300 Subject: [PATCH 18/71] Feedback --- .../LocalRewriter_StringConcat.cs | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index 3de7b815d8ddf..4b3af80688dd6 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -693,6 +693,14 @@ private BoundExpression ConvertConcatExprToString(SyntaxNode syntax, BoundExpres } } + // If expression is of form `constantChar.ToString()` then unwrap it from a `ToString()` call so we can lower it to a constant later + // NOTE: We get `object.ToString()` from a compilation because we just need to compare symbols and don't need all error recovery of `TryGetSpecialTypeMethod` + if (expr is BoundCall { Type.SpecialType: SpecialType.System_String, Method: { Name: "ToString" } method, ReceiverOpt: { Type: NamedTypeSymbol { SpecialType: SpecialType.System_Char } charType, ConstantValueOpt.IsChar: true } } call && + method.GetLeastOverriddenMember(charType) == _compilation.GetSpecialTypeMember(SpecialMember.System_Object__ToString)) + { + expr = call.ReceiverOpt; + } + // Is the expression a constant char? If so, we can // simply make it a literal string instead and avoid any // allocations for converting the char to a string at run time. @@ -709,19 +717,6 @@ private BoundExpression ConvertConcatExprToString(SyntaxNode syntax, BoundExpres } } - // If expression is of form `constantChar.ToString()` then rewrite it here to a string literal so we can choose better options for lowering later (e.g. fold it with another constant instead of performing concatenation) - // NOTE: We request `object.ToString()` as an optional member here to avoid reporting "missing member" diagnostic earlier than we should. - // Because if in the end it turns out that we have a simple concatenation of n strings there is no need to report missing `object.ToString()` member - if (TryGetSpecialTypeMethod(expr.Syntax, SpecialMember.System_Object__ToString, out MethodSymbol? optionalObjectToString, isOptional: true) && - expr is BoundCall { ReceiverOpt: { Type: NamedTypeSymbol { SpecialType: SpecialType.System_Char } charType, ConstantValueOpt: { IsChar: true } charConstant } } call && - call.Method.GetLeastOverriddenMember(charType) == optionalObjectToString) - { - var oldSyntax = _factory.Syntax; - _factory.Syntax = expr.Syntax; - expr = _factory.Literal(charConstant.CharValue.ToString()); - _factory.Syntax = oldSyntax; - } - Debug.Assert(expr.Type is not null); // If it's a string already, just return it From 404d7fc26150cb616a75a7de556f85bd009bf02d Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Tue, 30 Jan 2024 23:33:40 +0300 Subject: [PATCH 19/71] Get member from compilation --- .../Portable/Lowering/LocalRewriter/LocalRewriter.cs | 8 ++++---- .../Lowering/LocalRewriter/LocalRewriter_StringConcat.cs | 5 +---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs index d00b23dfa9561..bcadd43302ad3 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs @@ -571,9 +571,9 @@ private bool TryGetWellKnownTypeMember(SyntaxNode? syntax, WellKnownMem /// Recommendation: Do not use, use instead! /// If used, a unit-test with a missing member is absolutely a must have. /// - private MethodSymbol UnsafeGetSpecialTypeMethod(SyntaxNode syntax, SpecialMember specialMember, bool isOptional = false) + private MethodSymbol UnsafeGetSpecialTypeMethod(SyntaxNode syntax, SpecialMember specialMember) { - return UnsafeGetSpecialTypeMethod(syntax, specialMember, _compilation, _diagnostics, isOptional); + return UnsafeGetSpecialTypeMethod(syntax, specialMember, _compilation, _diagnostics); } /// @@ -581,10 +581,10 @@ private MethodSymbol UnsafeGetSpecialTypeMethod(SyntaxNode syntax, SpecialMember /// Recommendation: Do not use, use instead! /// If used, a unit-test with a missing member is absolutely a must have. /// - private static MethodSymbol UnsafeGetSpecialTypeMethod(SyntaxNode syntax, SpecialMember specialMember, CSharpCompilation compilation, BindingDiagnosticBag diagnostics, bool isOptional = false) + private static MethodSymbol UnsafeGetSpecialTypeMethod(SyntaxNode syntax, SpecialMember specialMember, CSharpCompilation compilation, BindingDiagnosticBag diagnostics) { MethodSymbol method; - if (TryGetSpecialTypeMethod(syntax, specialMember, compilation, diagnostics, out method, isOptional)) + if (TryGetSpecialTypeMethod(syntax, specialMember, compilation, diagnostics, out method)) { return method; } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index 4b3af80688dd6..115b1554c0e26 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -467,9 +467,6 @@ private bool TryRewriteStringConcatenationWithSpanBasedConcat(SyntaxNode syntax, var needsSpanRefParamConstructor = false; var needsImplicitConversionFromStringToSpan = false; - // When we get here we either 100% already queried for object.ToString in `ConvertConcatExprToString` (if at least 1 operand is not a string) or don't need it (if all operands are strings). - // Thus we can pass `isOptional` flag to avoid duplicate or unintentional "missing member" diagnostic in case we are actually missing `object.ToString()` - var objectToStringMethod = UnsafeGetSpecialTypeMethod(syntax, SpecialMember.System_Object__ToString, isOptional: true); NamedTypeSymbol? charType = null; foreach (var arg in args) @@ -477,7 +474,7 @@ private bool TryRewriteStringConcatenationWithSpanBasedConcat(SyntaxNode syntax, Debug.Assert(arg.HasAnyErrors || arg.Type?.IsStringType() == true); if (arg is BoundCall { ReceiverOpt: { Type: NamedTypeSymbol { SpecialType: SpecialType.System_Char } receiverCharType } receiver } potentialToStringCall && - (object)potentialToStringCall.Method.GetLeastOverriddenMethod(charType) == objectToStringMethod) + (object)potentialToStringCall.Method.GetLeastOverriddenMethod(charType) == _compilation.GetSpecialTypeMember(SpecialMember.System_Object__ToString)) { needsSpanRefParamConstructor = true; charType = receiverCharType; From b6bf346f1980676896a88a64116dcf7d70a50ba4 Mon Sep 17 00:00:00 2001 From: DoctorKrolic <70431552+DoctorKrolic@users.noreply.github.com> Date: Wed, 31 Jan 2024 23:11:13 +0300 Subject: [PATCH 20/71] Improve error location of missing `object.ToString()` for concatenation (#71890) Extracted from https://github.com/dotnet/roslyn/pull/71793 (suggested in https://github.com/dotnet/roslyn/pull/71793#discussion_r1471633235) Currently if `object.ToString()` member is missing error is reported on the whole concatenation. It makes more sense to report in on the argument, which actually requested that member. E.g. consider concatenation `a + b + c` where `a` is a string and `b` and `c` are not. If `object.ToString()` is missing without a chage of this PR error locations will be for `b` - `a + b` and for `c` - the whole `a + b + c`. This PR chages it so that errors are reported directly on `b` and `c`. Added 2 tests to show this behavior for 3 and 4 concatenation arguments I know that this is a very minor change and I would not normally make it, but since such separation of changes was requested by a compiler team meber, here is it. The "fix" is just several charachters long. And regardless of whether this is accepted or not, a lot more tests of missing `object.ToString()` for concatenation are about to come in https://github.com/dotnet/roslyn/pull/71793 --- .../LocalRewriter_StringConcat.cs | 16 ++--- .../Symbol/Symbols/MissingSpecialMember.cs | 62 +++++++++++++++++++ 2 files changed, 71 insertions(+), 7 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index 115b1554c0e26..7362abac4618a 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -47,8 +47,8 @@ private BoundExpression RewriteStringConcatenation(SyntaxNode syntax, BinaryOper } // Convert both sides to a string (calling ToString if necessary) - loweredLeft = ConvertConcatExprToString(syntax, loweredLeft); - loweredRight = ConvertConcatExprToString(syntax, loweredRight); + loweredLeft = ConvertConcatExprToString(loweredLeft); + loweredRight = ConvertConcatExprToString(loweredRight); Debug.Assert(loweredLeft.Type is { } && (loweredLeft.Type.IsStringType() || loweredLeft.Type.IsErrorType()) || loweredLeft.ConstantValueOpt?.IsNull == true); Debug.Assert(loweredRight.Type is { } && (loweredRight.Type.IsStringType() || loweredRight.Type.IsErrorType()) || loweredRight.ConstantValueOpt?.IsNull == true); @@ -677,8 +677,10 @@ private BoundExpression RewriteStringConcatInExpressionLambda(SyntaxNode syntax, /// Returns an expression which converts the given expression into a string (or null). /// If necessary, this invokes .ToString() on the expression, to avoid boxing value types. /// - private BoundExpression ConvertConcatExprToString(SyntaxNode syntax, BoundExpression expr) + private BoundExpression ConvertConcatExprToString(BoundExpression expr) { + var syntax = expr.Syntax; + // If it's a value type, it'll have been boxed by the +(string, object) or +(object, string) // operator. Undo that. if (expr.Kind == BoundKind.Conversion) @@ -724,7 +726,7 @@ private BoundExpression ConvertConcatExprToString(SyntaxNode syntax, BoundExpres // Evaluate toString at the last possible moment, to avoid spurious diagnostics if it's missing. // All code paths below here use it. - var objectToStringMethod = UnsafeGetSpecialTypeMethod(expr.Syntax, SpecialMember.System_Object__ToString); + var objectToStringMethod = UnsafeGetSpecialTypeMethod(syntax, SpecialMember.System_Object__ToString); // If it's a struct which has overridden ToString, find that method. Note that we might fail to // find it, e.g. if object.ToString is missing @@ -736,7 +738,7 @@ private BoundExpression ConvertConcatExprToString(SyntaxNode syntax, BoundExpres // types to all special value types. if (structToStringMethod != null && (expr.Type.SpecialType != SpecialType.None && !isFieldOfMarshalByRef(expr, _compilation))) { - return BoundCall.Synthesized(expr.Syntax, expr, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, structToStringMethod); + return BoundCall.Synthesized(syntax, expr, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, structToStringMethod); } // - It's a reference type (excluding unconstrained generics): no copy @@ -759,9 +761,9 @@ private BoundExpression ConvertConcatExprToString(SyntaxNode syntax, BoundExpres { if (!callWithoutCopy) { - expr = new BoundPassByCopy(expr.Syntax, expr, expr.Type); + expr = new BoundPassByCopy(syntax, expr, expr.Type); } - return BoundCall.Synthesized(expr.Syntax, expr, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, objectToStringMethod); + return BoundCall.Synthesized(syntax, expr, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, objectToStringMethod); } if (callWithoutCopy) diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs index 6f1c75d6bcbd4..512b6ca6bd875 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs @@ -2412,6 +2412,68 @@ static void Main() ); } + [Fact] + public void System_Object__ToString_3Args() + { + var source = """ + using System; + + public class Test + { + static void Main() + { + char c = 'c'; + int i = 2; + Console.WriteLine(c + "3" + i); + } + } + """; + + var compilation = CreateCompilationWithMscorlib45(source); + compilation.MakeMemberMissing(SpecialMember.System_Object__ToString); + compilation.VerifyEmitDiagnostics( + // (9,27): error CS0656: Missing compiler required member 'System.Object.ToString' + // Console.WriteLine(c + "3" + i); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "c").WithArguments("System.Object", "ToString").WithLocation(9, 27), + // (9,37): error CS0656: Missing compiler required member 'System.Object.ToString' + // Console.WriteLine(c + "3" + i); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "i").WithArguments("System.Object", "ToString").WithLocation(9, 37) + ); + } + + [Fact] + public void System_Object__ToString_4Args() + { + var source = """ + using System; + + public class Test + { + static void Main() + { + char c = 'c'; + int i = 2; + double d = 0.7; + Console.WriteLine(c + "3" + i + d); + } + } + """; + + var compilation = CreateCompilationWithMscorlib45(source); + compilation.MakeMemberMissing(SpecialMember.System_Object__ToString); + compilation.VerifyEmitDiagnostics( + // (10,27): error CS0656: Missing compiler required member 'System.Object.ToString' + // Console.WriteLine(c + "3" + i + d); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "c").WithArguments("System.Object", "ToString").WithLocation(10, 27), + // (10,37): error CS0656: Missing compiler required member 'System.Object.ToString' + // Console.WriteLine(c + "3" + i + d); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "i").WithArguments("System.Object", "ToString").WithLocation(10, 37), + // (10,41): error CS0656: Missing compiler required member 'System.Object.ToString' + // Console.WriteLine(c + "3" + i + d); + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "d").WithArguments("System.Object", "ToString").WithLocation(10, 41) + ); + } + [Fact] public void System_String__ConcatStringString() { From 083c3802b8ebcde60a848d75fb19acbde4d75871 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sat, 3 Feb 2024 22:21:29 +0300 Subject: [PATCH 21/71] Don't bother about user-made concat calls and leave them as is --- .../LocalRewriter_StringConcat.cs | 126 +- .../CodeGenSpanBasedStringConcatTests.cs | 1176 ++++------------- 2 files changed, 243 insertions(+), 1059 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index 7362abac4618a..740219dd44ae4 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -198,31 +198,15 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr (object)method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_3ReadOnlySpans) || (object)method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_4ReadOnlySpans)) { - // Faced a span-based string.Concat call. Since we can produce such call on the previous iterations ourselves, we need to unwrap it. + // Faced a span-based `string.Concat` call. Since we can produce such call on the previous iterations ourselves, we need to unwrap it. // The key thing is that we need not to only extract arguments, but also unwrap them from being spans and for chars also wrap them into `ToString` calls. - // Another challenge is that we may have merged user-written span-based string.Concat with additional argument previously and we need to undo this change as well - // so that if at some point we exceed 3 or 4 span arguments we can undo all span-concat changes and use string.Concat(string[]) overload with the same arguments as the user provided. - // We do that by tracking which argument nodes have `WasCompilerGenerated` flag. If If a consecutive span of nodes is not compiler-generated, they are arguments from original user-written span.Concat call var wrappedArgs = boundCall.Arguments; var unwrappedArgsBuilder = ArrayBuilder.GetInstance(capacity: wrappedArgs.Length); - var previousConcatArgsBuilder = ArrayBuilder.GetInstance(); - var previousConcatIndex = -1; - for (var i = 0; i < wrappedArgs.Length; i++) { var wrappedArg = wrappedArgs[i]; - if (!wrappedArg.WasCompilerGenerated) - { - previousConcatArgsBuilder.Add(wrappedArg); - if (previousConcatIndex == -1) - { - previousConcatIndex = i; - } - continue; - } - // Check whether a call is an implicit `string -> ReadOnlySpan` conversion if (wrappedArg is BoundCall { Method: var argMethod, Arguments: [var singleArgument] } && (object)argMethod == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)) @@ -240,7 +224,6 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr if (charToString is null) { unwrappedArgsBuilder.Free(); - previousConcatArgsBuilder.Free(); arguments = default; return false; } @@ -249,42 +232,12 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr else { unwrappedArgsBuilder.Free(); - previousConcatArgsBuilder.Free(); - arguments = default; - return false; - } - } - - if (previousConcatArgsBuilder.Count == boundCall.Arguments.Length) - { - unwrappedArgsBuilder.Free(); - previousConcatArgsBuilder.Free(); - arguments = default; - return false; - } - - var previousConcatMember = GetSpanConcatMemberByArgumentsCount(previousConcatArgsBuilder.Count); - - if (previousConcatMember.HasValue) - { - Debug.Assert(previousConcatIndex > -1); - - if (TryGetWellKnownTypeMember(lowered.Syntax, previousConcatMember.Value, out MethodSymbol? previousConcatMethod, isOptional: true)) - { - var reconstructedPreviousConcat = BoundCall.Synthesized(lowered.Syntax, receiverOpt: null, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, previousConcatMethod, previousConcatArgsBuilder.ToImmutable()); - unwrappedArgsBuilder.Insert(previousConcatIndex, reconstructedPreviousConcat); - } - else - { - unwrappedArgsBuilder.Free(); - previousConcatArgsBuilder.Free(); arguments = default; return false; } } arguments = unwrappedArgsBuilder.ToImmutableAndFree(); - previousConcatArgsBuilder.Free(); return true; } } @@ -455,14 +408,7 @@ private BoundExpression RewriteStringConcatenationManyExprs(SyntaxNode syntax, I private bool TryRewriteStringConcatenationWithSpanBasedConcat(SyntaxNode syntax, ArrayBuilder args, [NotNullWhen(true)] out BoundExpression? result) { - // As we scan arguments we might face span-based string.Concat. If it got to this stage it must be a user-provided one (since we weren't able to unwrap it back to strings previously) - // We try our best to merge it with another argument to minimize amount of intermediate string allocations, - // e.g. turn `string.Concat(span1, span2) + string3` into `string.Concat(span1, span2, string3ConvertedToSpan)` - // instead of `string.Concat(string.Concat(span1, span2), string3)`. We do that by separately tracking arguments as is - // and arguments if we unwrap all user-defined `string.Concat`s. If at the end amount of arguments with unwrapped user-defined `string.Concat`s - // is < 5 and we have respective span-based concat member, we emit it, otherwise treat user-provided `string.Concat`s as ordinary arguments var preparedArgs = ArrayBuilder.GetInstance(capacity: args.Count); - var preparedArgsIfUnwrapUserStringConcat = ArrayBuilder.GetInstance(capacity: args.Count); var needsSpanRefParamConstructor = false; var needsImplicitConversionFromStringToSpan = false; @@ -479,60 +425,28 @@ private bool TryRewriteStringConcatenationWithSpanBasedConcat(SyntaxNode syntax, needsSpanRefParamConstructor = true; charType = receiverCharType; preparedArgs.Add(receiver); - preparedArgsIfUnwrapUserStringConcat.Add(receiver); continue; } preparedArgs.Add(arg); - - if (arg is BoundCall spanConcatCall && - ((object)spanConcatCall.Method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_2ReadOnlySpans) || - (object)spanConcatCall.Method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_3ReadOnlySpans) || - (object)spanConcatCall.Method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_4ReadOnlySpans))) - { - preparedArgsIfUnwrapUserStringConcat.AddRange(spanConcatCall.Arguments); - continue; - } - needsImplicitConversionFromStringToSpan = true; - preparedArgsIfUnwrapUserStringConcat.Add(arg); } - var concatMemberIfUnwrapUserSpanConcats = GetSpanConcatMemberByArgumentsCount(preparedArgsIfUnwrapUserStringConcat.Count); - - MethodSymbol? spanConcat; - MethodSymbol? readOnlySpanCtorRefParamChar; - MethodSymbol? stringImplicitConversionToReadOnlySpan; - - if (preparedArgsIfUnwrapUserStringConcat.Count > preparedArgs.Count && concatMemberIfUnwrapUserSpanConcats.HasValue) + WellKnownMember? concatMember = preparedArgs.Count switch { - if (TryGetWellKnownTypeMember(syntax, concatMemberIfUnwrapUserSpanConcats.Value, out spanConcat, isOptional: true) && - tryGetNeededToSpanMembers(this, syntax, needsSpanRefParamConstructor, needsImplicitConversionFromStringToSpan, charType, out readOnlySpanCtorRefParamChar, out stringImplicitConversionToReadOnlySpan)) - { - result = rewriteStringConcatenationWithSpanBasedConcat( - syntax, - _factory, - spanConcat, - stringImplicitConversionToReadOnlySpan, - readOnlySpanCtorRefParamChar, - preparedArgsIfUnwrapUserStringConcat.ToImmutableAndFree()); - - preparedArgs.Free(); - return true; - } - - needsImplicitConversionFromStringToSpan = true; - } - - var concatMember = GetSpanConcatMemberByArgumentsCount(preparedArgsIfUnwrapUserStringConcat.Count); + 2 => WellKnownMember.System_String__Concat_2ReadOnlySpans, + 3 => WellKnownMember.System_String__Concat_3ReadOnlySpans, + 4 => WellKnownMember.System_String__Concat_4ReadOnlySpans, + _ => null, + }; - // If we got here it only makes sense to lower using span-based concat if at least one operand is a char. - // Because otherwise we will just unnecessarily wrap every string operand into span conversion and use span-based concat + // It only makes sense to lower using span-based concat if at least one operand is a char. + // Because otherwise we will just wrap every string operand into span conversion and use span-based concat // which is unnecessary IL bloat. Thus we require `needsSpanRefParamConstructor` to be true if (needsSpanRefParamConstructor && concatMember.HasValue && - TryGetWellKnownTypeMember(syntax, concatMember.Value, out spanConcat, isOptional: true) && - tryGetNeededToSpanMembers(this, syntax, needsSpanRefParamConstructor, needsImplicitConversionFromStringToSpan, charType, out readOnlySpanCtorRefParamChar, out stringImplicitConversionToReadOnlySpan)) + TryGetWellKnownTypeMember(syntax, concatMember.Value, out MethodSymbol? spanConcat, isOptional: true) && + tryGetNeededToSpanMembers(this, syntax, needsSpanRefParamConstructor, needsImplicitConversionFromStringToSpan, charType, out MethodSymbol? readOnlySpanCtorRefParamChar, out MethodSymbol? stringImplicitConversionToReadOnlySpan)) { result = rewriteStringConcatenationWithSpanBasedConcat( syntax, @@ -542,7 +456,6 @@ private bool TryRewriteStringConcatenationWithSpanBasedConcat(SyntaxNode syntax, readOnlySpanCtorRefParamChar, preparedArgs.ToImmutableAndFree()); - preparedArgsIfUnwrapUserStringConcat.Free(); return true; } @@ -591,11 +504,7 @@ static BoundExpression rewriteStringConcatenationWithSpanBasedConcat( { Debug.Assert(arg.Type is not null); - if (arg.Type.IsReadOnlySpanChar()) - { - preparedArgsBuilder.Add(arg); - } - else if (arg.Type.SpecialType == SpecialType.System_Char) + if (arg.Type.SpecialType == SpecialType.System_Char) { Debug.Assert(readOnlySpanCtorRefParamChar is not null); @@ -646,17 +555,6 @@ static BoundExpression rewriteStringConcatenationWithSpanBasedConcat( } } - private static WellKnownMember? GetSpanConcatMemberByArgumentsCount(int argumentCount) - { - return argumentCount switch - { - 2 => WellKnownMember.System_String__Concat_2ReadOnlySpans, - 3 => WellKnownMember.System_String__Concat_3ReadOnlySpans, - 4 => WellKnownMember.System_String__Concat_4ReadOnlySpans, - _ => null, - }; - } - /// /// Most of the above optimizations are not applicable in expression trees as the operator /// must stay a binary operator. We cannot do much beyond constant folding which is done in binder. diff --git a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs index 0106206659751..8cda63f383d77 100644 --- a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs @@ -1352,6 +1352,8 @@ .maxstack 3 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] public void ConcatThree_UserInputOfSpanBasedConcat_ConcatWithString(int? missingUnimportantWellKnownMember) { var source = """ @@ -1386,13 +1388,13 @@ static void Main() verifier.VerifyIL("Test.M1", """ { // Code size 19 (0x13) - .maxstack 3 + .maxstack 2 IL_0000: ldarg.0 IL_0001: ldarg.1 IL_0002: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0007: ldarg.2 - IL_0008: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_000d: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0007: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_000c: ldarg.2 + IL_000d: call "string string.Concat(string, string)" IL_0012: ret } """); @@ -1401,20 +1403,18 @@ .maxstack 3 // Code size 19 (0x13) .maxstack 3 IL_0000: ldarg.2 - IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_0006: ldarg.0 - IL_0007: ldarg.1 - IL_0008: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_000d: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0001: ldarg.0 + IL_0002: ldarg.1 + IL_0003: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0008: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_000d: call "string string.Concat(string, string)" IL_0012: ret } """); } - [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] - [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - public void ConcatThree_UserInputOfSpanBasedConcat_ConcatWithChar(int? missingUnimportantWellKnownMember) + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatThree_UserInputOfSpanBasedConcat_ConcatWithChar() { var source = """ using System; @@ -1435,35 +1435,30 @@ static void Main() } """; - var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - - if (missingUnimportantWellKnownMember.HasValue) - { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); - } + var comp = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abccab" : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); - var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abccab" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); - - verifier.VerifyDiagnostics(); - verifier.VerifyIL("Test.M1", """ + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.M1", """ { - // Code size 22 (0x16) - .maxstack 3 + // Code size 32 (0x20) + .maxstack 2 .locals init (char V_0) IL_0000: ldarg.0 IL_0001: ldarg.1 IL_0002: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0007: ldarg.2 - IL_0008: stloc.0 - IL_0009: ldloca.s V_0 - IL_000b: newobj "System.ReadOnlySpan..ctor(in char)" - IL_0010: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0015: ret + IL_0007: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_000c: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0011: ldarg.2 + IL_0012: stloc.0 + IL_0013: ldloca.s V_0 + IL_0015: newobj "System.ReadOnlySpan..ctor(in char)" + IL_001a: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001f: ret } """); - verifier.VerifyIL("Test.M2", """ + comp.VerifyIL("Test.M2", """ { - // Code size 22 (0x16) + // Code size 32 (0x20) .maxstack 3 .locals init (char V_0) IL_0000: ldarg.2 @@ -1473,74 +1468,18 @@ .locals init (char V_0) IL_0009: ldarg.0 IL_000a: ldarg.1 IL_000b: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0010: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0015: ret - } - """); - } - - [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] - public void ConcatThree_UserInputOfSpanBasedConcat_ConcatWithString_MissingMemberToMergeIntoSingleConcat(int member) - { - var source = """ - using System; - - public class Test - { - static void Main() - { - var s1 = "a"; - var s2 = "b"; - var s3 = "c"; - Console.Write(M1(s1.AsSpan(), s2, s3)); - Console.Write(M2(s1.AsSpan(), s2, s3)); - } - - static string M1(ReadOnlySpan s1, string s2, string s3) => string.Concat(s1, s2.AsSpan()) + s3; - static string M2(ReadOnlySpan s1, string s2, string s3) => s3 + string.Concat(s1, s2.AsSpan()); - } - """; - - var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - comp.MakeMemberMissing((WellKnownMember)member); - - var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abccab" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); - - verifier.VerifyDiagnostics(); - verifier.VerifyIL("Test.M1", """ - { - // Code size 19 (0x13) - .maxstack 2 - IL_0000: ldarg.0 - IL_0001: ldarg.1 - IL_0002: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0007: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" - IL_000c: ldarg.2 - IL_000d: call "string string.Concat(string, string)" - IL_0012: ret - } - """); - verifier.VerifyIL("Test.M2", """ - { - // Code size 19 (0x13) - .maxstack 3 - IL_0000: ldarg.2 - IL_0001: ldarg.0 - IL_0002: ldarg.1 - IL_0003: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0008: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" - IL_000d: call "string string.Concat(string, string)" - IL_0012: ret + IL_0010: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0015: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_001a: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001f: ret } """); } [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] - public void ConcatThree_UserInputOfSpanBasedConcat_ConcatWithChar_MissingMemberToMergeIntoSingleConcat(int member) + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + public void ConcatThree_UserInputOfSpanBasedConcat_ConcatWithChar_MissingMemberForSpanBasedConcat(int member) { var source = """ using System; @@ -2828,6 +2767,7 @@ .maxstack 4 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] public void ConcatFour_UserInputOfSpanBasedConcatOf2_ConcatWithString(int? missingUnimportantWellKnownMember) { @@ -2864,56 +2804,50 @@ static void Main() verifier.VerifyDiagnostics(); verifier.VerifyIL("Test.M1", """ { - // Code size 25 (0x19) - .maxstack 4 + // Code size 20 (0x14) + .maxstack 3 IL_0000: ldarg.0 IL_0001: ldarg.1 IL_0002: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0007: ldarg.2 - IL_0008: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0007: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_000c: ldarg.2 IL_000d: ldarg.2 - IL_000e: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_0013: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0018: ret + IL_000e: call "string string.Concat(string, string, string)" + IL_0013: ret } """); verifier.VerifyIL("Test.M2", """ { - // Code size 25 (0x19) + // Code size 20 (0x14) .maxstack 4 IL_0000: ldarg.2 - IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_0006: ldarg.2 - IL_0007: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_000c: ldarg.0 - IL_000d: ldarg.1 - IL_000e: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0013: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0018: ret + IL_0001: ldarg.2 + IL_0002: ldarg.0 + IL_0003: ldarg.1 + IL_0004: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0009: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_000e: call "string string.Concat(string, string, string)" + IL_0013: ret } """); verifier.VerifyIL("Test.M3", """ { - // Code size 25 (0x19) - .maxstack 4 + // Code size 20 (0x14) + .maxstack 3 IL_0000: ldarg.2 - IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_0006: ldarg.0 - IL_0007: ldarg.1 - IL_0008: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0001: ldarg.0 + IL_0002: ldarg.1 + IL_0003: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0008: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" IL_000d: ldarg.2 - IL_000e: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_0013: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0018: ret + IL_000e: call "string string.Concat(string, string, string)" + IL_0013: ret } """); } - [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] - [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] - public void ConcatFour_UserInputOfSpanBasedConcatOf2_ConcatWithChar(int? missingUnimportantWellKnownMember) + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatFour_UserInputOfSpanBasedConcatOf2_ConcatWithChar() { var source = """ using System; @@ -2936,40 +2870,35 @@ static void Main() } """; - var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - - if (missingUnimportantWellKnownMember.HasValue) - { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); - } - - var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abccccabcabc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + var comp = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abccccabcabc" : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); - verifier.VerifyDiagnostics(); - verifier.VerifyIL("Test.M1", """ + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.M1", """ { - // Code size 31 (0x1f) - .maxstack 4 + // Code size 41 (0x29) + .maxstack 3 .locals init (char V_0, char V_1) IL_0000: ldarg.0 IL_0001: ldarg.1 IL_0002: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0007: ldarg.2 - IL_0008: stloc.0 - IL_0009: ldloca.s V_0 - IL_000b: newobj "System.ReadOnlySpan..ctor(in char)" - IL_0010: ldarg.2 - IL_0011: stloc.1 - IL_0012: ldloca.s V_1 - IL_0014: newobj "System.ReadOnlySpan..ctor(in char)" - IL_0019: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_001e: ret + IL_0007: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_000c: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0011: ldarg.2 + IL_0012: stloc.0 + IL_0013: ldloca.s V_0 + IL_0015: newobj "System.ReadOnlySpan..ctor(in char)" + IL_001a: ldarg.2 + IL_001b: stloc.1 + IL_001c: ldloca.s V_1 + IL_001e: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0023: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0028: ret } """); - verifier.VerifyIL("Test.M2", """ + comp.VerifyIL("Test.M2", """ { - // Code size 31 (0x1f) + // Code size 41 (0x29) .maxstack 4 .locals init (char V_0, char V_1) @@ -2984,14 +2913,16 @@ .locals init (char V_0, IL_0012: ldarg.0 IL_0013: ldarg.1 IL_0014: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0019: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_001e: ret + IL_0019: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001e: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0023: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0028: ret } """); - verifier.VerifyIL("Test.M3", """ + comp.VerifyIL("Test.M3", """ { - // Code size 31 (0x1f) - .maxstack 4 + // Code size 41 (0x29) + .maxstack 3 .locals init (char V_0, char V_1) IL_0000: ldarg.2 @@ -3001,20 +2932,20 @@ .locals init (char V_0, IL_0009: ldarg.0 IL_000a: ldarg.1 IL_000b: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0010: ldarg.2 - IL_0011: stloc.1 - IL_0012: ldloca.s V_1 - IL_0014: newobj "System.ReadOnlySpan..ctor(in char)" - IL_0019: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_001e: ret + IL_0010: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0015: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_001a: ldarg.2 + IL_001b: stloc.1 + IL_001c: ldloca.s V_1 + IL_001e: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0023: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0028: ret } """); } - [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] - [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] - public void ConcatFour_UserInputOfSpanBasedConcatOf2_ConcatWithStringAndChar(int? missingUnimportantWellKnownMember) + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatFour_UserInputOfSpanBasedConcatOf2_ConcatWithStringAndChar() { var source = """ using System; @@ -3044,55 +2975,52 @@ static void Main() } """; - var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - - if (missingUnimportantWellKnownMember.HasValue) - { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); - } - - var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abcdabdccdabdcabcabddabc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + var comp = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abcdabdccdabdcabcabddabc" : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); - verifier.VerifyDiagnostics(); - verifier.VerifyIL("Test.M1", """ + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.M1", """ { - // Code size 28 (0x1c) - .maxstack 4 + // Code size 38 (0x26) + .maxstack 3 .locals init (char V_0) IL_0000: ldarg.0 IL_0001: ldarg.1 IL_0002: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0007: ldarg.2 - IL_0008: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_000d: ldarg.3 - IL_000e: stloc.0 - IL_000f: ldloca.s V_0 - IL_0011: newobj "System.ReadOnlySpan..ctor(in char)" - IL_0016: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_001b: ret + IL_0007: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_000c: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0011: ldarg.2 + IL_0012: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0017: ldarg.3 + IL_0018: stloc.0 + IL_0019: ldloca.s V_0 + IL_001b: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0020: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0025: ret } """); - verifier.VerifyIL("Test.M2", """ + comp.VerifyIL("Test.M2", """ { - // Code size 28 (0x1c) - .maxstack 4 + // Code size 38 (0x26) + .maxstack 3 .locals init (char V_0) IL_0000: ldarg.0 IL_0001: ldarg.1 IL_0002: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0007: ldarg.3 - IL_0008: stloc.0 - IL_0009: ldloca.s V_0 - IL_000b: newobj "System.ReadOnlySpan..ctor(in char)" - IL_0010: ldarg.2 - IL_0011: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_0016: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_001b: ret + IL_0007: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_000c: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0011: ldarg.3 + IL_0012: stloc.0 + IL_0013: ldloca.s V_0 + IL_0015: newobj "System.ReadOnlySpan..ctor(in char)" + IL_001a: ldarg.2 + IL_001b: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0020: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0025: ret } """); - verifier.VerifyIL("Test.M3", """ + comp.VerifyIL("Test.M3", """ { - // Code size 28 (0x1c) + // Code size 38 (0x26) .maxstack 4 .locals init (char V_0) IL_0000: ldarg.2 @@ -3104,13 +3032,15 @@ .locals init (char V_0) IL_000f: ldarg.0 IL_0010: ldarg.1 IL_0011: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0016: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_001b: ret + IL_0016: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001b: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0020: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0025: ret } """); - verifier.VerifyIL("Test.M4", """ + comp.VerifyIL("Test.M4", """ { - // Code size 28 (0x1c) + // Code size 38 (0x26) .maxstack 4 .locals init (char V_0) IL_0000: ldarg.3 @@ -3122,32 +3052,36 @@ .locals init (char V_0) IL_000f: ldarg.0 IL_0010: ldarg.1 IL_0011: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0016: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_001b: ret + IL_0016: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001b: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0020: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0025: ret } """); - verifier.VerifyIL("Test.M5", """ + comp.VerifyIL("Test.M5", """ { - // Code size 28 (0x1c) - .maxstack 4 + // Code size 38 (0x26) + .maxstack 3 .locals init (char V_0) IL_0000: ldarg.2 IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" IL_0006: ldarg.0 IL_0007: ldarg.1 IL_0008: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_000d: ldarg.3 - IL_000e: stloc.0 - IL_000f: ldloca.s V_0 - IL_0011: newobj "System.ReadOnlySpan..ctor(in char)" - IL_0016: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_001b: ret + IL_000d: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0012: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0017: ldarg.3 + IL_0018: stloc.0 + IL_0019: ldloca.s V_0 + IL_001b: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0020: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0025: ret } """); - verifier.VerifyIL("Test.M6", """ + comp.VerifyIL("Test.M6", """ { - // Code size 28 (0x1c) - .maxstack 4 + // Code size 38 (0x26) + .maxstack 3 .locals init (char V_0) IL_0000: ldarg.3 IL_0001: stloc.0 @@ -3156,10 +3090,12 @@ .locals init (char V_0) IL_0009: ldarg.0 IL_000a: ldarg.1 IL_000b: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0010: ldarg.2 - IL_0011: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_0016: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_001b: ret + IL_0010: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0015: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_001a: ldarg.2 + IL_001b: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0020: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0025: ret } """); } @@ -3168,7 +3104,6 @@ .locals init (char V_0) [InlineData(null)] [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] public void ConcatFour_TwoUserInputsOfSpanBasedConcatOf2(int? missingUnimportantWellKnownMember) { var source = """ @@ -3201,14 +3136,16 @@ static void Main() verifier.VerifyDiagnostics(); verifier.VerifyIL("Test.M", """ { - // Code size 10 (0xa) - .maxstack 4 + // Code size 20 (0x14) + .maxstack 3 IL_0000: ldarg.0 IL_0001: ldarg.1 - IL_0002: ldarg.2 - IL_0003: ldarg.3 - IL_0004: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0009: ret + IL_0002: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0007: ldarg.2 + IL_0008: ldarg.3 + IL_0009: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_000e: call "string string.Concat(string, string)" + IL_0013: ret } """); } @@ -3216,6 +3153,8 @@ .maxstack 4 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] public void ConcatFour_UserInputOfSpanBasedConcatOf3_ConcatWithString(int? missingUnimportantWellKnownMember) { var source = """ @@ -3251,14 +3190,14 @@ static void Main() verifier.VerifyIL("Test.M1", """ { // Code size 20 (0x14) - .maxstack 4 + .maxstack 3 IL_0000: ldarg.0 IL_0001: ldarg.1 IL_0002: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" IL_0007: ldarg.2 - IL_0008: ldarg.3 - IL_0009: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_000e: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0008: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_000d: ldarg.3 + IL_000e: call "string string.Concat(string, string)" IL_0013: ret } """); @@ -3267,21 +3206,19 @@ .maxstack 4 // Code size 20 (0x14) .maxstack 4 IL_0000: ldarg.3 - IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_0006: ldarg.0 - IL_0007: ldarg.1 - IL_0008: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_000d: ldarg.2 - IL_000e: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0001: ldarg.0 + IL_0002: ldarg.1 + IL_0003: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_0008: ldarg.2 + IL_0009: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_000e: call "string string.Concat(string, string)" IL_0013: ret } """); } - [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] - [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - public void ConcatFour_UserInputOfSpanBasedConcatOf3_ConcatWithChar(int? missingUnimportantWellKnownMember) + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatFour_UserInputOfSpanBasedConcatOf3_ConcatWithChar() { var source = """ using System; @@ -3303,36 +3240,31 @@ static void Main() } """; - var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - - if (missingUnimportantWellKnownMember.HasValue) - { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); - } - - var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abcddabc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + var comp = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abcddabc" : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); - verifier.VerifyDiagnostics(); - verifier.VerifyIL("Test.M1", """ + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.M1", """ { - // Code size 23 (0x17) - .maxstack 4 + // Code size 33 (0x21) + .maxstack 3 .locals init (char V_0) IL_0000: ldarg.0 IL_0001: ldarg.1 IL_0002: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" IL_0007: ldarg.2 - IL_0008: ldarg.3 - IL_0009: stloc.0 - IL_000a: ldloca.s V_0 - IL_000c: newobj "System.ReadOnlySpan..ctor(in char)" - IL_0011: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0016: ret + IL_0008: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_000d: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0012: ldarg.3 + IL_0013: stloc.0 + IL_0014: ldloca.s V_0 + IL_0016: newobj "System.ReadOnlySpan..ctor(in char)" + IL_001b: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0020: ret } """); - verifier.VerifyIL("Test.M2", """ + comp.VerifyIL("Test.M2", """ { - // Code size 23 (0x17) + // Code size 33 (0x21) .maxstack 4 .locals init (char V_0) IL_0000: ldarg.3 @@ -3343,95 +3275,19 @@ .locals init (char V_0) IL_000a: ldarg.1 IL_000b: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" IL_0010: ldarg.2 - IL_0011: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0016: ret - } - """); - } - - [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans, (int)WellKnownMember.System_String__Concat_4ReadOnlySpans)] - public void ConcatFour_UserInputOfSpanBasedConcatOf2_ConcatWithString_MissingMembersToMergeIntoSingleConcat(params int[] members) - { - var source = """ - using System; - - public class Test - { - static void Main() - { - var s1 = "a"; - var s2 = "b"; - var s3 = "c"; - Console.Write(M1(s1.AsSpan(), s2, s3)); - Console.Write(M2(s1.AsSpan(), s2, s3)); - Console.Write(M3(s1.AsSpan(), s2, s3)); - } - - static string M1(ReadOnlySpan s1, string s2, string s3) => string.Concat(s1, s2.AsSpan()) + s3 + s3; - static string M2(ReadOnlySpan s1, string s2, string s3) => s3 + s3 + string.Concat(s1, s2.AsSpan()); - static string M3(ReadOnlySpan s1, string s2, string s3) => s3 + string.Concat(s1, s2.AsSpan()) + s3; - } - """; - - var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - foreach (var member in members) - { - comp.MakeMemberMissing((WellKnownMember)member); - } - - var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abccccabcabc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); - - verifier.VerifyDiagnostics(); - verifier.VerifyIL("Test.M1", """ - { - // Code size 20 (0x14) - .maxstack 3 - IL_0000: ldarg.0 - IL_0001: ldarg.1 - IL_0002: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0007: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" - IL_000c: ldarg.2 - IL_000d: ldarg.2 - IL_000e: call "string string.Concat(string, string, string)" - IL_0013: ret - } - """); - verifier.VerifyIL("Test.M2", """ - { - // Code size 20 (0x14) - .maxstack 4 - IL_0000: ldarg.2 - IL_0001: ldarg.2 - IL_0002: ldarg.0 - IL_0003: ldarg.1 - IL_0004: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0009: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" - IL_000e: call "string string.Concat(string, string, string)" - IL_0013: ret - } - """); - verifier.VerifyIL("Test.M3", """ - { - // Code size 20 (0x14) - .maxstack 3 - IL_0000: ldarg.2 - IL_0001: ldarg.0 - IL_0002: ldarg.1 - IL_0003: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0008: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" - IL_000d: ldarg.2 - IL_000e: call "string string.Concat(string, string, string)" - IL_0013: ret + IL_0011: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0016: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_001b: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0020: ret } """); } [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans, (int)WellKnownMember.System_String__Concat_4ReadOnlySpans)] - public void ConcatFour_UserInputOfSpanBasedConcatOf2_ConcatWithChar_MissingMembersToMergeIntoSingleConcat(params int[] members) + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] + public void ConcatFour_UserInputOfSpanBasedConcatOf2_ConcatWithChar_MissingMemberForSpanBasedConcatConcat(int member) { var source = """ using System; @@ -3455,10 +3311,7 @@ static void Main() """; var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - foreach (var member in members) - { - comp.MakeMemberMissing((WellKnownMember)member); - } + comp.MakeMemberMissing((WellKnownMember)member); var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abccccabcabc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); @@ -3514,11 +3367,10 @@ .maxstack 3 } [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] - [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference, (int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans, (int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans, (int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans, (int)WellKnownMember.System_String__Concat_4ReadOnlySpans)] - public void ConcatFour_UserInputOfSpanBasedConcatOf2_ConcatWithStringAndChar_MissingMembersToMergeIntoSingleConcat(params int[] members) + [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] + public void ConcatFour_UserInputOfSpanBasedConcatOf2_ConcatWithStringAndChar_MissingMemberForSpanBasedConcat(int member) { var source = """ using System; @@ -3549,10 +3401,7 @@ static void Main() """; var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - foreach (var member in members) - { - comp.MakeMemberMissing((WellKnownMember)member); - } + comp.MakeMemberMissing((WellKnownMember)member); var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abcdabdccdabdcabcabddabc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); @@ -3650,53 +3499,10 @@ .maxstack 3 } [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] - [InlineData((int)WellKnownMember.System_String__Concat_4ReadOnlySpans)] - public void ConcatFour_TwoUserInputsOfSpanBasedConcatOf2_MissingMemberToMergeIntoSingleConcat(int member) - { - var source = """ - using System; - - public class Test - { - static void Main() - { - var s1 = "a"; - var s2 = "b"; - var s3 = "c"; - var s4 = "d"; - Console.Write(M(s1.AsSpan(), s2.AsSpan(), s3.AsSpan(), s4.AsSpan())); - } - - static string M(ReadOnlySpan s1, ReadOnlySpan s2, ReadOnlySpan s3, ReadOnlySpan s4) => string.Concat(s1, s2) + string.Concat(s3, s4); - } - """; - - var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - comp.MakeMemberMissing((WellKnownMember)member); - - var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abcd" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); - - verifier.VerifyDiagnostics(); - verifier.VerifyIL("Test.M", """ - { - // Code size 20 (0x14) - .maxstack 3 - IL_0000: ldarg.0 - IL_0001: ldarg.1 - IL_0002: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0007: ldarg.2 - IL_0008: ldarg.3 - IL_0009: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" - IL_000e: call "string string.Concat(string, string)" - IL_0013: ret - } - """); - } - - [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_String__Concat_4ReadOnlySpans)] - public void ConcatFour_UserInputOfSpanBasedConcatOf3_ConcatWithString_MissingMemberToMergeIntoSingleConcat(int member) + [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] + public void ConcatFour_UserInputOfSpanBasedConcatOf3_ConcatWithChar_MissingMemberForSpanBasedConcat(int member) { var source = """ using System; @@ -3708,13 +3514,13 @@ static void Main() var s1 = "a"; var s2 = "b"; var s3 = "c"; - var s4 = "d"; - Console.Write(M1(s1.AsSpan(), s2, s3.AsSpan(), s4)); - Console.Write(M2(s1.AsSpan(), s2, s3.AsSpan(), s4)); + var c = 'd'; + Console.Write(M1(s1.AsSpan(), s2, s3.AsSpan(), c)); + Console.Write(M2(s1.AsSpan(), s2, s3.AsSpan(), c)); } - static string M1(ReadOnlySpan s1, string s2, ReadOnlySpan s3, string s4) => string.Concat(s1, s2.AsSpan(), s3) + s4; - static string M2(ReadOnlySpan s1, string s2, ReadOnlySpan s3, string s4) => s4 + string.Concat(s1, s2.AsSpan(), s3); + static string M1(ReadOnlySpan s1, string s2, ReadOnlySpan s3, char c) => string.Concat(s1, s2.AsSpan(), s3) + c; + static string M2(ReadOnlySpan s1, string s2, ReadOnlySpan s3, char c) => c + string.Concat(s1, s2.AsSpan(), s3); } """; @@ -3726,103 +3532,42 @@ static void Main() verifier.VerifyDiagnostics(); verifier.VerifyIL("Test.M1", """ { - // Code size 20 (0x14) + // Code size 26 (0x1a) .maxstack 3 IL_0000: ldarg.0 IL_0001: ldarg.1 IL_0002: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" IL_0007: ldarg.2 IL_0008: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_000d: ldarg.3 - IL_000e: call "string string.Concat(string, string)" - IL_0013: ret + IL_000d: ldarga.s V_3 + IL_000f: call "string char.ToString()" + IL_0014: call "string string.Concat(string, string)" + IL_0019: ret } """); verifier.VerifyIL("Test.M2", """ { - // Code size 20 (0x14) + // Code size 26 (0x1a) .maxstack 4 - IL_0000: ldarg.3 - IL_0001: ldarg.0 - IL_0002: ldarg.1 - IL_0003: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0008: ldarg.2 - IL_0009: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_000e: call "string string.Concat(string, string)" - IL_0013: ret + IL_0000: ldarga.s V_3 + IL_0002: call "string char.ToString()" + IL_0007: ldarg.0 + IL_0008: ldarg.1 + IL_0009: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" + IL_000e: ldarg.2 + IL_000f: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0014: call "string string.Concat(string, string)" + IL_0019: ret } """); } - [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] - [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__Concat_4ReadOnlySpans)] - public void ConcatFour_UserInputOfSpanBasedConcatOf3_ConcatWithChar_MissingMemberToMergeIntoSingleConcat(int member) + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatFour_MissingObjectToString() { var source = """ using System; - - public class Test - { - static void Main() - { - var s1 = "a"; - var s2 = "b"; - var s3 = "c"; - var c = 'd'; - Console.Write(M1(s1.AsSpan(), s2, s3.AsSpan(), c)); - Console.Write(M2(s1.AsSpan(), s2, s3.AsSpan(), c)); - } - - static string M1(ReadOnlySpan s1, string s2, ReadOnlySpan s3, char c) => string.Concat(s1, s2.AsSpan(), s3) + c; - static string M2(ReadOnlySpan s1, string s2, ReadOnlySpan s3, char c) => c + string.Concat(s1, s2.AsSpan(), s3); - } - """; - - var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - comp.MakeMemberMissing((WellKnownMember)member); - - var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abcddabc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); - - verifier.VerifyDiagnostics(); - verifier.VerifyIL("Test.M1", """ - { - // Code size 26 (0x1a) - .maxstack 3 - IL_0000: ldarg.0 - IL_0001: ldarg.1 - IL_0002: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0007: ldarg.2 - IL_0008: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_000d: ldarga.s V_3 - IL_000f: call "string char.ToString()" - IL_0014: call "string string.Concat(string, string)" - IL_0019: ret - } - """); - verifier.VerifyIL("Test.M2", """ - { - // Code size 26 (0x1a) - .maxstack 4 - IL_0000: ldarga.s V_3 - IL_0002: call "string char.ToString()" - IL_0007: ldarg.0 - IL_0008: ldarg.1 - IL_0009: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_000e: ldarg.2 - IL_000f: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0014: call "string string.Concat(string, string)" - IL_0019: ret - } - """); - } - - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] - public void ConcatFour_MissingObjectToString() - { - var source = """ - using System; - + public class Test { static void Main() @@ -4149,463 +3894,4 @@ .maxstack 4 } """); } - - [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] - [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] - [InlineData((int)WellKnownMember.System_String__Concat_4ReadOnlySpans)] - public void ConcatTotalOfFive_UserInputOfSpanBasedConcatOf2(int? missingUnimportantWellKnownMember) - { - var source = """ - using System; - - public class Test - { - static void Main() - { - var s = "s"; - var c = 'c'; - Console.Write(M1(s, c)); - Console.Write(M2(s, c)); - Console.Write(M3(s, c)); - Console.Write(M4(s, c)); - } - - static string M1(string s, char c) => string.Concat(s.AsSpan(), s.AsSpan()) + c + s + s; - static string M2(string s, char c) => s + string.Concat(s.AsSpan(), s.AsSpan()) + s + c; - static string M3(string s, char c) => s + s + c + string.Concat(s.AsSpan(), s.AsSpan()); - static string M4(string s, char c) => string.Concat(s.AsSpan(), s.AsSpan()) + c + string.Concat(s.AsSpan(), s.AsSpan()); - } - """; - - var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - - if (missingUnimportantWellKnownMember.HasValue) - { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); - } - - var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "sscsssssscsscsssscss" : null, verify: ExecutionConditionUtil.IsCoreClr ? default : Verification.Skipped); - - verifier.VerifyDiagnostics(); - - // Since combined length of arguments if we unwrap all user-provided span-based concats is >4, - // we just lower outer concat as is without unwrapping any user-provided ones. - verifier.VerifyIL("Test.M1", """ - { - // Code size 32 (0x20) - .maxstack 4 - IL_0000: ldarg.0 - IL_0001: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0006: ldarg.0 - IL_0007: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_000c: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0011: ldarga.s V_1 - IL_0013: call "string char.ToString()" - IL_0018: ldarg.0 - IL_0019: ldarg.0 - IL_001a: call "string string.Concat(string, string, string, string)" - IL_001f: ret - } - """); - verifier.VerifyIL("Test.M2", """ - { - // Code size 32 (0x20) - .maxstack 4 - IL_0000: ldarg.0 - IL_0001: ldarg.0 - IL_0002: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0007: ldarg.0 - IL_0008: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_000d: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0012: ldarg.0 - IL_0013: ldarga.s V_1 - IL_0015: call "string char.ToString()" - IL_001a: call "string string.Concat(string, string, string, string)" - IL_001f: ret - } - """); - verifier.VerifyIL("Test.M3", """ - { - // Code size 32 (0x20) - .maxstack 5 - IL_0000: ldarg.0 - IL_0001: ldarg.0 - IL_0002: ldarga.s V_1 - IL_0004: call "string char.ToString()" - IL_0009: ldarg.0 - IL_000a: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_000f: ldarg.0 - IL_0010: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0015: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" - IL_001a: call "string string.Concat(string, string, string, string)" - IL_001f: ret - } - """); - verifier.VerifyIL("Test.M4", """ - { - // Code size 47 (0x2f) - .maxstack 4 - IL_0000: ldarg.0 - IL_0001: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0006: ldarg.0 - IL_0007: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_000c: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0011: ldarga.s V_1 - IL_0013: call "string char.ToString()" - IL_0018: ldarg.0 - IL_0019: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_001e: ldarg.0 - IL_001f: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0024: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0029: call "string string.Concat(string, string, string)" - IL_002e: ret - } - """); - } - - [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] - [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] - [InlineData((int)WellKnownMember.System_String__Concat_4ReadOnlySpans)] - public void ConcatTotalOfFive_UserInputOfSpanBasedConcatOf3(int? missingUnimportantWellKnownMember) - { - var source = """ - using System; - - public class Test - { - static void Main() - { - var s = "s"; - var c = 'c'; - Console.Write(M1(s, c)); - Console.Write(M2(s, c)); - Console.Write(M3(s, c)); - } - - static string M1(string s, char c) => string.Concat(s.AsSpan(), s.AsSpan(), s.AsSpan()) + c + s; - static string M2(string s, char c) => s + string.Concat(s.AsSpan(), s.AsSpan(), s.AsSpan()) + c; - static string M3(string s, char c) => s + c + string.Concat(s.AsSpan(), s.AsSpan(), s.AsSpan()); - } - """; - - var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - - if (missingUnimportantWellKnownMember.HasValue) - { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); - } - - var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "ssscssssscscsss" : null, verify: ExecutionConditionUtil.IsCoreClr ? default : Verification.Skipped); - - verifier.VerifyDiagnostics(); - - // Since combined length of arguments if we unwrap all user-provided span-based concats is >4, - // we just lower outer concat as is without unwrapping any user-provided ones. - verifier.VerifyIL("Test.M1", """ - { - // Code size 37 (0x25) - .maxstack 3 - IL_0000: ldarg.0 - IL_0001: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0006: ldarg.0 - IL_0007: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_000c: ldarg.0 - IL_000d: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0012: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0017: ldarga.s V_1 - IL_0019: call "string char.ToString()" - IL_001e: ldarg.0 - IL_001f: call "string string.Concat(string, string, string)" - IL_0024: ret - } - """); - verifier.VerifyIL("Test.M2", """ - { - // Code size 37 (0x25) - .maxstack 4 - IL_0000: ldarg.0 - IL_0001: ldarg.0 - IL_0002: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0007: ldarg.0 - IL_0008: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_000d: ldarg.0 - IL_000e: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0013: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0018: ldarga.s V_1 - IL_001a: call "string char.ToString()" - IL_001f: call "string string.Concat(string, string, string)" - IL_0024: ret - } - """); - verifier.VerifyIL("Test.M3", """ - { - // Code size 37 (0x25) - .maxstack 5 - IL_0000: ldarg.0 - IL_0001: ldarga.s V_1 - IL_0003: call "string char.ToString()" - IL_0008: ldarg.0 - IL_0009: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_000e: ldarg.0 - IL_000f: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0014: ldarg.0 - IL_0015: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_001a: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_001f: call "string string.Concat(string, string, string)" - IL_0024: ret - } - """); - } - - [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] - [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__Concat_4ReadOnlySpans)] - public void ConcatTotalOfFive_UserInputOfMixedSpanBasedConcatsOf2And3(int? missingUnimportantWellKnownMember) - { - var source = """ - using System; - - public class Test - { - static void Main() - { - var s = "s"; - Console.Write(M1(s)); - Console.Write(M2(s)); - } - - static string M1(string s) => string.Concat(s.AsSpan(), s.AsSpan()) + string.Concat(s.AsSpan(), s.AsSpan(), s.AsSpan()); - static string M2(string s) => string.Concat(s.AsSpan(), s.AsSpan(), s.AsSpan()) + string.Concat(s.AsSpan(), s.AsSpan()); - } - """; - - var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - - if (missingUnimportantWellKnownMember.HasValue) - { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); - } - - var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "ssssssssss" : null, verify: ExecutionConditionUtil.IsCoreClr ? default : Verification.Skipped); - - verifier.VerifyDiagnostics(); - - // Since combined length of arguments if we unwrap all user-provided span-based concats is >4, - // we just lower outer concat as is without unwrapping any user-provided ones. - verifier.VerifyIL("Test.M1", """ - { - // Code size 46 (0x2e) - .maxstack 4 - IL_0000: ldarg.0 - IL_0001: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0006: ldarg.0 - IL_0007: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_000c: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0011: ldarg.0 - IL_0012: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0017: ldarg.0 - IL_0018: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_001d: ldarg.0 - IL_001e: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0023: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0028: call "string string.Concat(string, string)" - IL_002d: ret - } - """); - verifier.VerifyIL("Test.M2", """ - { - // Code size 46 (0x2e) - .maxstack 3 - IL_0000: ldarg.0 - IL_0001: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0006: ldarg.0 - IL_0007: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_000c: ldarg.0 - IL_000d: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0012: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0017: ldarg.0 - IL_0018: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_001d: ldarg.0 - IL_001e: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0023: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0028: call "string string.Concat(string, string)" - IL_002d: ret - } - """); - } - - [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] - [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - public void ConcatTotalOfMoreThanFive_UserInputOf2SpanBasedConcatsOf2InARow(int? missingUnimportantWellKnownMember) - { - var source = """ - using System; - - public class Test - { - static void Main() - { - var s = "s"; - Console.Write(M1(s)); - Console.Write(M2(s)); - } - - static string M1(string s) => string.Concat(s.AsSpan(), s.AsSpan()) + string.Concat(s.AsSpan(), s.AsSpan()) + string.Concat(s.AsSpan(), s.AsSpan(), s.AsSpan()); - static string M2(string s) => string.Concat(s.AsSpan(), s.AsSpan(), s.AsSpan()) + string.Concat(s.AsSpan(), s.AsSpan()) + string.Concat(s.AsSpan(), s.AsSpan()); - } - """; - - var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - - if (missingUnimportantWellKnownMember.HasValue) - { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); - } - - var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "ssssssssssssss" : null, verify: ExecutionConditionUtil.IsCoreClr ? default : Verification.Skipped); - - verifier.VerifyDiagnostics(); - - // This is an edge case where due to order of processing arguments during rewriting we manage to merge 2 concats of 2 into a single concat of 4 in the first case (which changes what we get in the end), - // but in the second case we leave things untouched. Both lowerings produce correct output in the end, so this isn't much to worry about - verifier.VerifyIL("Test.M1", """ - { - // Code size 58 (0x3a) - .maxstack 4 - IL_0000: ldarg.0 - IL_0001: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0006: ldarg.0 - IL_0007: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_000c: ldarg.0 - IL_000d: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0012: ldarg.0 - IL_0013: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0018: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_001d: ldarg.0 - IL_001e: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0023: ldarg.0 - IL_0024: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0029: ldarg.0 - IL_002a: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_002f: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0034: call "string string.Concat(string, string)" - IL_0039: ret - } - """); - verifier.VerifyIL("Test.M2", """ - { - // Code size 63 (0x3f) - .maxstack 4 - IL_0000: ldarg.0 - IL_0001: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0006: ldarg.0 - IL_0007: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_000c: ldarg.0 - IL_000d: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0012: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0017: ldarg.0 - IL_0018: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_001d: ldarg.0 - IL_001e: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0023: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0028: ldarg.0 - IL_0029: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_002e: ldarg.0 - IL_002f: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0034: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0039: call "string string.Concat(string, string, string)" - IL_003e: ret - } - """); - } - - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] - public void ConcatTotalOfMoreThanFive_UserInputOf2SpanBasedConcatsOf2InARow_MissingSpanBasedConcatOf4() - { - var source = """ - using System; - - public class Test - { - static void Main() - { - var s = "s"; - Console.Write(M1(s)); - Console.Write(M2(s)); - } - - static string M1(string s) => string.Concat(s.AsSpan(), s.AsSpan()) + string.Concat(s.AsSpan(), s.AsSpan()) + string.Concat(s.AsSpan(), s.AsSpan(), s.AsSpan()); - static string M2(string s) => string.Concat(s.AsSpan(), s.AsSpan(), s.AsSpan()) + string.Concat(s.AsSpan(), s.AsSpan()) + string.Concat(s.AsSpan(), s.AsSpan()); - } - """; - - var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - comp.MakeMemberMissing(WellKnownMember.System_String__Concat_4ReadOnlySpans); - - var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "ssssssssssssss" : null, verify: ExecutionConditionUtil.IsCoreClr ? default : Verification.Skipped); - - verifier.VerifyDiagnostics(); - - // If span-based concat of 4 is missing we produce Il equivalent to what user has written without any changes - verifier.VerifyIL("Test.M1", """ - { - // Code size 63 (0x3f) - .maxstack 5 - IL_0000: ldarg.0 - IL_0001: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0006: ldarg.0 - IL_0007: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_000c: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0011: ldarg.0 - IL_0012: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0017: ldarg.0 - IL_0018: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_001d: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0022: ldarg.0 - IL_0023: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0028: ldarg.0 - IL_0029: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_002e: ldarg.0 - IL_002f: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0034: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0039: call "string string.Concat(string, string, string)" - IL_003e: ret - } - """); - verifier.VerifyIL("Test.M2", """ - { - // Code size 63 (0x3f) - .maxstack 4 - IL_0000: ldarg.0 - IL_0001: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0006: ldarg.0 - IL_0007: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_000c: ldarg.0 - IL_000d: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0012: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0017: ldarg.0 - IL_0018: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_001d: ldarg.0 - IL_001e: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0023: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0028: ldarg.0 - IL_0029: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_002e: ldarg.0 - IL_002f: call "System.ReadOnlySpan System.MemoryExtensions.AsSpan(string)" - IL_0034: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0039: call "string string.Concat(string, string, string)" - IL_003e: ret - } - """); - } } From 1118dcf347e2273bdabd8b86fc7bf8317a3d10d8 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sat, 3 Feb 2024 23:50:08 +0300 Subject: [PATCH 22/71] Handle case when `char` doesn't override `object.ToString()` --- .../LocalRewriter_StringConcat.cs | 1 + .../CodeGenSpanBasedStringConcatTests.cs | 216 ++++++++++++++++++ 2 files changed, 217 insertions(+) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index 740219dd44ae4..b24951462e70b 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -420,6 +420,7 @@ private bool TryRewriteStringConcatenationWithSpanBasedConcat(SyntaxNode syntax, Debug.Assert(arg.HasAnyErrors || arg.Type?.IsStringType() == true); if (arg is BoundCall { ReceiverOpt: { Type: NamedTypeSymbol { SpecialType: SpecialType.System_Char } receiverCharType } receiver } potentialToStringCall && + (object)potentialToStringCall.Method != _compilation.GetSpecialTypeMember(SpecialMember.System_Object__ToString) && (object)potentialToStringCall.Method.GetLeastOverriddenMethod(charType) == _compilation.GetSpecialTypeMember(SpecialMember.System_Object__ToString)) { needsSpanRefParamConstructor = true; diff --git a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs index 8cda63f383d77..6120d19eaf37f 100644 --- a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs @@ -546,6 +546,76 @@ static void Main() Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "c").WithArguments("System.Object", "ToString").WithLocation(14, 43)); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatTwo_CharDoesntOverrideObjectToString() + { + var corlib_cs = """ + namespace System + { + public class Object + { + public virtual string ToString() => null; + } + public class String + { + public static string Concat(string str0, string str1) => null; + public static string Concat(ReadOnlySpan str0, ReadOnlySpan str1) => null; + public static implicit operator ReadOnlySpan(string value) => default; + } + public class ValueType { } + public struct Char { } + public struct Void { } + public struct Int32 { } + public struct Byte { } + public struct Boolean { } + public struct ReadOnlySpan + { + public ReadOnlySpan(ref readonly T reference) { } + } + public class Enum : ValueType { } + public class Attribute { } + public enum AttributeTargets { } + public class AttributeUsageAttribute : Attribute + { + public AttributeUsageAttribute(AttributeTargets validOn) { } + + public bool AllowMultiple { get { return default; } set { } } + public bool Inherited { get { return default; } set { } } + } + } + """; + + var corlib = CreateEmptyCompilation(corlib_cs).EmitToImageReference(); + + var source = """ + public class Test + { + static string M(string s, char c) => s + c; + } + """; + + var comp = CreateEmptyCompilation(source, [corlib]); + comp.VerifyDiagnostics(); + + var verifier = CompileAndVerify(compilation: comp); + verifier.VerifyDiagnostics(); + + // Even though we have all other members for span-based concatenation, `char` doesn't override `ToString` + // thus we cannot rely on its well-known behavior and emit string-based concat with a virtual `object.ToString()` call + verifier.VerifyIL("Test.M", """ + { + // Code size 20 (0x14) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldarga.s V_1 + IL_0003: constrained. "char" + IL_0009: callvirt "string object.ToString()" + IL_000e: call "string string.Concat(string, string)" + IL_0013: ret + } + """); + } + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] @@ -1584,6 +1654,78 @@ static void Main() Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "c").WithArguments("System.Object", "ToString").WithLocation(18, 51)); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatThree_CharDoesntOverrideObjectToString() + { + var corlib_cs = """ + namespace System + { + public class Object + { + public virtual string ToString() => null; + } + public class String + { + public static string Concat(string str0, string str1) => null; + public static string Concat(string str0, string str1, string str2) => null; + public static string Concat(ReadOnlySpan str0, ReadOnlySpan str1, ReadOnlySpan str2) => null; + public static implicit operator ReadOnlySpan(string value) => default; + } + public class ValueType { } + public struct Char { } + public struct Void { } + public struct Int32 { } + public struct Byte { } + public struct Boolean { } + public struct ReadOnlySpan + { + public ReadOnlySpan(ref readonly T reference) { } + } + public class Enum : ValueType { } + public class Attribute { } + public enum AttributeTargets { } + public class AttributeUsageAttribute : Attribute + { + public AttributeUsageAttribute(AttributeTargets validOn) { } + + public bool AllowMultiple { get { return default; } set { } } + public bool Inherited { get { return default; } set { } } + } + } + """; + + var corlib = CreateEmptyCompilation(corlib_cs).EmitToImageReference(); + + var source = """ + public class Test + { + static string M(string s, char c) => s + c + s; + } + """; + + var comp = CreateEmptyCompilation(source, [corlib]); + comp.VerifyDiagnostics(); + + var verifier = CompileAndVerify(compilation: comp); + verifier.VerifyDiagnostics(); + + // Even though we have all other members for span-based concatenation, `char` doesn't override `ToString` + // thus we cannot rely on its well-known behavior and emit string-based concat with a virtual `object.ToString()` call + verifier.VerifyIL("Test.M", """ + { + // Code size 21 (0x15) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: ldarga.s V_1 + IL_0003: constrained. "char" + IL_0009: callvirt "string object.ToString()" + IL_000e: ldarg.0 + IL_000f: call "string string.Concat(string, string, string)" + IL_0014: ret + } + """); + } + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] @@ -3631,6 +3773,80 @@ static void Main() Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "c").WithArguments("System.Object", "ToString").WithLocation(24, 55)); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatFour_CharDoesntOverrideObjectToString() + { + var corlib_cs = """ + namespace System + { + public class Object + { + public virtual string ToString() => null; + } + public class String + { + public static string Concat(string str0, string str1) => null; + public static string Concat(string str0, string str1, string str2) => null; + public static string Concat(string str0, string str1, string str2, string str3) => null; + public static string Concat(ReadOnlySpan str0, ReadOnlySpan str1, ReadOnlySpan str2, ReadOnlySpan str3) => null; + public static implicit operator ReadOnlySpan(string value) => default; + } + public class ValueType { } + public struct Char { } + public struct Void { } + public struct Int32 { } + public struct Byte { } + public struct Boolean { } + public struct ReadOnlySpan + { + public ReadOnlySpan(ref readonly T reference) { } + } + public class Enum : ValueType { } + public class Attribute { } + public enum AttributeTargets { } + public class AttributeUsageAttribute : Attribute + { + public AttributeUsageAttribute(AttributeTargets validOn) { } + + public bool AllowMultiple { get { return default; } set { } } + public bool Inherited { get { return default; } set { } } + } + } + """; + + var corlib = CreateEmptyCompilation(corlib_cs).EmitToImageReference(); + + var source = """ + public class Test + { + static string M(string s, char c) => s + c + s + s; + } + """; + + var comp = CreateEmptyCompilation(source, [corlib]); + comp.VerifyDiagnostics(); + + var verifier = CompileAndVerify(compilation: comp); + verifier.VerifyDiagnostics(); + + // Even though we have all other members for span-based concatenation, `char` doesn't override `ToString` + // thus we cannot rely on its well-known behavior and emit string-based concat with a virtual `object.ToString()` call + verifier.VerifyIL("Test.M", """ + { + // Code size 22 (0x16) + .maxstack 4 + IL_0000: ldarg.0 + IL_0001: ldarga.s V_1 + IL_0003: constrained. "char" + IL_0009: callvirt "string object.ToString()" + IL_000e: ldarg.0 + IL_000f: ldarg.0 + IL_0010: call "string string.Concat(string, string, string, string)" + IL_0015: ret + } + """); + } + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] From 2b11728a59149b70fa0d8e145276e27f8764e812 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sun, 4 Feb 2024 17:19:46 +0300 Subject: [PATCH 23/71] Fix --- .../Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs index 6120d19eaf37f..41e9cc518931b 100644 --- a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs @@ -597,7 +597,7 @@ public class Test var comp = CreateEmptyCompilation(source, [corlib]); comp.VerifyDiagnostics(); - var verifier = CompileAndVerify(compilation: comp); + var verifier = CompileAndVerify(compilation: comp, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); verifier.VerifyDiagnostics(); // Even though we have all other members for span-based concatenation, `char` doesn't override `ToString` @@ -1706,7 +1706,7 @@ public class Test var comp = CreateEmptyCompilation(source, [corlib]); comp.VerifyDiagnostics(); - var verifier = CompileAndVerify(compilation: comp); + var verifier = CompileAndVerify(compilation: comp, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); verifier.VerifyDiagnostics(); // Even though we have all other members for span-based concatenation, `char` doesn't override `ToString` @@ -3826,7 +3826,7 @@ public class Test var comp = CreateEmptyCompilation(source, [corlib]); comp.VerifyDiagnostics(); - var verifier = CompileAndVerify(compilation: comp); + var verifier = CompileAndVerify(compilation: comp, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); verifier.VerifyDiagnostics(); // Even though we have all other members for span-based concatenation, `char` doesn't override `ToString` From 6c3423ee8178ddbc43e9ca7cdccabcf08505d49d Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Tue, 6 Feb 2024 19:43:48 +0300 Subject: [PATCH 24/71] Track sequence locals --- .../LocalRewriter_StringConcat.cs | 115 ++++++++++-------- 1 file changed, 66 insertions(+), 49 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index b24951462e70b..c606110f039c6 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -193,53 +193,6 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr } } } - - if ((object)method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_2ReadOnlySpans) || - (object)method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_3ReadOnlySpans) || - (object)method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_4ReadOnlySpans)) - { - // Faced a span-based `string.Concat` call. Since we can produce such call on the previous iterations ourselves, we need to unwrap it. - // The key thing is that we need not to only extract arguments, but also unwrap them from being spans and for chars also wrap them into `ToString` calls. - var wrappedArgs = boundCall.Arguments; - var unwrappedArgsBuilder = ArrayBuilder.GetInstance(capacity: wrappedArgs.Length); - - for (var i = 0; i < wrappedArgs.Length; i++) - { - var wrappedArg = wrappedArgs[i]; - - // Check whether a call is an implicit `string -> ReadOnlySpan` conversion - if (wrappedArg is BoundCall { Method: var argMethod, Arguments: [var singleArgument] } && - (object)argMethod == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)) - { - unwrappedArgsBuilder.Add(singleArgument); - } - // This complicated check is for a sequence, which wraps a span around single char. - // The sequence needs to have this shape: `{ locals: , sideEffects: temp = , result: new ReadOnlySpan(in temp) }` - else if (wrappedArg is BoundSequence { Locals.Length: 0, SideEffects: [BoundAssignmentOperator { Right: var assignmentRight }], Value: BoundObjectCreationExpression { Constructor: var objectCreationConstructor, Arguments: [BoundLocal] } } && - (object)objectCreationConstructor.OriginalDefinition == _compilation.GetWellKnownTypeMember(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference) && - objectCreationConstructor.ReceiverType.IsReadOnlySpanChar()) - { - Debug.Assert(assignmentRight.Type?.IsCharType() == true); - var charToString = FindSpecificToStringOfStructType(assignmentRight.Type, UnsafeGetSpecialTypeMethod(wrappedArg.Syntax, SpecialMember.System_Object__ToString)); - if (charToString is null) - { - unwrappedArgsBuilder.Free(); - arguments = default; - return false; - } - unwrappedArgsBuilder.Add(BoundCall.Synthesized(wrappedArg.Syntax, assignmentRight, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, charToString)); - } - else - { - unwrappedArgsBuilder.Free(); - arguments = default; - return false; - } - } - - arguments = unwrappedArgsBuilder.ToImmutableAndFree(); - return true; - } } break; @@ -261,8 +214,72 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr break; - case BoundSequence { SideEffects.Length: 0, Value: BoundCall sequenceCall }: - return TryExtractStringConcatArgs(sequenceCall, out arguments); + case BoundSequence { SideEffects.Length: 0, Value: BoundCall sequenceCall } sequence: + var locals = sequence.Locals; + + if ((object)sequenceCall.Method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_2ReadOnlySpans) || + (object)sequenceCall.Method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_3ReadOnlySpans) || + (object)sequenceCall.Method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_4ReadOnlySpans)) + { + // Faced a span-based `string.Concat` call. Since we can produce such call on the previous iterations ourselves, we need to unwrap it. + // The key thing is that we need not to only extract arguments, but also unwrap them from being spans and for chars also wrap them into `ToString` calls. + var wrappedArgs = sequenceCall.Arguments; + var unwrappedArgsBuilder = ArrayBuilder.GetInstance(capacity: wrappedArgs.Length); + + for (var i = 0; i < wrappedArgs.Length; i++) + { + var wrappedArg = wrappedArgs[i]; + + // Check whether a call is an implicit `string -> ReadOnlySpan` conversion + if (wrappedArg is BoundCall { Method: var argMethod, Arguments: [var singleArgument] } && + (object)argMethod == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)) + { + unwrappedArgsBuilder.Add(singleArgument); + } + // This complicated check is for a sequence, which wraps a span around single char. + // The sequence needs to have this shape: `{ locals: , sideEffects: temp = , result: new ReadOnlySpan(in temp) }` + else if (wrappedArg is BoundSequence + { + Locals.Length: 0, + SideEffects: [BoundAssignmentOperator { Right.Type.SpecialType: SpecialType.System_Char } assignment], + Value: BoundObjectCreationExpression { Constructor: var objectCreationConstructor, Arguments: [BoundLocal constructorLocal] } + } && + constructorLocal == assignment.Left && + locals.Contains(constructorLocal.LocalSymbol) && + (object)objectCreationConstructor.OriginalDefinition == _compilation.GetWellKnownTypeMember(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference) && + objectCreationConstructor.ReceiverType.IsReadOnlySpanChar()) + { + Debug.Assert(assignment.Right.Type?.IsCharType() == true); + var charToString = FindSpecificToStringOfStructType(assignment.Right.Type, UnsafeGetSpecialTypeMethod(wrappedArg.Syntax, SpecialMember.System_Object__ToString)); + if (charToString is null) + { + unwrappedArgsBuilder.Free(); + arguments = default; + return false; + } + unwrappedArgsBuilder.Add(BoundCall.Synthesized(wrappedArg.Syntax, assignment.Right, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, charToString)); + locals = locals.Remove(constructorLocal.LocalSymbol); + } + else + { + unwrappedArgsBuilder.Free(); + arguments = default; + return false; + } + } + + if (locals.Length > 0) + { + // Not all locals are part of a known shape + arguments = default; + return false; + } + + arguments = unwrappedArgsBuilder.ToImmutableAndFree(); + return true; + } + + break; } arguments = default; From 54765fa9b7a4724e2d9fc6c8c9acc35685797c42 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Tue, 6 Feb 2024 19:48:36 +0300 Subject: [PATCH 25/71] Early return --- .../Lowering/LocalRewriter/LocalRewriter_StringConcat.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index c606110f039c6..84177ffa16bab 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -425,6 +425,13 @@ private BoundExpression RewriteStringConcatenationManyExprs(SyntaxNode syntax, I private bool TryRewriteStringConcatenationWithSpanBasedConcat(SyntaxNode syntax, ArrayBuilder args, [NotNullWhen(true)] out BoundExpression? result) { + // There are span-based `string.Concat` overloads only for 2, 3 or 4 arguments + if (args.Count is not (>= 2 and <= 4)) + { + result = null; + return false; + } + var preparedArgs = ArrayBuilder.GetInstance(capacity: args.Count); var needsSpanRefParamConstructor = false; From 199862e43139c31a901d4cb0aa9704dc833ae84a Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Tue, 6 Feb 2024 19:57:59 +0300 Subject: [PATCH 26/71] Still produce span-based concat when `char` doesn't directly override `object.ToString()` --- .../LocalRewriter_StringConcat.cs | 1 - .../CodeGenSpanBasedStringConcatTests.cs | 61 +++++++++++-------- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index 84177ffa16bab..ef80b9d29e217 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -444,7 +444,6 @@ private bool TryRewriteStringConcatenationWithSpanBasedConcat(SyntaxNode syntax, Debug.Assert(arg.HasAnyErrors || arg.Type?.IsStringType() == true); if (arg is BoundCall { ReceiverOpt: { Type: NamedTypeSymbol { SpecialType: SpecialType.System_Char } receiverCharType } receiver } potentialToStringCall && - (object)potentialToStringCall.Method != _compilation.GetSpecialTypeMember(SpecialMember.System_Object__ToString) && (object)potentialToStringCall.Method.GetLeastOverriddenMethod(charType) == _compilation.GetSpecialTypeMember(SpecialMember.System_Object__ToString)) { needsSpanRefParamConstructor = true; diff --git a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs index 41e9cc518931b..2bd235bfd2910 100644 --- a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs @@ -600,18 +600,20 @@ public class Test var verifier = CompileAndVerify(compilation: comp, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); verifier.VerifyDiagnostics(); - // Even though we have all other members for span-based concatenation, `char` doesn't override `ToString` - // thus we cannot rely on its well-known behavior and emit string-based concat with a virtual `object.ToString()` call + // No matter whether `char` directly overrides `ToString` or not, we still produce span-based concat if we can verifier.VerifyIL("Test.M", """ { - // Code size 20 (0x14) + // Code size 21 (0x15) .maxstack 2 + .locals init (char V_0) IL_0000: ldarg.0 - IL_0001: ldarga.s V_1 - IL_0003: constrained. "char" - IL_0009: callvirt "string object.ToString()" - IL_000e: call "string string.Concat(string, string)" - IL_0013: ret + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.1 + IL_0007: stloc.0 + IL_0008: ldloca.s V_0 + IL_000a: newobj "System.ReadOnlySpan..ctor(ref readonly char)" + IL_000f: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0014: ret } """); } @@ -1709,19 +1711,22 @@ public class Test var verifier = CompileAndVerify(compilation: comp, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); verifier.VerifyDiagnostics(); - // Even though we have all other members for span-based concatenation, `char` doesn't override `ToString` - // thus we cannot rely on its well-known behavior and emit string-based concat with a virtual `object.ToString()` call + // No matter whether `char` directly overrides `ToString` or not, we still produce span-based concat if we can verifier.VerifyIL("Test.M", """ { - // Code size 21 (0x15) + // Code size 27 (0x1b) .maxstack 3 + .locals init (char V_0) IL_0000: ldarg.0 - IL_0001: ldarga.s V_1 - IL_0003: constrained. "char" - IL_0009: callvirt "string object.ToString()" - IL_000e: ldarg.0 - IL_000f: call "string string.Concat(string, string, string)" - IL_0014: ret + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.1 + IL_0007: stloc.0 + IL_0008: ldloca.s V_0 + IL_000a: newobj "System.ReadOnlySpan..ctor(ref readonly char)" + IL_000f: ldarg.0 + IL_0010: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0015: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001a: ret } """); } @@ -3829,20 +3834,24 @@ public class Test var verifier = CompileAndVerify(compilation: comp, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); verifier.VerifyDiagnostics(); - // Even though we have all other members for span-based concatenation, `char` doesn't override `ToString` - // thus we cannot rely on its well-known behavior and emit string-based concat with a virtual `object.ToString()` call + // No matter whether `char` directly overrides `ToString` or not, we still produce span-based concat if we can verifier.VerifyIL("Test.M", """ { - // Code size 22 (0x16) + // Code size 33 (0x21) .maxstack 4 + .locals init (char V_0) IL_0000: ldarg.0 - IL_0001: ldarga.s V_1 - IL_0003: constrained. "char" - IL_0009: callvirt "string object.ToString()" - IL_000e: ldarg.0 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.1 + IL_0007: stloc.0 + IL_0008: ldloca.s V_0 + IL_000a: newobj "System.ReadOnlySpan..ctor(ref readonly char)" IL_000f: ldarg.0 - IL_0010: call "string string.Concat(string, string, string, string)" - IL_0015: ret + IL_0010: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0015: ldarg.0 + IL_0016: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_001b: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0020: ret } """); } From 91888a38151cdaf04573a8e7a734aa883d79935a Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Tue, 6 Feb 2024 20:00:37 +0300 Subject: [PATCH 27/71] Throw unreachable exception --- .../Lowering/LocalRewriter/LocalRewriter_StringConcat.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index ef80b9d29e217..b57de44c3a4c2 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -456,20 +456,19 @@ private bool TryRewriteStringConcatenationWithSpanBasedConcat(SyntaxNode syntax, needsImplicitConversionFromStringToSpan = true; } - WellKnownMember? concatMember = preparedArgs.Count switch + var concatMember = preparedArgs.Count switch { 2 => WellKnownMember.System_String__Concat_2ReadOnlySpans, 3 => WellKnownMember.System_String__Concat_3ReadOnlySpans, 4 => WellKnownMember.System_String__Concat_4ReadOnlySpans, - _ => null, + _ => throw ExceptionUtilities.Unreachable(), }; // It only makes sense to lower using span-based concat if at least one operand is a char. // Because otherwise we will just wrap every string operand into span conversion and use span-based concat // which is unnecessary IL bloat. Thus we require `needsSpanRefParamConstructor` to be true if (needsSpanRefParamConstructor && - concatMember.HasValue && - TryGetWellKnownTypeMember(syntax, concatMember.Value, out MethodSymbol? spanConcat, isOptional: true) && + TryGetWellKnownTypeMember(syntax, concatMember, out MethodSymbol? spanConcat, isOptional: true) && tryGetNeededToSpanMembers(this, syntax, needsSpanRefParamConstructor, needsImplicitConversionFromStringToSpan, charType, out MethodSymbol? readOnlySpanCtorRefParamChar, out MethodSymbol? stringImplicitConversionToReadOnlySpan)) { result = rewriteStringConcatenationWithSpanBasedConcat( From 50bf4e24ca020b3be095288e6bc615330e896233 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Tue, 6 Feb 2024 20:06:30 +0300 Subject: [PATCH 28/71] Simplify --- .../LocalRewriter_StringConcat.cs | 50 +++++++++++-------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index b57de44c3a4c2..a3ace62987329 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -456,6 +456,18 @@ private bool TryRewriteStringConcatenationWithSpanBasedConcat(SyntaxNode syntax, needsImplicitConversionFromStringToSpan = true; } + // It only makes sense to lower using span-based concat if at least one operand is a char. + // Because otherwise we will just wrap every string operand into span conversion and use span-based concat + // which is unnecessary IL bloat. Thus we require `needsSpanRefParamConstructor` to be true + if (!needsSpanRefParamConstructor) + { + result = null; + return false; + } + + // Just direct consequence of a condition above since we capture `char` type and set `needsSpanRefParamConstructor` at the same time + Debug.Assert(charType is not null); + var concatMember = preparedArgs.Count switch { 2 => WellKnownMember.System_String__Concat_2ReadOnlySpans, @@ -464,12 +476,8 @@ private bool TryRewriteStringConcatenationWithSpanBasedConcat(SyntaxNode syntax, _ => throw ExceptionUtilities.Unreachable(), }; - // It only makes sense to lower using span-based concat if at least one operand is a char. - // Because otherwise we will just wrap every string operand into span conversion and use span-based concat - // which is unnecessary IL bloat. Thus we require `needsSpanRefParamConstructor` to be true - if (needsSpanRefParamConstructor && - TryGetWellKnownTypeMember(syntax, concatMember, out MethodSymbol? spanConcat, isOptional: true) && - tryGetNeededToSpanMembers(this, syntax, needsSpanRefParamConstructor, needsImplicitConversionFromStringToSpan, charType, out MethodSymbol? readOnlySpanCtorRefParamChar, out MethodSymbol? stringImplicitConversionToReadOnlySpan)) + if (TryGetWellKnownTypeMember(syntax, concatMember, out MethodSymbol? spanConcat, isOptional: true) && + tryGetNeededToSpanMembers(this, syntax, needsImplicitConversionFromStringToSpan, charType, out MethodSymbol? readOnlySpanCtorRefParamChar, out MethodSymbol? stringImplicitConversionToReadOnlySpan)) { result = rewriteStringConcatenationWithSpanBasedConcat( syntax, @@ -485,23 +493,25 @@ private bool TryRewriteStringConcatenationWithSpanBasedConcat(SyntaxNode syntax, result = null; return false; - static bool tryGetNeededToSpanMembers(LocalRewriter self, SyntaxNode syntax, bool needsSpanRefParamConstructor, bool needsImplicitConversionFromStringToSpan, NamedTypeSymbol? charType, out MethodSymbol? readOnlySpanCtorRefParamChar, out MethodSymbol? stringImplicitConversionToReadOnlySpan) + static bool tryGetNeededToSpanMembers( + LocalRewriter self, + SyntaxNode syntax, + bool needsImplicitConversionFromStringToSpan, + NamedTypeSymbol charType, + [NotNullWhen(true)] out MethodSymbol? readOnlySpanCtorRefParamChar, + out MethodSymbol? stringImplicitConversionToReadOnlySpan) { readOnlySpanCtorRefParamChar = null; stringImplicitConversionToReadOnlySpan = null; - if (needsSpanRefParamConstructor) + if (self.TryGetWellKnownTypeMember(syntax, WellKnownMember.System_ReadOnlySpan_T__ctor_Reference, out MethodSymbol? readOnlySpanCtorRefParamGeneric, isOptional: true)) { - if (self.TryGetWellKnownTypeMember(syntax, WellKnownMember.System_ReadOnlySpan_T__ctor_Reference, out MethodSymbol? readOnlySpanCtorRefParamGeneric, isOptional: true)) - { - Debug.Assert(charType is not null); - var readOnlySpanOfChar = readOnlySpanCtorRefParamGeneric.ContainingType.Construct(charType); - readOnlySpanCtorRefParamChar = readOnlySpanCtorRefParamGeneric.AsMember(readOnlySpanOfChar); - } - else - { - return false; - } + var readOnlySpanOfChar = readOnlySpanCtorRefParamGeneric.ContainingType.Construct(charType); + readOnlySpanCtorRefParamChar = readOnlySpanCtorRefParamGeneric.AsMember(readOnlySpanOfChar); + } + else + { + return false; } if (needsImplicitConversionFromStringToSpan) @@ -517,7 +527,7 @@ static BoundExpression rewriteStringConcatenationWithSpanBasedConcat( SyntheticBoundNodeFactory factory, MethodSymbol spanConcat, MethodSymbol? stringImplicitConversionToReadOnlySpan, - MethodSymbol? readOnlySpanCtorRefParamChar, + MethodSymbol readOnlySpanCtorRefParamChar, ImmutableArray args) { var preparedArgsBuilder = ArrayBuilder.GetInstance(capacity: args.Length); @@ -529,8 +539,6 @@ static BoundExpression rewriteStringConcatenationWithSpanBasedConcat( if (arg.Type.SpecialType == SpecialType.System_Char) { - Debug.Assert(readOnlySpanCtorRefParamChar is not null); - var temp = factory.StoreToTemp(arg, out var tempAssignment); localsBuilder.Add(temp.LocalSymbol); From 0f6302d86b9a464e9a3bc9f3611579c02e6f534a Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Tue, 6 Feb 2024 20:06:45 +0300 Subject: [PATCH 29/71] Drop --- .../Lowering/LocalRewriter/LocalRewriter_StringConcat.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index a3ace62987329..d9cee594caaf5 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -560,8 +560,7 @@ static BoundExpression rewriteStringConcatenationWithSpanBasedConcat( [], [tempAssignment], wrappedChar, - wrappedChar.Type) - { WasCompilerGenerated = true }); + wrappedChar.Type)); } else { From db25bda110a7c9da7dcf3b8c344bfa072a18d7ff Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Tue, 6 Feb 2024 20:12:54 +0300 Subject: [PATCH 30/71] Simplify --- .../LocalRewriter/LocalRewriter_StringConcat.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index d9cee594caaf5..d10bfc14078eb 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -250,14 +250,8 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr objectCreationConstructor.ReceiverType.IsReadOnlySpanChar()) { Debug.Assert(assignment.Right.Type?.IsCharType() == true); - var charToString = FindSpecificToStringOfStructType(assignment.Right.Type, UnsafeGetSpecialTypeMethod(wrappedArg.Syntax, SpecialMember.System_Object__ToString)); - if (charToString is null) - { - unwrappedArgsBuilder.Free(); - arguments = default; - return false; - } - unwrappedArgsBuilder.Add(BoundCall.Synthesized(wrappedArg.Syntax, assignment.Right, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, charToString)); + var wrappedExpr = ConvertConcatExprToString(assignment.Right); + unwrappedArgsBuilder.Add(wrappedExpr); locals = locals.Remove(constructorLocal.LocalSymbol); } else From 53c529f352acc3c0e070c8aff2ed937270f1b6ba Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Tue, 6 Feb 2024 20:13:54 +0300 Subject: [PATCH 31/71] Inline helper back --- .../LocalRewriter_StringConcat.cs | 35 ++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index d10bfc14078eb..cd65e09da6ab7 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -652,7 +652,21 @@ private BoundExpression ConvertConcatExprToString(BoundExpression expr) // If it's a struct which has overridden ToString, find that method. Note that we might fail to // find it, e.g. if object.ToString is missing - MethodSymbol? structToStringMethod = FindSpecificToStringOfStructType(expr.Type, objectToStringMethod); + MethodSymbol? structToStringMethod = null; + if (expr.Type.IsValueType && !expr.Type.IsTypeParameter()) + { + var type = (NamedTypeSymbol)expr.Type; + var typeToStringMembers = type.GetMembers(objectToStringMethod.Name); + foreach (var member in typeToStringMembers) + { + if (member is MethodSymbol toStringMethod && + toStringMethod.GetLeastOverriddenMethod(type) == (object)objectToStringMethod) + { + structToStringMethod = toStringMethod; + break; + } + } + } // If it's a special value type (and not a field of a MarshalByRef object), it should have its own ToString method (but we might fail to find // it if object.ToString is missing). Assume that this won't be removed, and emit a direct call rather @@ -735,24 +749,5 @@ static bool isFieldOfMarshalByRef(BoundExpression expr, CSharpCompilation compil return false; } } - - private static MethodSymbol? FindSpecificToStringOfStructType(TypeSymbol exprType, MethodSymbol objectToStringMethod) - { - if (exprType.IsValueType && !exprType.IsTypeParameter()) - { - var type = (NamedTypeSymbol)exprType; - var typeToStringMembers = type.GetMembers(objectToStringMethod.Name); - foreach (var member in typeToStringMembers) - { - if (member is MethodSymbol toStringMethod && - toStringMethod.GetLeastOverriddenMethod(type) == (object)objectToStringMethod) - { - return toStringMethod; - } - } - } - - return null; - } } } From 201fd04b5cacab4a30d229caa10e20b7051baf46 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Wed, 7 Feb 2024 21:59:14 +0300 Subject: [PATCH 32/71] Use pooled hash set --- .../Lowering/LocalRewriter/LocalRewriter_StringConcat.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index cd65e09da6ab7..55fe8f182e69f 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -215,7 +215,8 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr break; case BoundSequence { SideEffects.Length: 0, Value: BoundCall sequenceCall } sequence: - var locals = sequence.Locals; + var locals = PooledHashSet.GetInstance(); + locals.AddAll(sequence.Locals); if ((object)sequenceCall.Method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_2ReadOnlySpans) || (object)sequenceCall.Method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_3ReadOnlySpans) || @@ -245,14 +246,13 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr Value: BoundObjectCreationExpression { Constructor: var objectCreationConstructor, Arguments: [BoundLocal constructorLocal] } } && constructorLocal == assignment.Left && - locals.Contains(constructorLocal.LocalSymbol) && + locals.Remove(constructorLocal.LocalSymbol) && (object)objectCreationConstructor.OriginalDefinition == _compilation.GetWellKnownTypeMember(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference) && objectCreationConstructor.ReceiverType.IsReadOnlySpanChar()) { Debug.Assert(assignment.Right.Type?.IsCharType() == true); var wrappedExpr = ConvertConcatExprToString(assignment.Right); unwrappedArgsBuilder.Add(wrappedExpr); - locals = locals.Remove(constructorLocal.LocalSymbol); } else { @@ -262,7 +262,7 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr } } - if (locals.Length > 0) + if (locals.Count > 0) { // Not all locals are part of a known shape arguments = default; From 49a865f116c14f09943e444c007c03706c763b46 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Wed, 7 Feb 2024 22:03:44 +0300 Subject: [PATCH 33/71] Use switch statement --- .../LocalRewriter_StringConcat.cs | 51 +++++++++---------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index 55fe8f182e69f..b8bcf0f112f7a 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -231,34 +231,31 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr { var wrappedArg = wrappedArgs[i]; - // Check whether a call is an implicit `string -> ReadOnlySpan` conversion - if (wrappedArg is BoundCall { Method: var argMethod, Arguments: [var singleArgument] } && - (object)argMethod == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)) + switch (wrappedArg) { - unwrappedArgsBuilder.Add(singleArgument); - } - // This complicated check is for a sequence, which wraps a span around single char. - // The sequence needs to have this shape: `{ locals: , sideEffects: temp = , result: new ReadOnlySpan(in temp) }` - else if (wrappedArg is BoundSequence - { - Locals.Length: 0, - SideEffects: [BoundAssignmentOperator { Right.Type.SpecialType: SpecialType.System_Char } assignment], - Value: BoundObjectCreationExpression { Constructor: var objectCreationConstructor, Arguments: [BoundLocal constructorLocal] } - } && - constructorLocal == assignment.Left && - locals.Remove(constructorLocal.LocalSymbol) && - (object)objectCreationConstructor.OriginalDefinition == _compilation.GetWellKnownTypeMember(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference) && - objectCreationConstructor.ReceiverType.IsReadOnlySpanChar()) - { - Debug.Assert(assignment.Right.Type?.IsCharType() == true); - var wrappedExpr = ConvertConcatExprToString(assignment.Right); - unwrappedArgsBuilder.Add(wrappedExpr); - } - else - { - unwrappedArgsBuilder.Free(); - arguments = default; - return false; + // Check whether a call is an implicit `string -> ReadOnlySpan` conversion + case BoundCall { Method: var argMethod, Arguments: [var singleArgument] } when (object)argMethod == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar): + unwrappedArgsBuilder.Add(singleArgument); + break; + // This complicated check is for a sequence, which wraps a span around single char. + // The sequence needs to have this shape: `{ locals: , sideEffects: temp = , result: new ReadOnlySpan(in temp) }` + case BoundSequence + { + Locals.Length: 0, + SideEffects: [BoundAssignmentOperator { Right.Type.SpecialType: SpecialType.System_Char } assignment], + Value: BoundObjectCreationExpression { Constructor: var objectCreationConstructor, Arguments: [BoundLocal constructorLocal] } + } when constructorLocal == assignment.Left && + locals.Remove(constructorLocal.LocalSymbol) && + (object)objectCreationConstructor.OriginalDefinition == _compilation.GetWellKnownTypeMember(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference) && + objectCreationConstructor.ReceiverType.IsReadOnlySpanChar(): + Debug.Assert(assignment.Right.Type?.IsCharType() == true); + var wrappedExpr = ConvertConcatExprToString(assignment.Right); + unwrappedArgsBuilder.Add(wrappedExpr); + break; + default: + unwrappedArgsBuilder.Free(); + arguments = default; + return false; } } From a3a58a6eb4994d011fc50f6ddccb8a6e232c74d1 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Wed, 7 Feb 2024 22:12:09 +0300 Subject: [PATCH 34/71] Use containing type --- .../Lowering/LocalRewriter/LocalRewriter_StringConcat.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index b8bcf0f112f7a..f7ce8c12596b7 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -247,7 +247,7 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr } when constructorLocal == assignment.Left && locals.Remove(constructorLocal.LocalSymbol) && (object)objectCreationConstructor.OriginalDefinition == _compilation.GetWellKnownTypeMember(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference) && - objectCreationConstructor.ReceiverType.IsReadOnlySpanChar(): + objectCreationConstructor.ContainingType.IsReadOnlySpanChar(): Debug.Assert(assignment.Right.Type?.IsCharType() == true); var wrappedExpr = ConvertConcatExprToString(assignment.Right); unwrappedArgsBuilder.Add(wrappedExpr); From 1d3447c86deb95b19e0ec99ce89d4e2670750362 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Wed, 7 Feb 2024 22:20:56 +0300 Subject: [PATCH 35/71] Assert --- .../Lowering/LocalRewriter/LocalRewriter_StringConcat.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index f7ce8c12596b7..4e03406b17a01 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -416,12 +416,8 @@ private BoundExpression RewriteStringConcatenationManyExprs(SyntaxNode syntax, I private bool TryRewriteStringConcatenationWithSpanBasedConcat(SyntaxNode syntax, ArrayBuilder args, [NotNullWhen(true)] out BoundExpression? result) { - // There are span-based `string.Concat` overloads only for 2, 3 or 4 arguments - if (args.Count is not (>= 2 and <= 4)) - { - result = null; - return false; - } + // We should have called this only for 2, 3 or 4 arguments + Debug.Assert(args.Count is >= 2 and <= 4); var preparedArgs = ArrayBuilder.GetInstance(capacity: args.Count); From 3634b2fb8bfcd71021fe3d54ff3b65c8f7001ebd Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Thu, 8 Feb 2024 12:38:28 +0300 Subject: [PATCH 36/71] Convert to `foreach` --- .../Lowering/LocalRewriter/LocalRewriter_StringConcat.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index 4e03406b17a01..afd38acec04eb 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -227,10 +227,8 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr var wrappedArgs = sequenceCall.Arguments; var unwrappedArgsBuilder = ArrayBuilder.GetInstance(capacity: wrappedArgs.Length); - for (var i = 0; i < wrappedArgs.Length; i++) + foreach (var wrappedArg in wrappedArgs) { - var wrappedArg = wrappedArgs[i]; - switch (wrappedArg) { // Check whether a call is an implicit `string -> ReadOnlySpan` conversion From 0b048b8542f463ea3179b2141b06026729224711 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Thu, 8 Feb 2024 12:39:20 +0300 Subject: [PATCH 37/71] Move into `if` --- .../Lowering/LocalRewriter/LocalRewriter_StringConcat.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index afd38acec04eb..a2fb20e4a34d9 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -215,9 +215,6 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr break; case BoundSequence { SideEffects.Length: 0, Value: BoundCall sequenceCall } sequence: - var locals = PooledHashSet.GetInstance(); - locals.AddAll(sequence.Locals); - if ((object)sequenceCall.Method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_2ReadOnlySpans) || (object)sequenceCall.Method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_3ReadOnlySpans) || (object)sequenceCall.Method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_4ReadOnlySpans)) @@ -227,6 +224,9 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr var wrappedArgs = sequenceCall.Arguments; var unwrappedArgsBuilder = ArrayBuilder.GetInstance(capacity: wrappedArgs.Length); + var locals = PooledHashSet.GetInstance(); + locals.AddAll(sequence.Locals); + foreach (var wrappedArg in wrappedArgs) { switch (wrappedArg) From a384b977f1dfa6f9131676bc0979510647eccd6b Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Thu, 8 Feb 2024 12:41:13 +0300 Subject: [PATCH 38/71] Free pooled object --- .../Lowering/LocalRewriter/LocalRewriter_StringConcat.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index a2fb20e4a34d9..3e1de1646ff12 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -251,6 +251,7 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr unwrappedArgsBuilder.Add(wrappedExpr); break; default: + locals.Free(); unwrappedArgsBuilder.Free(); arguments = default; return false; @@ -260,10 +261,12 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr if (locals.Count > 0) { // Not all locals are part of a known shape + locals.Free(); arguments = default; return false; } + locals.Free(); arguments = unwrappedArgsBuilder.ToImmutableAndFree(); return true; } From 05a2e3c3e5ea45b064dd76f78cf5eddae6ead6ad Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Thu, 8 Feb 2024 12:49:28 +0300 Subject: [PATCH 39/71] Remove assert --- .../Lowering/LocalRewriter/LocalRewriter_StringConcat.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index 3e1de1646ff12..18d989c30145b 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -246,7 +246,6 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr locals.Remove(constructorLocal.LocalSymbol) && (object)objectCreationConstructor.OriginalDefinition == _compilation.GetWellKnownTypeMember(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference) && objectCreationConstructor.ContainingType.IsReadOnlySpanChar(): - Debug.Assert(assignment.Right.Type?.IsCharType() == true); var wrappedExpr = ConvertConcatExprToString(assignment.Right); unwrappedArgsBuilder.Add(wrappedExpr); break; From 71ea57d2b1ec3a3b41b62ed4b54e9e3c825855ca Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Thu, 8 Feb 2024 12:50:11 +0300 Subject: [PATCH 40/71] Free --- .../Lowering/LocalRewriter/LocalRewriter_StringConcat.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index 18d989c30145b..35e6dd7f1627f 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -261,6 +261,7 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr { // Not all locals are part of a known shape locals.Free(); + unwrappedArgsBuilder.Free(); arguments = default; return false; } From 0cc529d9649602fb2e5c3a4ce12b7ebff125af5e Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Thu, 8 Feb 2024 13:22:56 +0300 Subject: [PATCH 41/71] Handle ROS constructor ref kinds other than `ref readonly` or `in` --- .../LocalRewriter_StringConcat.cs | 7 +- .../CodeGenSpanBasedStringConcatTests.cs | 450 ++++++++++++++++++ 2 files changed, 455 insertions(+), 2 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index 35e6dd7f1627f..8227b25c1d90a 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -492,7 +492,8 @@ static bool tryGetNeededToSpanMembers( readOnlySpanCtorRefParamChar = null; stringImplicitConversionToReadOnlySpan = null; - if (self.TryGetWellKnownTypeMember(syntax, WellKnownMember.System_ReadOnlySpan_T__ctor_Reference, out MethodSymbol? readOnlySpanCtorRefParamGeneric, isOptional: true)) + if (self.TryGetWellKnownTypeMember(syntax, WellKnownMember.System_ReadOnlySpan_T__ctor_Reference, out MethodSymbol? readOnlySpanCtorRefParamGeneric, isOptional: true) && + readOnlySpanCtorRefParamGeneric.Parameters[0].RefKind != RefKind.Out) { var readOnlySpanOfChar = readOnlySpanCtorRefParamGeneric.ContainingType.Construct(charType); readOnlySpanCtorRefParamChar = readOnlySpanCtorRefParamGeneric.AsMember(readOnlySpanOfChar); @@ -530,12 +531,14 @@ static BoundExpression rewriteStringConcatenationWithSpanBasedConcat( var temp = factory.StoreToTemp(arg, out var tempAssignment); localsBuilder.Add(temp.LocalSymbol); + Debug.Assert(readOnlySpanCtorRefParamChar.Parameters[0].RefKind != RefKind.Out); + var wrappedChar = new BoundObjectCreationExpression( arg.Syntax, readOnlySpanCtorRefParamChar, [temp], argumentNamesOpt: default, - argumentRefKindsOpt: [RefKindExtensions.StrictIn], + argumentRefKindsOpt: [readOnlySpanCtorRefParamChar.Parameters[0].RefKind == RefKind.Ref ? RefKind.Ref : RefKindExtensions.StrictIn], expanded: false, argsToParamsOpt: default, defaultArguments: default, diff --git a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs index 2bd235bfd2910..c76d333de6845 100644 --- a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs @@ -618,6 +618,151 @@ .locals init (char V_0) """); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatTwo_ReadOnlySpanConstructorParameterIsOrdinaryRef() + { + var corlib_cs = """ + namespace System + { + public class Object + { + public virtual string ToString() => null; + } + public class String + { + public static string Concat(string str0, string str1) => null; + public static string Concat(ReadOnlySpan str0, ReadOnlySpan str1) => null; + public static implicit operator ReadOnlySpan(string value) => default; + } + public class ValueType { } + public struct Char + { + public override string ToString() => null; + } + public struct Void { } + public struct Int32 { } + public struct Byte { } + public struct Boolean { } + public struct ReadOnlySpan + { + public ReadOnlySpan(ref T reference) { } + } + public class Enum : ValueType { } + public class Attribute { } + public enum AttributeTargets { } + public class AttributeUsageAttribute : Attribute + { + public AttributeUsageAttribute(AttributeTargets validOn) { } + + public bool AllowMultiple { get { return default; } set { } } + public bool Inherited { get { return default; } set { } } + } + } + """; + + var corlib = CreateEmptyCompilation(corlib_cs).EmitToImageReference(); + + var source = """ + public class Test + { + static string M(string s, char c) => s + c; + } + """; + + var comp = CreateEmptyCompilation(source, [corlib]); + comp.VerifyDiagnostics(); + + var verifier = CompileAndVerify(compilation: comp, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Test.M", """ + { + // Code size 21 (0x15) + .maxstack 2 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.1 + IL_0007: stloc.0 + IL_0008: ldloca.s V_0 + IL_000a: newobj "System.ReadOnlySpan..ctor(ref char)" + IL_000f: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0014: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatTwo_ReadOnlySpanConstructorParameterIsOut() + { + var corlib_cs = """ + namespace System + { + public class Object + { + public virtual string ToString() => null; + } + public class String + { + public static string Concat(string str0, string str1) => null; + public static string Concat(ReadOnlySpan str0, ReadOnlySpan str1) => null; + public static implicit operator ReadOnlySpan(string value) => default; + } + public class ValueType { } + public struct Char + { + public override string ToString() => null; + } + public struct Void { } + public struct Int32 { } + public struct Byte { } + public struct Boolean { } + public struct ReadOnlySpan + { + public ReadOnlySpan(out T reference) { reference = default; } + } + public class Enum : ValueType { } + public class Attribute { } + public enum AttributeTargets { } + public class AttributeUsageAttribute : Attribute + { + public AttributeUsageAttribute(AttributeTargets validOn) { } + + public bool AllowMultiple { get { return default; } set { } } + public bool Inherited { get { return default; } set { } } + } + } + """; + + var corlib = CreateEmptyCompilation(corlib_cs).EmitToImageReference(); + + var source = """ + public class Test + { + static string M(string s, char c) => s + c; + } + """; + + var comp = CreateEmptyCompilation(source, [corlib]); + comp.VerifyDiagnostics(); + + var verifier = CompileAndVerify(compilation: comp, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + verifier.VerifyDiagnostics(); + + // Constructor of ReadOnlySpan has unexpected `out` reference. Fallback to string-based concat + verifier.VerifyIL("Test.M", """ + { + // Code size 14 (0xe) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldarga.s V_1 + IL_0003: call "string char.ToString()" + IL_0008: call "string string.Concat(string, string)" + IL_000d: ret + } + """); + } + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] @@ -1731,6 +1876,156 @@ .locals init (char V_0) """); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatThree_ReadOnlySpanConstructorParameterIsOrdinaryRef() + { + var corlib_cs = """ + namespace System + { + public class Object + { + public virtual string ToString() => null; + } + public class String + { + public static string Concat(string str0, string str1) => null; + public static string Concat(string str0, string str1, string str2) => null; + public static string Concat(ReadOnlySpan str0, ReadOnlySpan str1, ReadOnlySpan str2) => null; + public static implicit operator ReadOnlySpan(string value) => default; + } + public class ValueType { } + public struct Char + { + public override string ToString() => null; + } + public struct Void { } + public struct Int32 { } + public struct Byte { } + public struct Boolean { } + public struct ReadOnlySpan + { + public ReadOnlySpan(ref T reference) { } + } + public class Enum : ValueType { } + public class Attribute { } + public enum AttributeTargets { } + public class AttributeUsageAttribute : Attribute + { + public AttributeUsageAttribute(AttributeTargets validOn) { } + + public bool AllowMultiple { get { return default; } set { } } + public bool Inherited { get { return default; } set { } } + } + } + """; + + var corlib = CreateEmptyCompilation(corlib_cs).EmitToImageReference(); + + var source = """ + public class Test + { + static string M(string s, char c) => s + c + s; + } + """; + + var comp = CreateEmptyCompilation(source, [corlib]); + comp.VerifyDiagnostics(); + + var verifier = CompileAndVerify(compilation: comp, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Test.M", """ + { + // Code size 27 (0x1b) + .maxstack 3 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.1 + IL_0007: stloc.0 + IL_0008: ldloca.s V_0 + IL_000a: newobj "System.ReadOnlySpan..ctor(ref char)" + IL_000f: ldarg.0 + IL_0010: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0015: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001a: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatThree_ReadOnlySpanConstructorParameterIsOut() + { + var corlib_cs = """ + namespace System + { + public class Object + { + public virtual string ToString() => null; + } + public class String + { + public static string Concat(string str0, string str1) => null; + public static string Concat(string str0, string str1, string str2) => null; + public static string Concat(ReadOnlySpan str0, ReadOnlySpan str1, ReadOnlySpan str2) => null; + public static implicit operator ReadOnlySpan(string value) => default; + } + public class ValueType { } + public struct Char + { + public override string ToString() => null; + } + public struct Void { } + public struct Int32 { } + public struct Byte { } + public struct Boolean { } + public struct ReadOnlySpan + { + public ReadOnlySpan(out T reference) { reference = default; } + } + public class Enum : ValueType { } + public class Attribute { } + public enum AttributeTargets { } + public class AttributeUsageAttribute : Attribute + { + public AttributeUsageAttribute(AttributeTargets validOn) { } + + public bool AllowMultiple { get { return default; } set { } } + public bool Inherited { get { return default; } set { } } + } + } + """; + + var corlib = CreateEmptyCompilation(corlib_cs).EmitToImageReference(); + + var source = """ + public class Test + { + static string M(string s, char c) => s + c + s; + } + """; + + var comp = CreateEmptyCompilation(source, [corlib]); + comp.VerifyDiagnostics(); + + var verifier = CompileAndVerify(compilation: comp, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + verifier.VerifyDiagnostics(); + + // Constructor of ReadOnlySpan has unexpected `out` reference. Fallback to string-based concat + verifier.VerifyIL("Test.M", """ + { + // Code size 15 (0xf) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: ldarga.s V_1 + IL_0003: call "string char.ToString()" + IL_0008: ldarg.0 + IL_0009: call "string string.Concat(string, string, string)" + IL_000e: ret + } + """); + } + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] @@ -3856,6 +4151,161 @@ .locals init (char V_0) """); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatFour_ReadOnlySpanConstructorParameterIsOrdinaryRef() + { + var corlib_cs = """ + namespace System + { + public class Object + { + public virtual string ToString() => null; + } + public class String + { + public static string Concat(string str0, string str1) => null; + public static string Concat(string str0, string str1, string str2) => null; + public static string Concat(string str0, string str1, string str2, string str3) => null; + public static string Concat(ReadOnlySpan str0, ReadOnlySpan str1, ReadOnlySpan str2, ReadOnlySpan str3) => null; + public static implicit operator ReadOnlySpan(string value) => default; + } + public class ValueType { } + public struct Char + { + public override string ToString() => null; + } + public struct Void { } + public struct Int32 { } + public struct Byte { } + public struct Boolean { } + public struct ReadOnlySpan + { + public ReadOnlySpan(ref T reference) { } + } + public class Enum : ValueType { } + public class Attribute { } + public enum AttributeTargets { } + public class AttributeUsageAttribute : Attribute + { + public AttributeUsageAttribute(AttributeTargets validOn) { } + + public bool AllowMultiple { get { return default; } set { } } + public bool Inherited { get { return default; } set { } } + } + } + """; + + var corlib = CreateEmptyCompilation(corlib_cs).EmitToImageReference(); + + var source = """ + public class Test + { + static string M(string s, char c) => s + c + s + s; + } + """; + + var comp = CreateEmptyCompilation(source, [corlib]); + comp.VerifyDiagnostics(); + + var verifier = CompileAndVerify(compilation: comp, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Test.M", """ + { + // Code size 33 (0x21) + .maxstack 4 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.1 + IL_0007: stloc.0 + IL_0008: ldloca.s V_0 + IL_000a: newobj "System.ReadOnlySpan..ctor(ref char)" + IL_000f: ldarg.0 + IL_0010: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0015: ldarg.0 + IL_0016: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_001b: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0020: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatFour_ReadOnlySpanConstructorParameterIsOut() + { + var corlib_cs = """ + namespace System + { + public class Object + { + public virtual string ToString() => null; + } + public class String + { + public static string Concat(string str0, string str1) => null; + public static string Concat(string str0, string str1, string str2) => null; + public static string Concat(string str0, string str1, string str2, string str3) => null; + public static string Concat(ReadOnlySpan str0, ReadOnlySpan str1, ReadOnlySpan str2, ReadOnlySpan str3) => null; + public static implicit operator ReadOnlySpan(string value) => default; + } + public class ValueType { } + public struct Char + { + public override string ToString() => null; + } + public struct Void { } + public struct Int32 { } + public struct Byte { } + public struct Boolean { } + public struct ReadOnlySpan + { + public ReadOnlySpan(out T reference) { reference = default; } + } + public class Enum : ValueType { } + public class Attribute { } + public enum AttributeTargets { } + public class AttributeUsageAttribute : Attribute + { + public AttributeUsageAttribute(AttributeTargets validOn) { } + + public bool AllowMultiple { get { return default; } set { } } + public bool Inherited { get { return default; } set { } } + } + } + """; + + var corlib = CreateEmptyCompilation(corlib_cs).EmitToImageReference(); + + var source = """ + public class Test + { + static string M(string s, char c) => s + c + s + s; + } + """; + + var comp = CreateEmptyCompilation(source, [corlib]); + comp.VerifyDiagnostics(); + + var verifier = CompileAndVerify(compilation: comp, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + verifier.VerifyDiagnostics(); + + // Constructor of ReadOnlySpan has unexpected `out` reference. Fallback to string-based concat + verifier.VerifyIL("Test.M", """ + { + // Code size 16 (0x10) + .maxstack 4 + IL_0000: ldarg.0 + IL_0001: ldarga.s V_1 + IL_0003: call "string char.ToString()" + IL_0008: ldarg.0 + IL_0009: ldarg.0 + IL_000a: call "string string.Concat(string, string, string, string)" + IL_000f: ret + } + """); + } + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] From 8d3a40beb0c328f77b0f886d8dbd085b55395085 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Mon, 12 Feb 2024 19:30:42 +0300 Subject: [PATCH 42/71] Support `await` scenarios --- .../Portable/Lowering/SpillSequenceSpiller.cs | 19 +- .../CodeGenSpanBasedStringConcatTests.cs | 598 ++++++++++++++++++ 2 files changed, 616 insertions(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs b/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs index 9bfb3a387e2f3..f040a895e86a7 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs @@ -4,7 +4,6 @@ #nullable disable -using System; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; @@ -469,6 +468,24 @@ private BoundExpression Spill( call.Method, ImmutableArray.Create(Spill(builder, call.Arguments[0]), Spill(builder, call.Arguments[1]))); } + else if (call.Method == _F.Compilation.GetWellKnownTypeMember(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)) + { + Debug.Assert(call.Arguments.Length == 1); + return call.Update([Spill(builder, call.Arguments[0])]); + } + + goto default; + + case BoundKind.ObjectCreationExpression: + var objectCreationExpression = (BoundObjectCreationExpression)expression; + + if (objectCreationExpression.Constructor.OriginalDefinition == _F.Compilation.GetWellKnownTypeMember(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)) + { + Debug.Assert(objectCreationExpression.Arguments.Length == 1); + return objectCreationExpression.UpdateArgumentsAndInitializer([Spill(builder, objectCreationExpression.Arguments[0])], + objectCreationExpression.ArgumentRefKindsOpt, + objectCreationExpression.InitializerExpressionOpt); + } goto default; diff --git a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs index c76d333de6845..d20dc3d1e7a11 100644 --- a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs @@ -763,6 +763,164 @@ .maxstack 2 """); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatTwo_Await() + { + var source = """ + using System; + using System.Threading.Tasks; + + public class Test + { + static async Task Main() + { + Console.Write(await M()); + } + + static async Task M() + { + return (await GetStringAsync()) + (await GetCharAsync()); + } + + static async Task GetStringAsync() + { + await Task.Yield(); + return "s"; + } + + static async Task GetCharAsync() + { + await Task.Yield(); + return 'c'; + } + } + """; + + var comp = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "sc" : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.d__1.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", """ + { + // Code size 276 (0x114) + .maxstack 3 + .locals init (int V_0, + string V_1, + char V_2, + System.Runtime.CompilerServices.TaskAwaiter V_3, + System.Runtime.CompilerServices.TaskAwaiter V_4, + System.Exception V_5) + IL_0000: ldarg.0 + IL_0001: ldfld "int Test.d__1.<>1__state" + IL_0006: stloc.0 + .try + { + IL_0007: ldloc.0 + IL_0008: brfalse.s IL_0048 + IL_000a: ldloc.0 + IL_000b: ldc.i4.1 + IL_000c: beq IL_00a7 + IL_0011: call "System.Threading.Tasks.Task Test.GetStringAsync()" + IL_0016: callvirt "System.Runtime.CompilerServices.TaskAwaiter System.Threading.Tasks.Task.GetAwaiter()" + IL_001b: stloc.3 + IL_001c: ldloca.s V_3 + IL_001e: call "bool System.Runtime.CompilerServices.TaskAwaiter.IsCompleted.get" + IL_0023: brtrue.s IL_0064 + IL_0025: ldarg.0 + IL_0026: ldc.i4.0 + IL_0027: dup + IL_0028: stloc.0 + IL_0029: stfld "int Test.d__1.<>1__state" + IL_002e: ldarg.0 + IL_002f: ldloc.3 + IL_0030: stfld "System.Runtime.CompilerServices.TaskAwaiter Test.d__1.<>u__1" + IL_0035: ldarg.0 + IL_0036: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Test.d__1.<>t__builder" + IL_003b: ldloca.s V_3 + IL_003d: ldarg.0 + IL_003e: call "void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AwaitUnsafeOnCompleted, Test.d__1>(ref System.Runtime.CompilerServices.TaskAwaiter, ref Test.d__1)" + IL_0043: leave IL_0113 + IL_0048: ldarg.0 + IL_0049: ldfld "System.Runtime.CompilerServices.TaskAwaiter Test.d__1.<>u__1" + IL_004e: stloc.3 + IL_004f: ldarg.0 + IL_0050: ldflda "System.Runtime.CompilerServices.TaskAwaiter Test.d__1.<>u__1" + IL_0055: initobj "System.Runtime.CompilerServices.TaskAwaiter" + IL_005b: ldarg.0 + IL_005c: ldc.i4.m1 + IL_005d: dup + IL_005e: stloc.0 + IL_005f: stfld "int Test.d__1.<>1__state" + IL_0064: ldarg.0 + IL_0065: ldloca.s V_3 + IL_0067: call "string System.Runtime.CompilerServices.TaskAwaiter.GetResult()" + IL_006c: stfld "string Test.d__1.<>7__wrap1" + IL_0071: call "System.Threading.Tasks.Task Test.GetCharAsync()" + IL_0076: callvirt "System.Runtime.CompilerServices.TaskAwaiter System.Threading.Tasks.Task.GetAwaiter()" + IL_007b: stloc.s V_4 + IL_007d: ldloca.s V_4 + IL_007f: call "bool System.Runtime.CompilerServices.TaskAwaiter.IsCompleted.get" + IL_0084: brtrue.s IL_00c4 + IL_0086: ldarg.0 + IL_0087: ldc.i4.1 + IL_0088: dup + IL_0089: stloc.0 + IL_008a: stfld "int Test.d__1.<>1__state" + IL_008f: ldarg.0 + IL_0090: ldloc.s V_4 + IL_0092: stfld "System.Runtime.CompilerServices.TaskAwaiter Test.d__1.<>u__2" + IL_0097: ldarg.0 + IL_0098: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Test.d__1.<>t__builder" + IL_009d: ldloca.s V_4 + IL_009f: ldarg.0 + IL_00a0: call "void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AwaitUnsafeOnCompleted, Test.d__1>(ref System.Runtime.CompilerServices.TaskAwaiter, ref Test.d__1)" + IL_00a5: leave.s IL_0113 + IL_00a7: ldarg.0 + IL_00a8: ldfld "System.Runtime.CompilerServices.TaskAwaiter Test.d__1.<>u__2" + IL_00ad: stloc.s V_4 + IL_00af: ldarg.0 + IL_00b0: ldflda "System.Runtime.CompilerServices.TaskAwaiter Test.d__1.<>u__2" + IL_00b5: initobj "System.Runtime.CompilerServices.TaskAwaiter" + IL_00bb: ldarg.0 + IL_00bc: ldc.i4.m1 + IL_00bd: dup + IL_00be: stloc.0 + IL_00bf: stfld "int Test.d__1.<>1__state" + IL_00c4: ldloca.s V_4 + IL_00c6: call "char System.Runtime.CompilerServices.TaskAwaiter.GetResult()" + IL_00cb: stloc.2 + IL_00cc: ldarg.0 + IL_00cd: ldfld "string Test.d__1.<>7__wrap1" + IL_00d2: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_00d7: ldloca.s V_2 + IL_00d9: newobj "System.ReadOnlySpan..ctor(in char)" + IL_00de: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_00e3: stloc.1 + IL_00e4: leave.s IL_00ff + } + catch System.Exception + { + IL_00e6: stloc.s V_5 + IL_00e8: ldarg.0 + IL_00e9: ldc.i4.s -2 + IL_00eb: stfld "int Test.d__1.<>1__state" + IL_00f0: ldarg.0 + IL_00f1: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Test.d__1.<>t__builder" + IL_00f6: ldloc.s V_5 + IL_00f8: call "void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetException(System.Exception)" + IL_00fd: leave.s IL_0113 + } + IL_00ff: ldarg.0 + IL_0100: ldc.i4.s -2 + IL_0102: stfld "int Test.d__1.<>1__state" + IL_0107: ldarg.0 + IL_0108: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Test.d__1.<>t__builder" + IL_010d: ldloc.1 + IL_010e: call "void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetResult(string)" + IL_0113: ret + } + """); + } + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] @@ -2026,6 +2184,205 @@ .maxstack 3 """); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatThree_Await() + { + var source = """ + using System; + using System.Threading.Tasks; + + public class Test + { + static async Task Main() + { + Console.Write(await M()); + } + + static async Task M() + { + return (await GetStringAsync()) + (await GetCharAsync()) + (await GetStringAsync()); + } + + static async Task GetStringAsync() + { + await Task.Yield(); + return "s"; + } + + static async Task GetCharAsync() + { + await Task.Yield(); + return 'c'; + } + } + """; + + var comp = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "scs" : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.d__1.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", """ + { + // Code size 398 (0x18e) + .maxstack 3 + .locals init (int V_0, + string V_1, + char V_2, + string V_3, + System.Runtime.CompilerServices.TaskAwaiter V_4, + System.Runtime.CompilerServices.TaskAwaiter V_5, + System.Exception V_6) + IL_0000: ldarg.0 + IL_0001: ldfld "int Test.d__1.<>1__state" + IL_0006: stloc.0 + .try + { + IL_0007: ldloc.0 + IL_0008: switch ( + IL_0052, + IL_00b5, + IL_0117) + IL_0019: call "System.Threading.Tasks.Task Test.GetStringAsync()" + IL_001e: callvirt "System.Runtime.CompilerServices.TaskAwaiter System.Threading.Tasks.Task.GetAwaiter()" + IL_0023: stloc.s V_4 + IL_0025: ldloca.s V_4 + IL_0027: call "bool System.Runtime.CompilerServices.TaskAwaiter.IsCompleted.get" + IL_002c: brtrue.s IL_006f + IL_002e: ldarg.0 + IL_002f: ldc.i4.0 + IL_0030: dup + IL_0031: stloc.0 + IL_0032: stfld "int Test.d__1.<>1__state" + IL_0037: ldarg.0 + IL_0038: ldloc.s V_4 + IL_003a: stfld "System.Runtime.CompilerServices.TaskAwaiter Test.d__1.<>u__1" + IL_003f: ldarg.0 + IL_0040: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Test.d__1.<>t__builder" + IL_0045: ldloca.s V_4 + IL_0047: ldarg.0 + IL_0048: call "void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AwaitUnsafeOnCompleted, Test.d__1>(ref System.Runtime.CompilerServices.TaskAwaiter, ref Test.d__1)" + IL_004d: leave IL_018d + IL_0052: ldarg.0 + IL_0053: ldfld "System.Runtime.CompilerServices.TaskAwaiter Test.d__1.<>u__1" + IL_0058: stloc.s V_4 + IL_005a: ldarg.0 + IL_005b: ldflda "System.Runtime.CompilerServices.TaskAwaiter Test.d__1.<>u__1" + IL_0060: initobj "System.Runtime.CompilerServices.TaskAwaiter" + IL_0066: ldarg.0 + IL_0067: ldc.i4.m1 + IL_0068: dup + IL_0069: stloc.0 + IL_006a: stfld "int Test.d__1.<>1__state" + IL_006f: ldarg.0 + IL_0070: ldloca.s V_4 + IL_0072: call "string System.Runtime.CompilerServices.TaskAwaiter.GetResult()" + IL_0077: stfld "string Test.d__1.<>7__wrap1" + IL_007c: call "System.Threading.Tasks.Task Test.GetCharAsync()" + IL_0081: callvirt "System.Runtime.CompilerServices.TaskAwaiter System.Threading.Tasks.Task.GetAwaiter()" + IL_0086: stloc.s V_5 + IL_0088: ldloca.s V_5 + IL_008a: call "bool System.Runtime.CompilerServices.TaskAwaiter.IsCompleted.get" + IL_008f: brtrue.s IL_00d2 + IL_0091: ldarg.0 + IL_0092: ldc.i4.1 + IL_0093: dup + IL_0094: stloc.0 + IL_0095: stfld "int Test.d__1.<>1__state" + IL_009a: ldarg.0 + IL_009b: ldloc.s V_5 + IL_009d: stfld "System.Runtime.CompilerServices.TaskAwaiter Test.d__1.<>u__2" + IL_00a2: ldarg.0 + IL_00a3: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Test.d__1.<>t__builder" + IL_00a8: ldloca.s V_5 + IL_00aa: ldarg.0 + IL_00ab: call "void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AwaitUnsafeOnCompleted, Test.d__1>(ref System.Runtime.CompilerServices.TaskAwaiter, ref Test.d__1)" + IL_00b0: leave IL_018d + IL_00b5: ldarg.0 + IL_00b6: ldfld "System.Runtime.CompilerServices.TaskAwaiter Test.d__1.<>u__2" + IL_00bb: stloc.s V_5 + IL_00bd: ldarg.0 + IL_00be: ldflda "System.Runtime.CompilerServices.TaskAwaiter Test.d__1.<>u__2" + IL_00c3: initobj "System.Runtime.CompilerServices.TaskAwaiter" + IL_00c9: ldarg.0 + IL_00ca: ldc.i4.m1 + IL_00cb: dup + IL_00cc: stloc.0 + IL_00cd: stfld "int Test.d__1.<>1__state" + IL_00d2: ldloca.s V_5 + IL_00d4: call "char System.Runtime.CompilerServices.TaskAwaiter.GetResult()" + IL_00d9: stloc.2 + IL_00da: ldarg.0 + IL_00db: ldloc.2 + IL_00dc: stfld "char Test.d__1.<>7__wrap2" + IL_00e1: call "System.Threading.Tasks.Task Test.GetStringAsync()" + IL_00e6: callvirt "System.Runtime.CompilerServices.TaskAwaiter System.Threading.Tasks.Task.GetAwaiter()" + IL_00eb: stloc.s V_4 + IL_00ed: ldloca.s V_4 + IL_00ef: call "bool System.Runtime.CompilerServices.TaskAwaiter.IsCompleted.get" + IL_00f4: brtrue.s IL_0134 + IL_00f6: ldarg.0 + IL_00f7: ldc.i4.2 + IL_00f8: dup + IL_00f9: stloc.0 + IL_00fa: stfld "int Test.d__1.<>1__state" + IL_00ff: ldarg.0 + IL_0100: ldloc.s V_4 + IL_0102: stfld "System.Runtime.CompilerServices.TaskAwaiter Test.d__1.<>u__1" + IL_0107: ldarg.0 + IL_0108: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Test.d__1.<>t__builder" + IL_010d: ldloca.s V_4 + IL_010f: ldarg.0 + IL_0110: call "void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AwaitUnsafeOnCompleted, Test.d__1>(ref System.Runtime.CompilerServices.TaskAwaiter, ref Test.d__1)" + IL_0115: leave.s IL_018d + IL_0117: ldarg.0 + IL_0118: ldfld "System.Runtime.CompilerServices.TaskAwaiter Test.d__1.<>u__1" + IL_011d: stloc.s V_4 + IL_011f: ldarg.0 + IL_0120: ldflda "System.Runtime.CompilerServices.TaskAwaiter Test.d__1.<>u__1" + IL_0125: initobj "System.Runtime.CompilerServices.TaskAwaiter" + IL_012b: ldarg.0 + IL_012c: ldc.i4.m1 + IL_012d: dup + IL_012e: stloc.0 + IL_012f: stfld "int Test.d__1.<>1__state" + IL_0134: ldloca.s V_4 + IL_0136: call "string System.Runtime.CompilerServices.TaskAwaiter.GetResult()" + IL_013b: stloc.3 + IL_013c: ldarg.0 + IL_013d: ldfld "string Test.d__1.<>7__wrap1" + IL_0142: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0147: ldarg.0 + IL_0148: ldflda "char Test.d__1.<>7__wrap2" + IL_014d: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0152: ldloc.3 + IL_0153: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0158: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_015d: stloc.1 + IL_015e: leave.s IL_0179 + } + catch System.Exception + { + IL_0160: stloc.s V_6 + IL_0162: ldarg.0 + IL_0163: ldc.i4.s -2 + IL_0165: stfld "int Test.d__1.<>1__state" + IL_016a: ldarg.0 + IL_016b: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Test.d__1.<>t__builder" + IL_0170: ldloc.s V_6 + IL_0172: call "void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetException(System.Exception)" + IL_0177: leave.s IL_018d + } + IL_0179: ldarg.0 + IL_017a: ldc.i4.s -2 + IL_017c: stfld "int Test.d__1.<>1__state" + IL_0181: ldarg.0 + IL_0182: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Test.d__1.<>t__builder" + IL_0187: ldloc.1 + IL_0188: call "void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetResult(string)" + IL_018d: ret + } + """); + } + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] @@ -4306,6 +4663,247 @@ .maxstack 4 """); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatFour_Await() + { + var source = """ + using System; + using System.Threading.Tasks; + + public class Test + { + static async Task Main() + { + Console.Write(await M()); + } + + static async Task M() + { + return (await GetStringAsync()) + (await GetCharAsync()) + (await GetCharAsync()) + (await GetStringAsync()); + } + + static async Task GetStringAsync() + { + await Task.Yield(); + return "s"; + } + + static async Task GetCharAsync() + { + await Task.Yield(); + return 'c'; + } + } + """; + + var comp = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "sccs" : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.d__1.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", """ + { + // Code size 519 (0x207) + .maxstack 4 + .locals init (int V_0, + string V_1, + char V_2, + char V_3, + string V_4, + System.Runtime.CompilerServices.TaskAwaiter V_5, + System.Runtime.CompilerServices.TaskAwaiter V_6, + System.Exception V_7) + IL_0000: ldarg.0 + IL_0001: ldfld "int Test.d__1.<>1__state" + IL_0006: stloc.0 + .try + { + IL_0007: ldloc.0 + IL_0008: switch ( + IL_0056, + IL_00b9, + IL_011e, + IL_0183) + IL_001d: call "System.Threading.Tasks.Task Test.GetStringAsync()" + IL_0022: callvirt "System.Runtime.CompilerServices.TaskAwaiter System.Threading.Tasks.Task.GetAwaiter()" + IL_0027: stloc.s V_5 + IL_0029: ldloca.s V_5 + IL_002b: call "bool System.Runtime.CompilerServices.TaskAwaiter.IsCompleted.get" + IL_0030: brtrue.s IL_0073 + IL_0032: ldarg.0 + IL_0033: ldc.i4.0 + IL_0034: dup + IL_0035: stloc.0 + IL_0036: stfld "int Test.d__1.<>1__state" + IL_003b: ldarg.0 + IL_003c: ldloc.s V_5 + IL_003e: stfld "System.Runtime.CompilerServices.TaskAwaiter Test.d__1.<>u__1" + IL_0043: ldarg.0 + IL_0044: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Test.d__1.<>t__builder" + IL_0049: ldloca.s V_5 + IL_004b: ldarg.0 + IL_004c: call "void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AwaitUnsafeOnCompleted, Test.d__1>(ref System.Runtime.CompilerServices.TaskAwaiter, ref Test.d__1)" + IL_0051: leave IL_0206 + IL_0056: ldarg.0 + IL_0057: ldfld "System.Runtime.CompilerServices.TaskAwaiter Test.d__1.<>u__1" + IL_005c: stloc.s V_5 + IL_005e: ldarg.0 + IL_005f: ldflda "System.Runtime.CompilerServices.TaskAwaiter Test.d__1.<>u__1" + IL_0064: initobj "System.Runtime.CompilerServices.TaskAwaiter" + IL_006a: ldarg.0 + IL_006b: ldc.i4.m1 + IL_006c: dup + IL_006d: stloc.0 + IL_006e: stfld "int Test.d__1.<>1__state" + IL_0073: ldarg.0 + IL_0074: ldloca.s V_5 + IL_0076: call "string System.Runtime.CompilerServices.TaskAwaiter.GetResult()" + IL_007b: stfld "string Test.d__1.<>7__wrap1" + IL_0080: call "System.Threading.Tasks.Task Test.GetCharAsync()" + IL_0085: callvirt "System.Runtime.CompilerServices.TaskAwaiter System.Threading.Tasks.Task.GetAwaiter()" + IL_008a: stloc.s V_6 + IL_008c: ldloca.s V_6 + IL_008e: call "bool System.Runtime.CompilerServices.TaskAwaiter.IsCompleted.get" + IL_0093: brtrue.s IL_00d6 + IL_0095: ldarg.0 + IL_0096: ldc.i4.1 + IL_0097: dup + IL_0098: stloc.0 + IL_0099: stfld "int Test.d__1.<>1__state" + IL_009e: ldarg.0 + IL_009f: ldloc.s V_6 + IL_00a1: stfld "System.Runtime.CompilerServices.TaskAwaiter Test.d__1.<>u__2" + IL_00a6: ldarg.0 + IL_00a7: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Test.d__1.<>t__builder" + IL_00ac: ldloca.s V_6 + IL_00ae: ldarg.0 + IL_00af: call "void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AwaitUnsafeOnCompleted, Test.d__1>(ref System.Runtime.CompilerServices.TaskAwaiter, ref Test.d__1)" + IL_00b4: leave IL_0206 + IL_00b9: ldarg.0 + IL_00ba: ldfld "System.Runtime.CompilerServices.TaskAwaiter Test.d__1.<>u__2" + IL_00bf: stloc.s V_6 + IL_00c1: ldarg.0 + IL_00c2: ldflda "System.Runtime.CompilerServices.TaskAwaiter Test.d__1.<>u__2" + IL_00c7: initobj "System.Runtime.CompilerServices.TaskAwaiter" + IL_00cd: ldarg.0 + IL_00ce: ldc.i4.m1 + IL_00cf: dup + IL_00d0: stloc.0 + IL_00d1: stfld "int Test.d__1.<>1__state" + IL_00d6: ldloca.s V_6 + IL_00d8: call "char System.Runtime.CompilerServices.TaskAwaiter.GetResult()" + IL_00dd: stloc.2 + IL_00de: ldarg.0 + IL_00df: ldloc.2 + IL_00e0: stfld "char Test.d__1.<>7__wrap2" + IL_00e5: call "System.Threading.Tasks.Task Test.GetCharAsync()" + IL_00ea: callvirt "System.Runtime.CompilerServices.TaskAwaiter System.Threading.Tasks.Task.GetAwaiter()" + IL_00ef: stloc.s V_6 + IL_00f1: ldloca.s V_6 + IL_00f3: call "bool System.Runtime.CompilerServices.TaskAwaiter.IsCompleted.get" + IL_00f8: brtrue.s IL_013b + IL_00fa: ldarg.0 + IL_00fb: ldc.i4.2 + IL_00fc: dup + IL_00fd: stloc.0 + IL_00fe: stfld "int Test.d__1.<>1__state" + IL_0103: ldarg.0 + IL_0104: ldloc.s V_6 + IL_0106: stfld "System.Runtime.CompilerServices.TaskAwaiter Test.d__1.<>u__2" + IL_010b: ldarg.0 + IL_010c: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Test.d__1.<>t__builder" + IL_0111: ldloca.s V_6 + IL_0113: ldarg.0 + IL_0114: call "void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AwaitUnsafeOnCompleted, Test.d__1>(ref System.Runtime.CompilerServices.TaskAwaiter, ref Test.d__1)" + IL_0119: leave IL_0206 + IL_011e: ldarg.0 + IL_011f: ldfld "System.Runtime.CompilerServices.TaskAwaiter Test.d__1.<>u__2" + IL_0124: stloc.s V_6 + IL_0126: ldarg.0 + IL_0127: ldflda "System.Runtime.CompilerServices.TaskAwaiter Test.d__1.<>u__2" + IL_012c: initobj "System.Runtime.CompilerServices.TaskAwaiter" + IL_0132: ldarg.0 + IL_0133: ldc.i4.m1 + IL_0134: dup + IL_0135: stloc.0 + IL_0136: stfld "int Test.d__1.<>1__state" + IL_013b: ldloca.s V_6 + IL_013d: call "char System.Runtime.CompilerServices.TaskAwaiter.GetResult()" + IL_0142: stloc.3 + IL_0143: ldarg.0 + IL_0144: ldloc.3 + IL_0145: stfld "char Test.d__1.<>7__wrap3" + IL_014a: call "System.Threading.Tasks.Task Test.GetStringAsync()" + IL_014f: callvirt "System.Runtime.CompilerServices.TaskAwaiter System.Threading.Tasks.Task.GetAwaiter()" + IL_0154: stloc.s V_5 + IL_0156: ldloca.s V_5 + IL_0158: call "bool System.Runtime.CompilerServices.TaskAwaiter.IsCompleted.get" + IL_015d: brtrue.s IL_01a0 + IL_015f: ldarg.0 + IL_0160: ldc.i4.3 + IL_0161: dup + IL_0162: stloc.0 + IL_0163: stfld "int Test.d__1.<>1__state" + IL_0168: ldarg.0 + IL_0169: ldloc.s V_5 + IL_016b: stfld "System.Runtime.CompilerServices.TaskAwaiter Test.d__1.<>u__1" + IL_0170: ldarg.0 + IL_0171: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Test.d__1.<>t__builder" + IL_0176: ldloca.s V_5 + IL_0178: ldarg.0 + IL_0179: call "void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AwaitUnsafeOnCompleted, Test.d__1>(ref System.Runtime.CompilerServices.TaskAwaiter, ref Test.d__1)" + IL_017e: leave IL_0206 + IL_0183: ldarg.0 + IL_0184: ldfld "System.Runtime.CompilerServices.TaskAwaiter Test.d__1.<>u__1" + IL_0189: stloc.s V_5 + IL_018b: ldarg.0 + IL_018c: ldflda "System.Runtime.CompilerServices.TaskAwaiter Test.d__1.<>u__1" + IL_0191: initobj "System.Runtime.CompilerServices.TaskAwaiter" + IL_0197: ldarg.0 + IL_0198: ldc.i4.m1 + IL_0199: dup + IL_019a: stloc.0 + IL_019b: stfld "int Test.d__1.<>1__state" + IL_01a0: ldloca.s V_5 + IL_01a2: call "string System.Runtime.CompilerServices.TaskAwaiter.GetResult()" + IL_01a7: stloc.s V_4 + IL_01a9: ldarg.0 + IL_01aa: ldfld "string Test.d__1.<>7__wrap1" + IL_01af: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_01b4: ldarg.0 + IL_01b5: ldflda "char Test.d__1.<>7__wrap2" + IL_01ba: newobj "System.ReadOnlySpan..ctor(in char)" + IL_01bf: ldarg.0 + IL_01c0: ldflda "char Test.d__1.<>7__wrap3" + IL_01c5: newobj "System.ReadOnlySpan..ctor(in char)" + IL_01ca: ldloc.s V_4 + IL_01cc: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_01d1: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_01d6: stloc.1 + IL_01d7: leave.s IL_01f2 + } + catch System.Exception + { + IL_01d9: stloc.s V_7 + IL_01db: ldarg.0 + IL_01dc: ldc.i4.s -2 + IL_01de: stfld "int Test.d__1.<>1__state" + IL_01e3: ldarg.0 + IL_01e4: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Test.d__1.<>t__builder" + IL_01e9: ldloc.s V_7 + IL_01eb: call "void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetException(System.Exception)" + IL_01f0: leave.s IL_0206 + } + IL_01f2: ldarg.0 + IL_01f3: ldc.i4.s -2 + IL_01f5: stfld "int Test.d__1.<>1__state" + IL_01fa: ldarg.0 + IL_01fb: ldflda "System.Runtime.CompilerServices.AsyncTaskMethodBuilder Test.d__1.<>t__builder" + IL_0200: ldloc.1 + IL_0201: call "void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetResult(string)" + IL_0206: ret + } + """); + } + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] From 04046af46e0edd7353c0ef7dc761e0d80dbc0162 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Mon, 12 Feb 2024 21:02:01 +0300 Subject: [PATCH 43/71] Pass ref kind --- .../Portable/Lowering/SpillSequenceSpiller.cs | 2 +- .../CodeGenSpanBasedStringConcatTests.cs | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs b/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs index f040a895e86a7..25f8d818f46ac 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs @@ -482,7 +482,7 @@ private BoundExpression Spill( if (objectCreationExpression.Constructor.OriginalDefinition == _F.Compilation.GetWellKnownTypeMember(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)) { Debug.Assert(objectCreationExpression.Arguments.Length == 1); - return objectCreationExpression.UpdateArgumentsAndInitializer([Spill(builder, objectCreationExpression.Arguments[0])], + return objectCreationExpression.UpdateArgumentsAndInitializer([Spill(builder, objectCreationExpression.Arguments[0], objectCreationExpression.ArgumentRefKindsOpt[0])], objectCreationExpression.ArgumentRefKindsOpt, objectCreationExpression.InitializerExpressionOpt); } diff --git a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs index d20dc3d1e7a11..667d5dd06e85f 100644 --- a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs @@ -2275,7 +2275,7 @@ .locals init (int V_0, IL_006f: ldarg.0 IL_0070: ldloca.s V_4 IL_0072: call "string System.Runtime.CompilerServices.TaskAwaiter.GetResult()" - IL_0077: stfld "string Test.d__1.<>7__wrap1" + IL_0077: stfld "string Test.d__1.<>7__wrap2" IL_007c: call "System.Threading.Tasks.Task Test.GetCharAsync()" IL_0081: callvirt "System.Runtime.CompilerServices.TaskAwaiter System.Threading.Tasks.Task.GetAwaiter()" IL_0086: stloc.s V_5 @@ -2312,7 +2312,7 @@ .locals init (int V_0, IL_00d9: stloc.2 IL_00da: ldarg.0 IL_00db: ldloc.2 - IL_00dc: stfld "char Test.d__1.<>7__wrap2" + IL_00dc: stfld "char Test.d__1.<>7__wrap1" IL_00e1: call "System.Threading.Tasks.Task Test.GetStringAsync()" IL_00e6: callvirt "System.Runtime.CompilerServices.TaskAwaiter System.Threading.Tasks.Task.GetAwaiter()" IL_00eb: stloc.s V_4 @@ -2348,10 +2348,10 @@ .locals init (int V_0, IL_0136: call "string System.Runtime.CompilerServices.TaskAwaiter.GetResult()" IL_013b: stloc.3 IL_013c: ldarg.0 - IL_013d: ldfld "string Test.d__1.<>7__wrap1" + IL_013d: ldfld "string Test.d__1.<>7__wrap2" IL_0142: call "System.ReadOnlySpan string.op_Implicit(string)" IL_0147: ldarg.0 - IL_0148: ldflda "char Test.d__1.<>7__wrap2" + IL_0148: ldflda "char Test.d__1.<>7__wrap1" IL_014d: newobj "System.ReadOnlySpan..ctor(in char)" IL_0152: ldloc.3 IL_0153: call "System.ReadOnlySpan string.op_Implicit(string)" @@ -4756,7 +4756,7 @@ .locals init (int V_0, IL_0073: ldarg.0 IL_0074: ldloca.s V_5 IL_0076: call "string System.Runtime.CompilerServices.TaskAwaiter.GetResult()" - IL_007b: stfld "string Test.d__1.<>7__wrap1" + IL_007b: stfld "string Test.d__1.<>7__wrap3" IL_0080: call "System.Threading.Tasks.Task Test.GetCharAsync()" IL_0085: callvirt "System.Runtime.CompilerServices.TaskAwaiter System.Threading.Tasks.Task.GetAwaiter()" IL_008a: stloc.s V_6 @@ -4793,7 +4793,7 @@ .locals init (int V_0, IL_00dd: stloc.2 IL_00de: ldarg.0 IL_00df: ldloc.2 - IL_00e0: stfld "char Test.d__1.<>7__wrap2" + IL_00e0: stfld "char Test.d__1.<>7__wrap1" IL_00e5: call "System.Threading.Tasks.Task Test.GetCharAsync()" IL_00ea: callvirt "System.Runtime.CompilerServices.TaskAwaiter System.Threading.Tasks.Task.GetAwaiter()" IL_00ef: stloc.s V_6 @@ -4830,7 +4830,7 @@ .locals init (int V_0, IL_0142: stloc.3 IL_0143: ldarg.0 IL_0144: ldloc.3 - IL_0145: stfld "char Test.d__1.<>7__wrap3" + IL_0145: stfld "char Test.d__1.<>7__wrap2" IL_014a: call "System.Threading.Tasks.Task Test.GetStringAsync()" IL_014f: callvirt "System.Runtime.CompilerServices.TaskAwaiter System.Threading.Tasks.Task.GetAwaiter()" IL_0154: stloc.s V_5 @@ -4866,13 +4866,13 @@ .locals init (int V_0, IL_01a2: call "string System.Runtime.CompilerServices.TaskAwaiter.GetResult()" IL_01a7: stloc.s V_4 IL_01a9: ldarg.0 - IL_01aa: ldfld "string Test.d__1.<>7__wrap1" + IL_01aa: ldfld "string Test.d__1.<>7__wrap3" IL_01af: call "System.ReadOnlySpan string.op_Implicit(string)" IL_01b4: ldarg.0 - IL_01b5: ldflda "char Test.d__1.<>7__wrap2" + IL_01b5: ldflda "char Test.d__1.<>7__wrap1" IL_01ba: newobj "System.ReadOnlySpan..ctor(in char)" IL_01bf: ldarg.0 - IL_01c0: ldflda "char Test.d__1.<>7__wrap3" + IL_01c0: ldflda "char Test.d__1.<>7__wrap2" IL_01c5: newobj "System.ReadOnlySpan..ctor(in char)" IL_01ca: ldloc.s V_4 IL_01cc: call "System.ReadOnlySpan string.op_Implicit(string)" From 323b50a7ec4aaaa049d8533111a85a048f27294d Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Mon, 12 Feb 2024 21:06:21 +0300 Subject: [PATCH 44/71] Feedback --- .../CSharp/Portable/Lowering/SpillSequenceSpiller.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs b/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs index 25f8d818f46ac..fd081aff53196 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs @@ -479,12 +479,14 @@ private BoundExpression Spill( case BoundKind.ObjectCreationExpression: var objectCreationExpression = (BoundObjectCreationExpression)expression; - if (objectCreationExpression.Constructor.OriginalDefinition == _F.Compilation.GetWellKnownTypeMember(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)) + if (refKind == RefKind.None && + objectCreationExpression.InitializerExpressionOpt is null && + objectCreationExpression.Constructor.OriginalDefinition == _F.Compilation.GetWellKnownTypeMember(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)) { Debug.Assert(objectCreationExpression.Arguments.Length == 1); return objectCreationExpression.UpdateArgumentsAndInitializer([Spill(builder, objectCreationExpression.Arguments[0], objectCreationExpression.ArgumentRefKindsOpt[0])], objectCreationExpression.ArgumentRefKindsOpt, - objectCreationExpression.InitializerExpressionOpt); + newInitializerExpression: null); } goto default; From 14fb8c1965a4dc783c0d38c9905e0e0d0180b94f Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Tue, 13 Feb 2024 19:47:37 +0300 Subject: [PATCH 45/71] Extra safe --- src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs b/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs index fd081aff53196..f704dca8f8134 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs @@ -484,7 +484,8 @@ objectCreationExpression.InitializerExpressionOpt is null && objectCreationExpression.Constructor.OriginalDefinition == _F.Compilation.GetWellKnownTypeMember(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)) { Debug.Assert(objectCreationExpression.Arguments.Length == 1); - return objectCreationExpression.UpdateArgumentsAndInitializer([Spill(builder, objectCreationExpression.Arguments[0], objectCreationExpression.ArgumentRefKindsOpt[0])], + var argRefKinds = objectCreationExpression.ArgumentRefKindsOpt; + return objectCreationExpression.UpdateArgumentsAndInitializer([Spill(builder, objectCreationExpression.Arguments[0], argRefKinds.IsDefault ? RefKind.None : argRefKinds[0])], objectCreationExpression.ArgumentRefKindsOpt, newInitializerExpression: null); } From 5df710b596c6944b5c14ee10765ff8e6f14e1d4c Mon Sep 17 00:00:00 2001 From: AlekseyTs Date: Tue, 13 Feb 2024 09:57:36 -0800 Subject: [PATCH 46/71] Unify handling of constant and not constant chars during string concat --- .../LocalRewriter_StringConcat.cs | 7 + .../CodeGenSpanBasedStringConcatTests.cs | 321 +++++++++++------- 2 files changed, 212 insertions(+), 116 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index 8227b25c1d90a..e38553db3bb16 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -439,6 +439,13 @@ private bool TryRewriteStringConcatenationWithSpanBasedConcat(SyntaxNode syntax, preparedArgs.Add(receiver); continue; } + else if (arg.ConstantValueOpt is { IsString: true, StringValue: [char c] }) + { + needsSpanRefParamConstructor = true; + charType = _factory.SpecialType(SpecialType.System_Char); + preparedArgs.Add(new BoundLiteral(arg.Syntax, constantValueOpt: ConstantValue.Create(c), charType)); + continue; + } preparedArgs.Add(arg); needsImplicitConversionFromStringToSpan = true; diff --git a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs index 667d5dd06e85f..60c3fe8619833 100644 --- a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs @@ -304,12 +304,8 @@ .locals init (char V_0, //c """); } - [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] - [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] - public void ConcatTwo_ConstantCharToString(int? missingUnimportantWellKnownMember) + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatTwo_ConstantCharToString() { var source = """ using System; @@ -330,11 +326,6 @@ static void Main() var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - if (missingUnimportantWellKnownMember.HasValue) - { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); - } - var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "sccs" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); verifier.VerifyDiagnostics(); @@ -342,22 +333,32 @@ static void Main() // Instead of emitting this as a span-based concat of string and char we recognize "constantChar.ToString()" pattern and lower that argument to a constant string verifier.VerifyIL("Test.M1", """ { - // Code size 12 (0xc) + // Code size 22 (0x16) .maxstack 2 + .locals init (char V_0) IL_0000: ldarg.0 - IL_0001: ldstr "c" - IL_0006: call "string string.Concat(string, string)" - IL_000b: ret + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldc.i4.s 99 + IL_0008: stloc.0 + IL_0009: ldloca.s V_0 + IL_000b: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0010: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0015: ret } """); verifier.VerifyIL("Test.M2", """ { - // Code size 12 (0xc) + // Code size 22 (0x16) .maxstack 2 - IL_0000: ldstr "c" - IL_0005: ldarg.0 - IL_0006: call "string string.Concat(string, string)" - IL_000b: ret + .locals init (char V_0) + IL_0000: ldc.i4.s 99 + IL_0002: stloc.0 + IL_0003: ldloca.s V_0 + IL_0005: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000a: ldarg.0 + IL_000b: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0010: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0015: ret } """); } @@ -1364,19 +1365,22 @@ public void M() // Code size 50 (0x32) .maxstack 3 .locals init (char V_0, - char V_1) - IL_0000: ldstr "a" - IL_0005: call "System.ReadOnlySpan string.op_Implicit(string)" + char V_1, + char V_2) + IL_0000: ldc.i4.s 97 + IL_0002: stloc.0 + IL_0003: ldloca.s V_0 + IL_0005: newobj "System.ReadOnlySpan..ctor(in char)" IL_000a: ldarg.0 IL_000b: ldfld "char C.c" - IL_0010: stloc.0 - IL_0011: ldloca.s V_0 + IL_0010: stloc.1 + IL_0011: ldloca.s V_1 IL_0013: newobj "System.ReadOnlySpan..ctor(in char)" IL_0018: ldarg.0 IL_0019: call "ref char C.GetC()" IL_001e: ldind.u2 - IL_001f: stloc.1 - IL_0020: ldloca.s V_1 + IL_001f: stloc.2 + IL_0020: ldloca.s V_2 IL_0022: newobj "System.ReadOnlySpan..ctor(in char)" IL_0027: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" IL_002c: call "void System.Console.Write(string)" @@ -1428,20 +1432,23 @@ private char SneakyLocalChange(ref char local) .maxstack 4 .locals init (char V_0, //c char V_1, - char V_2) + char V_2, + char V_3) IL_0000: ldc.i4.s 97 IL_0002: stloc.0 - IL_0003: ldstr "a" - IL_0008: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0003: ldc.i4.s 97 + IL_0005: stloc.1 + IL_0006: ldloca.s V_1 + IL_0008: newobj "System.ReadOnlySpan..ctor(in char)" IL_000d: ldloc.0 - IL_000e: stloc.1 - IL_000f: ldloca.s V_1 + IL_000e: stloc.2 + IL_000f: ldloca.s V_2 IL_0011: newobj "System.ReadOnlySpan..ctor(in char)" IL_0016: ldarg.0 IL_0017: ldloca.s V_0 IL_0019: call "char C.SneakyLocalChange(ref char)" - IL_001e: stloc.2 - IL_001f: ldloca.s V_2 + IL_001e: stloc.3 + IL_001f: ldloca.s V_3 IL_0021: newobj "System.ReadOnlySpan..ctor(in char)" IL_0026: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" IL_002b: ret @@ -1451,10 +1458,7 @@ .locals init (char V_0, //c [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] - [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] public void ConcatThree_ConstantCharToString(int? missingUnimportantWellKnownMember) { var source = """ @@ -1492,46 +1496,73 @@ static void Main() // Instead of emitting this as a span-based concat of strings and chars we recognize "constantChar.ToString()" pattern and lower that arguments to constant strings verifier.VerifyIL("Test.M1", """ { - // Code size 13 (0xd) + // Code size 28 (0x1c) .maxstack 3 - IL_0000: ldstr "c" - IL_0005: ldarg.0 - IL_0006: ldarg.0 - IL_0007: call "string string.Concat(string, string, string)" - IL_000c: ret + .locals init (char V_0) + IL_0000: ldc.i4.s 99 + IL_0002: stloc.0 + IL_0003: ldloca.s V_0 + IL_0005: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000a: ldarg.0 + IL_000b: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0010: ldarg.0 + IL_0011: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0016: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001b: ret } """); verifier.VerifyIL("Test.M2", """ { - // Code size 13 (0xd) + // Code size 28 (0x1c) .maxstack 3 + .locals init (char V_0) IL_0000: ldarg.0 - IL_0001: ldstr "c" - IL_0006: ldarg.0 - IL_0007: call "string string.Concat(string, string, string)" - IL_000c: ret + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldc.i4.s 99 + IL_0008: stloc.0 + IL_0009: ldloca.s V_0 + IL_000b: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0010: ldarg.0 + IL_0011: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0016: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001b: ret } """); verifier.VerifyIL("Test.M3", """ { - // Code size 13 (0xd) + // Code size 28 (0x1c) .maxstack 3 + .locals init (char V_0) IL_0000: ldarg.0 - IL_0001: ldarg.0 - IL_0002: ldstr "c" - IL_0007: call "string string.Concat(string, string, string)" - IL_000c: ret + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.0 + IL_0007: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000c: ldc.i4.s 99 + IL_000e: stloc.0 + IL_000f: ldloca.s V_0 + IL_0011: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0016: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001b: ret } """); verifier.VerifyIL("Test.M4", """ { - // Code size 17 (0x11) + // Code size 32 (0x20) .maxstack 3 - IL_0000: ldstr "c" - IL_0005: ldarg.0 - IL_0006: ldstr "c" - IL_000b: call "string string.Concat(string, string, string)" - IL_0010: ret + .locals init (char V_0, + char V_1) + IL_0000: ldc.i4.s 99 + IL_0002: stloc.0 + IL_0003: ldloca.s V_0 + IL_0005: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000a: ldarg.0 + IL_000b: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0010: ldc.i4.s 99 + IL_0012: stloc.1 + IL_0013: ldloca.s V_1 + IL_0015: newobj "System.ReadOnlySpan..ctor(in char)" + IL_001a: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001f: ret } """); } @@ -3082,25 +3113,28 @@ public void M() .maxstack 4 .locals init (char V_0, char V_1, - char V_2) - IL_0000: ldstr "a" - IL_0005: call "System.ReadOnlySpan string.op_Implicit(string)" + char V_2, + char V_3) + IL_0000: ldc.i4.s 97 + IL_0002: stloc.0 + IL_0003: ldloca.s V_0 + IL_0005: newobj "System.ReadOnlySpan..ctor(in char)" IL_000a: ldarg.0 IL_000b: ldfld "char C.c" - IL_0010: stloc.0 - IL_0011: ldloca.s V_0 + IL_0010: stloc.1 + IL_0011: ldloca.s V_1 IL_0013: newobj "System.ReadOnlySpan..ctor(in char)" IL_0018: ldarg.0 IL_0019: call "ref char C.GetC()" IL_001e: ldind.u2 - IL_001f: stloc.1 - IL_0020: ldloca.s V_1 + IL_001f: stloc.2 + IL_0020: ldloca.s V_2 IL_0022: newobj "System.ReadOnlySpan..ctor(in char)" IL_0027: ldarg.0 IL_0028: call "ref char C.GetC2()" IL_002d: ldind.u2 - IL_002e: stloc.2 - IL_002f: ldloca.s V_2 + IL_002e: stloc.3 + IL_002f: ldloca.s V_3 IL_0031: newobj "System.ReadOnlySpan..ctor(in char)" IL_0036: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" IL_003b: call "void System.Console.Write(string)" @@ -3184,11 +3218,8 @@ .locals init (char V_0, //c1 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] - [InlineData((int)WellKnownMember.System_String__Concat_4ReadOnlySpans)] public void ConcatFour_ConstantCharToString(int? missingUnimportantWellKnownMember) { var source = """ @@ -3232,86 +3263,144 @@ static void Main() // Instead of emitting this as a span-based concat of strings and chars we recognize "constantChar.ToString()" pattern and lower that arguments to constant strings verifier.VerifyIL("Test.M1", """ { - // Code size 14 (0xe) + // Code size 34 (0x22) .maxstack 4 - IL_0000: ldstr "c" - IL_0005: ldarg.0 - IL_0006: ldarg.0 - IL_0007: ldarg.0 - IL_0008: call "string string.Concat(string, string, string, string)" - IL_000d: ret + .locals init (char V_0) + IL_0000: ldc.i4.s 99 + IL_0002: stloc.0 + IL_0003: ldloca.s V_0 + IL_0005: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000a: ldarg.0 + IL_000b: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0010: ldarg.0 + IL_0011: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0016: ldarg.0 + IL_0017: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_001c: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0021: ret } """); verifier.VerifyIL("Test.M2", """ { - // Code size 14 (0xe) + // Code size 34 (0x22) .maxstack 4 + .locals init (char V_0) IL_0000: ldarg.0 - IL_0001: ldstr "c" - IL_0006: ldarg.0 - IL_0007: ldarg.0 - IL_0008: call "string string.Concat(string, string, string, string)" - IL_000d: ret + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldc.i4.s 99 + IL_0008: stloc.0 + IL_0009: ldloca.s V_0 + IL_000b: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0010: ldarg.0 + IL_0011: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0016: ldarg.0 + IL_0017: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_001c: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0021: ret } """); verifier.VerifyIL("Test.M3", """ { - // Code size 14 (0xe) + // Code size 34 (0x22) .maxstack 4 + .locals init (char V_0) IL_0000: ldarg.0 - IL_0001: ldarg.0 - IL_0002: ldstr "c" - IL_0007: ldarg.0 - IL_0008: call "string string.Concat(string, string, string, string)" - IL_000d: ret + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.0 + IL_0007: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000c: ldc.i4.s 99 + IL_000e: stloc.0 + IL_000f: ldloca.s V_0 + IL_0011: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0016: ldarg.0 + IL_0017: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_001c: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0021: ret } """); verifier.VerifyIL("Test.M4", """ { - // Code size 14 (0xe) + // Code size 34 (0x22) .maxstack 4 + .locals init (char V_0) IL_0000: ldarg.0 - IL_0001: ldarg.0 - IL_0002: ldarg.0 - IL_0003: ldstr "c" - IL_0008: call "string string.Concat(string, string, string, string)" - IL_000d: ret + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.0 + IL_0007: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000c: ldarg.0 + IL_000d: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0012: ldc.i4.s 99 + IL_0014: stloc.0 + IL_0015: ldloca.s V_0 + IL_0017: newobj "System.ReadOnlySpan..ctor(in char)" + IL_001c: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0021: ret } """); verifier.VerifyIL("Test.M5", """ { - // Code size 18 (0x12) + // Code size 38 (0x26) .maxstack 4 - IL_0000: ldstr "c" - IL_0005: ldarg.0 - IL_0006: ldstr "c" - IL_000b: ldarg.0 - IL_000c: call "string string.Concat(string, string, string, string)" - IL_0011: ret + .locals init (char V_0, + char V_1) + IL_0000: ldc.i4.s 99 + IL_0002: stloc.0 + IL_0003: ldloca.s V_0 + IL_0005: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000a: ldarg.0 + IL_000b: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0010: ldc.i4.s 99 + IL_0012: stloc.1 + IL_0013: ldloca.s V_1 + IL_0015: newobj "System.ReadOnlySpan..ctor(in char)" + IL_001a: ldarg.0 + IL_001b: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0020: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0025: ret } """); verifier.VerifyIL("Test.M6", """ { - // Code size 18 (0x12) + // Code size 38 (0x26) .maxstack 4 + .locals init (char V_0, + char V_1) IL_0000: ldarg.0 - IL_0001: ldstr "c" - IL_0006: ldarg.0 - IL_0007: ldstr "c" - IL_000c: call "string string.Concat(string, string, string, string)" - IL_0011: ret + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldc.i4.s 99 + IL_0008: stloc.0 + IL_0009: ldloca.s V_0 + IL_000b: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0010: ldarg.0 + IL_0011: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0016: ldc.i4.s 99 + IL_0018: stloc.1 + IL_0019: ldloca.s V_1 + IL_001b: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0020: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0025: ret } """); verifier.VerifyIL("Test.M7", """ { - // Code size 18 (0x12) + // Code size 38 (0x26) .maxstack 4 - IL_0000: ldstr "c" - IL_0005: ldarg.0 - IL_0006: ldarg.0 - IL_0007: ldstr "c" - IL_000c: call "string string.Concat(string, string, string, string)" - IL_0011: ret + .locals init (char V_0, + char V_1) + IL_0000: ldc.i4.s 99 + IL_0002: stloc.0 + IL_0003: ldloca.s V_0 + IL_0005: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000a: ldarg.0 + IL_000b: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0010: ldarg.0 + IL_0011: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0016: ldc.i4.s 99 + IL_0018: stloc.1 + IL_0019: ldloca.s V_1 + IL_001b: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0020: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0025: ret } """); } From 3e841f78041e4bac301b559c8ca2ff6d755ed594 Mon Sep 17 00:00:00 2001 From: AlekseyTs Date: Tue, 13 Feb 2024 15:00:38 -0800 Subject: [PATCH 47/71] Fixup --- .../Emit2/Emit/LocalStateTracing/LocalStateTracingTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit2/Emit/LocalStateTracing/LocalStateTracingTests.cs b/src/Compilers/CSharp/Test/Emit2/Emit/LocalStateTracing/LocalStateTracingTests.cs index b7561d7939aaa..be25b5b647774 100644 --- a/src/Compilers/CSharp/Test/Emit2/Emit/LocalStateTracing/LocalStateTracingTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Emit/LocalStateTracing/LocalStateTracingTests.cs @@ -170,8 +170,8 @@ private static string WithHelpers(string source) [LogLambdaEntry]: Return type is ByRef, TypedReference, ArgHandle, or ArgIterator. { Offset = 0x9 } [LogStateMachineMethodEntry]: Return type is ByRef, TypedReference, ArgHandle, or ArgIterator. { Offset = 0x8 } [LogStateMachineLambdaEntry]: Return type is ByRef, TypedReference, ArgHandle, or ArgIterator. { Offset = 0x8 } - [Entry]: Return type is ByRef, TypedReference, ArgHandle, or ArgIterator. { Offset = 0xbc } - [MemoryToString]: Unmanaged pointers are not a verifiable type. { Offset = 0x5 } + [Entry]: Return type is ByRef, TypedReference, ArgHandle, or ArgIterator. { Offset = 0xd1 } + [MemoryToString]: Unmanaged pointers are not a verifiable type. { Offset = 0xa } [LogLocalStore]: Unmanaged pointers are not a verifiable type. { Offset = 0x1 } [LogLocalStoreUnmanaged]: Unmanaged pointers are not a verifiable type. { Offset = 0x1 } [LogParameterStore]: Unmanaged pointers are not a verifiable type. { Offset = 0x1 } From 5ddb57979b05b7c35c2499351e47c7aad0b03c3a Mon Sep 17 00:00:00 2001 From: AlekseyTs Date: Wed, 14 Feb 2024 16:17:26 -0800 Subject: [PATCH 48/71] Reduce amount of scenarios affected by unification --- .../LocalRewriter_StringConcat.cs | 7 +- .../CodeGenSpanBasedStringConcatTests.cs | 266 ++++++------------ .../LocalStateTracingTests.cs | 4 +- 3 files changed, 99 insertions(+), 178 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index e38553db3bb16..5d317d195edb2 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -441,9 +441,10 @@ private bool TryRewriteStringConcatenationWithSpanBasedConcat(SyntaxNode syntax, } else if (arg.ConstantValueOpt is { IsString: true, StringValue: [char c] }) { - needsSpanRefParamConstructor = true; - charType = _factory.SpecialType(SpecialType.System_Char); - preparedArgs.Add(new BoundLiteral(arg.Syntax, constantValueOpt: ConstantValue.Create(c), charType)); + preparedArgs.Add( + new BoundLiteral( + arg.Syntax, constantValueOpt: ConstantValue.Create(c), + charType ?? _compilation.GetSpecialType(SpecialType.System_Char))); // We will pull 'charType' from BoundCall, if it is bad an error has been already reported elsewhere continue; } diff --git a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs index 60c3fe8619833..87480e9f490aa 100644 --- a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs @@ -304,8 +304,12 @@ .locals init (char V_0, //c """); } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] - public void ConcatTwo_ConstantCharToString() + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] + public void ConcatTwo_ConstantCharToString(int? missingUnimportantWellKnownMember) { var source = """ using System; @@ -326,6 +330,11 @@ static void Main() var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + if (missingUnimportantWellKnownMember.HasValue) + { + comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + } + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "sccs" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); verifier.VerifyDiagnostics(); @@ -333,32 +342,22 @@ static void Main() // Instead of emitting this as a span-based concat of string and char we recognize "constantChar.ToString()" pattern and lower that argument to a constant string verifier.VerifyIL("Test.M1", """ { - // Code size 22 (0x16) + // Code size 12 (0xc) .maxstack 2 - .locals init (char V_0) IL_0000: ldarg.0 - IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_0006: ldc.i4.s 99 - IL_0008: stloc.0 - IL_0009: ldloca.s V_0 - IL_000b: newobj "System.ReadOnlySpan..ctor(in char)" - IL_0010: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0015: ret + IL_0001: ldstr "c" + IL_0006: call "string string.Concat(string, string)" + IL_000b: ret } """); verifier.VerifyIL("Test.M2", """ { - // Code size 22 (0x16) + // Code size 12 (0xc) .maxstack 2 - .locals init (char V_0) - IL_0000: ldc.i4.s 99 - IL_0002: stloc.0 - IL_0003: ldloca.s V_0 - IL_0005: newobj "System.ReadOnlySpan..ctor(in char)" - IL_000a: ldarg.0 - IL_000b: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_0010: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0015: ret + IL_0000: ldstr "c" + IL_0005: ldarg.0 + IL_0006: call "string string.Concat(string, string)" + IL_000b: ret } """); } @@ -1458,7 +1457,10 @@ .locals init (char V_0, //c [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] + [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] public void ConcatThree_ConstantCharToString(int? missingUnimportantWellKnownMember) { var source = """ @@ -1496,73 +1498,46 @@ static void Main() // Instead of emitting this as a span-based concat of strings and chars we recognize "constantChar.ToString()" pattern and lower that arguments to constant strings verifier.VerifyIL("Test.M1", """ { - // Code size 28 (0x1c) + // Code size 13 (0xd) .maxstack 3 - .locals init (char V_0) - IL_0000: ldc.i4.s 99 - IL_0002: stloc.0 - IL_0003: ldloca.s V_0 - IL_0005: newobj "System.ReadOnlySpan..ctor(in char)" - IL_000a: ldarg.0 - IL_000b: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_0010: ldarg.0 - IL_0011: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_0016: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_001b: ret + IL_0000: ldstr "c" + IL_0005: ldarg.0 + IL_0006: ldarg.0 + IL_0007: call "string string.Concat(string, string, string)" + IL_000c: ret } """); verifier.VerifyIL("Test.M2", """ { - // Code size 28 (0x1c) + // Code size 13 (0xd) .maxstack 3 - .locals init (char V_0) IL_0000: ldarg.0 - IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_0006: ldc.i4.s 99 - IL_0008: stloc.0 - IL_0009: ldloca.s V_0 - IL_000b: newobj "System.ReadOnlySpan..ctor(in char)" - IL_0010: ldarg.0 - IL_0011: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_0016: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_001b: ret + IL_0001: ldstr "c" + IL_0006: ldarg.0 + IL_0007: call "string string.Concat(string, string, string)" + IL_000c: ret } """); verifier.VerifyIL("Test.M3", """ { - // Code size 28 (0x1c) + // Code size 13 (0xd) .maxstack 3 - .locals init (char V_0) IL_0000: ldarg.0 - IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_0006: ldarg.0 - IL_0007: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_000c: ldc.i4.s 99 - IL_000e: stloc.0 - IL_000f: ldloca.s V_0 - IL_0011: newobj "System.ReadOnlySpan..ctor(in char)" - IL_0016: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_001b: ret + IL_0001: ldarg.0 + IL_0002: ldstr "c" + IL_0007: call "string string.Concat(string, string, string)" + IL_000c: ret } """); verifier.VerifyIL("Test.M4", """ { - // Code size 32 (0x20) + // Code size 17 (0x11) .maxstack 3 - .locals init (char V_0, - char V_1) - IL_0000: ldc.i4.s 99 - IL_0002: stloc.0 - IL_0003: ldloca.s V_0 - IL_0005: newobj "System.ReadOnlySpan..ctor(in char)" - IL_000a: ldarg.0 - IL_000b: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_0010: ldc.i4.s 99 - IL_0012: stloc.1 - IL_0013: ldloca.s V_1 - IL_0015: newobj "System.ReadOnlySpan..ctor(in char)" - IL_001a: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_001f: ret + IL_0000: ldstr "c" + IL_0005: ldarg.0 + IL_0006: ldstr "c" + IL_000b: call "string string.Concat(string, string, string)" + IL_0010: ret } """); } @@ -3218,8 +3193,11 @@ .locals init (char V_0, //c1 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] + [InlineData((int)WellKnownMember.System_String__Concat_4ReadOnlySpans)] public void ConcatFour_ConstantCharToString(int? missingUnimportantWellKnownMember) { var source = """ @@ -3263,144 +3241,86 @@ static void Main() // Instead of emitting this as a span-based concat of strings and chars we recognize "constantChar.ToString()" pattern and lower that arguments to constant strings verifier.VerifyIL("Test.M1", """ { - // Code size 34 (0x22) + // Code size 14 (0xe) .maxstack 4 - .locals init (char V_0) - IL_0000: ldc.i4.s 99 - IL_0002: stloc.0 - IL_0003: ldloca.s V_0 - IL_0005: newobj "System.ReadOnlySpan..ctor(in char)" - IL_000a: ldarg.0 - IL_000b: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_0010: ldarg.0 - IL_0011: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_0016: ldarg.0 - IL_0017: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_001c: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0021: ret + IL_0000: ldstr "c" + IL_0005: ldarg.0 + IL_0006: ldarg.0 + IL_0007: ldarg.0 + IL_0008: call "string string.Concat(string, string, string, string)" + IL_000d: ret } """); verifier.VerifyIL("Test.M2", """ { - // Code size 34 (0x22) + // Code size 14 (0xe) .maxstack 4 - .locals init (char V_0) IL_0000: ldarg.0 - IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_0006: ldc.i4.s 99 - IL_0008: stloc.0 - IL_0009: ldloca.s V_0 - IL_000b: newobj "System.ReadOnlySpan..ctor(in char)" - IL_0010: ldarg.0 - IL_0011: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_0016: ldarg.0 - IL_0017: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_001c: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0021: ret + IL_0001: ldstr "c" + IL_0006: ldarg.0 + IL_0007: ldarg.0 + IL_0008: call "string string.Concat(string, string, string, string)" + IL_000d: ret } """); verifier.VerifyIL("Test.M3", """ { - // Code size 34 (0x22) + // Code size 14 (0xe) .maxstack 4 - .locals init (char V_0) IL_0000: ldarg.0 - IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_0006: ldarg.0 - IL_0007: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_000c: ldc.i4.s 99 - IL_000e: stloc.0 - IL_000f: ldloca.s V_0 - IL_0011: newobj "System.ReadOnlySpan..ctor(in char)" - IL_0016: ldarg.0 - IL_0017: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_001c: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0021: ret + IL_0001: ldarg.0 + IL_0002: ldstr "c" + IL_0007: ldarg.0 + IL_0008: call "string string.Concat(string, string, string, string)" + IL_000d: ret } """); verifier.VerifyIL("Test.M4", """ { - // Code size 34 (0x22) + // Code size 14 (0xe) .maxstack 4 - .locals init (char V_0) IL_0000: ldarg.0 - IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_0006: ldarg.0 - IL_0007: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_000c: ldarg.0 - IL_000d: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_0012: ldc.i4.s 99 - IL_0014: stloc.0 - IL_0015: ldloca.s V_0 - IL_0017: newobj "System.ReadOnlySpan..ctor(in char)" - IL_001c: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0021: ret + IL_0001: ldarg.0 + IL_0002: ldarg.0 + IL_0003: ldstr "c" + IL_0008: call "string string.Concat(string, string, string, string)" + IL_000d: ret } """); verifier.VerifyIL("Test.M5", """ { - // Code size 38 (0x26) + // Code size 18 (0x12) .maxstack 4 - .locals init (char V_0, - char V_1) - IL_0000: ldc.i4.s 99 - IL_0002: stloc.0 - IL_0003: ldloca.s V_0 - IL_0005: newobj "System.ReadOnlySpan..ctor(in char)" - IL_000a: ldarg.0 - IL_000b: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_0010: ldc.i4.s 99 - IL_0012: stloc.1 - IL_0013: ldloca.s V_1 - IL_0015: newobj "System.ReadOnlySpan..ctor(in char)" - IL_001a: ldarg.0 - IL_001b: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_0020: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0025: ret + IL_0000: ldstr "c" + IL_0005: ldarg.0 + IL_0006: ldstr "c" + IL_000b: ldarg.0 + IL_000c: call "string string.Concat(string, string, string, string)" + IL_0011: ret } """); verifier.VerifyIL("Test.M6", """ { - // Code size 38 (0x26) + // Code size 18 (0x12) .maxstack 4 - .locals init (char V_0, - char V_1) IL_0000: ldarg.0 - IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_0006: ldc.i4.s 99 - IL_0008: stloc.0 - IL_0009: ldloca.s V_0 - IL_000b: newobj "System.ReadOnlySpan..ctor(in char)" - IL_0010: ldarg.0 - IL_0011: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_0016: ldc.i4.s 99 - IL_0018: stloc.1 - IL_0019: ldloca.s V_1 - IL_001b: newobj "System.ReadOnlySpan..ctor(in char)" - IL_0020: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0025: ret + IL_0001: ldstr "c" + IL_0006: ldarg.0 + IL_0007: ldstr "c" + IL_000c: call "string string.Concat(string, string, string, string)" + IL_0011: ret } """); verifier.VerifyIL("Test.M7", """ { - // Code size 38 (0x26) + // Code size 18 (0x12) .maxstack 4 - .locals init (char V_0, - char V_1) - IL_0000: ldc.i4.s 99 - IL_0002: stloc.0 - IL_0003: ldloca.s V_0 - IL_0005: newobj "System.ReadOnlySpan..ctor(in char)" - IL_000a: ldarg.0 - IL_000b: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_0010: ldarg.0 - IL_0011: call "System.ReadOnlySpan string.op_Implicit(string)" - IL_0016: ldc.i4.s 99 - IL_0018: stloc.1 - IL_0019: ldloca.s V_1 - IL_001b: newobj "System.ReadOnlySpan..ctor(in char)" - IL_0020: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" - IL_0025: ret + IL_0000: ldstr "c" + IL_0005: ldarg.0 + IL_0006: ldarg.0 + IL_0007: ldstr "c" + IL_000c: call "string string.Concat(string, string, string, string)" + IL_0011: ret } """); } diff --git a/src/Compilers/CSharp/Test/Emit2/Emit/LocalStateTracing/LocalStateTracingTests.cs b/src/Compilers/CSharp/Test/Emit2/Emit/LocalStateTracing/LocalStateTracingTests.cs index be25b5b647774..b7561d7939aaa 100644 --- a/src/Compilers/CSharp/Test/Emit2/Emit/LocalStateTracing/LocalStateTracingTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Emit/LocalStateTracing/LocalStateTracingTests.cs @@ -170,8 +170,8 @@ private static string WithHelpers(string source) [LogLambdaEntry]: Return type is ByRef, TypedReference, ArgHandle, or ArgIterator. { Offset = 0x9 } [LogStateMachineMethodEntry]: Return type is ByRef, TypedReference, ArgHandle, or ArgIterator. { Offset = 0x8 } [LogStateMachineLambdaEntry]: Return type is ByRef, TypedReference, ArgHandle, or ArgIterator. { Offset = 0x8 } - [Entry]: Return type is ByRef, TypedReference, ArgHandle, or ArgIterator. { Offset = 0xd1 } - [MemoryToString]: Unmanaged pointers are not a verifiable type. { Offset = 0xa } + [Entry]: Return type is ByRef, TypedReference, ArgHandle, or ArgIterator. { Offset = 0xbc } + [MemoryToString]: Unmanaged pointers are not a verifiable type. { Offset = 0x5 } [LogLocalStore]: Unmanaged pointers are not a verifiable type. { Offset = 0x1 } [LogLocalStoreUnmanaged]: Unmanaged pointers are not a verifiable type. { Offset = 0x1 } [LogParameterStore]: Unmanaged pointers are not a verifiable type. { Offset = 0x1 } From c09ad7112db97be5eb4fe096af4788e08afb5681 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sun, 18 Feb 2024 16:00:36 +0300 Subject: [PATCH 49/71] Add `ReadOnlySpan` to special types --- .../CSharp/Test/Symbol/Symbols/CompilationCreationTests.cs | 3 ++- .../CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs | 3 ++- .../Test/Symbol/Symbols/Source/ClsComplianceTests.cs | 2 +- src/Compilers/Core/Portable/PublicAPI.Shipped.txt | 2 +- src/Compilers/Core/Portable/PublicAPI.Unshipped.txt | 1 + src/Compilers/Core/Portable/SpecialType.cs | 7 ++++++- src/Compilers/Core/Portable/SpecialTypes.cs | 1 + src/Compilers/Core/Portable/WellKnownMembers.cs | 4 ++-- src/Compilers/Core/Portable/WellKnownTypes.cs | 5 ++--- 9 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/CompilationCreationTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/CompilationCreationTests.cs index a16c746ef12fc..eabbebd3818b7 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/CompilationCreationTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/CompilationCreationTests.cs @@ -80,7 +80,8 @@ public void CorLibTypes() NamedTypeSymbol type = c1.GetSpecialType((SpecialType)i); if (i is (int)SpecialType.System_Runtime_CompilerServices_RuntimeFeature or (int)SpecialType.System_Runtime_CompilerServices_PreserveBaseOverridesAttribute or - (int)SpecialType.System_Runtime_CompilerServices_InlineArrayAttribute) + (int)SpecialType.System_Runtime_CompilerServices_InlineArrayAttribute or + (int)SpecialType.System_ReadOnlySpan_T) { Assert.True(type.IsErrorType()); // Not available } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs index 512b6ca6bd875..1612fe8682137 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs @@ -532,7 +532,8 @@ public void AllSpecialTypes() if (special is SpecialType.System_Runtime_CompilerServices_RuntimeFeature or SpecialType.System_Runtime_CompilerServices_PreserveBaseOverridesAttribute or - SpecialType.System_Runtime_CompilerServices_InlineArrayAttribute) + SpecialType.System_Runtime_CompilerServices_InlineArrayAttribute or + SpecialType.System_ReadOnlySpan_T) { Assert.Equal(SymbolKind.ErrorType, symbol.Kind); // Not available } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/ClsComplianceTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/ClsComplianceTests.cs index 862c6a8f5c116..6b409bd315dd5 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/ClsComplianceTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/ClsComplianceTests.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.CSharp.Symbols; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Test.Utilities; @@ -3166,6 +3165,7 @@ public class C case SpecialType.System_Runtime_CompilerServices_RuntimeFeature: // static and not available case SpecialType.System_Runtime_CompilerServices_PreserveBaseOverridesAttribute: // not available case SpecialType.System_Runtime_CompilerServices_InlineArrayAttribute: // not available + case SpecialType.System_ReadOnlySpan_T: // not available continue; } diff --git a/src/Compilers/Core/Portable/PublicAPI.Shipped.txt b/src/Compilers/Core/Portable/PublicAPI.Shipped.txt index 496cc20f14b21..c7de3bf377049 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Shipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Shipped.txt @@ -2542,7 +2542,7 @@ Microsoft.CodeAnalysis.SourceProductionContext.SourceProductionContext() -> void Microsoft.CodeAnalysis.SourceReferenceResolver Microsoft.CodeAnalysis.SourceReferenceResolver.SourceReferenceResolver() -> void Microsoft.CodeAnalysis.SpecialType -Microsoft.CodeAnalysis.SpecialType.Count = 46 -> Microsoft.CodeAnalysis.SpecialType +Microsoft.CodeAnalysis.SpecialType.Count = 47 -> Microsoft.CodeAnalysis.SpecialType Microsoft.CodeAnalysis.SpecialType.None = 0 -> Microsoft.CodeAnalysis.SpecialType Microsoft.CodeAnalysis.SpecialType.System_ArgIterator = 37 -> Microsoft.CodeAnalysis.SpecialType Microsoft.CodeAnalysis.SpecialType.System_Array = 23 -> Microsoft.CodeAnalysis.SpecialType diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index 4f36140c8df39..91b269a7a6dc5 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -8,6 +8,7 @@ Microsoft.CodeAnalysis.Operations.ISpreadOperation Microsoft.CodeAnalysis.Operations.ISpreadOperation.ElementConversion.get -> Microsoft.CodeAnalysis.Operations.CommonConversion Microsoft.CodeAnalysis.Operations.ISpreadOperation.ElementType.get -> Microsoft.CodeAnalysis.ITypeSymbol? Microsoft.CodeAnalysis.Operations.ISpreadOperation.Operand.get -> Microsoft.CodeAnalysis.IOperation! +Microsoft.CodeAnalysis.SpecialType.System_ReadOnlySpan_T = 47 -> Microsoft.CodeAnalysis.SpecialType virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitCollectionExpression(Microsoft.CodeAnalysis.Operations.ICollectionExpressionOperation! operation) -> void virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitSpread(Microsoft.CodeAnalysis.Operations.ISpreadOperation! operation) -> void virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitCollectionExpression(Microsoft.CodeAnalysis.Operations.ICollectionExpressionOperation! operation, TArgument argument) -> TResult? diff --git a/src/Compilers/Core/Portable/SpecialType.cs b/src/Compilers/Core/Portable/SpecialType.cs index 66e5b3eb9d84d..b64ba45ae3ae0 100644 --- a/src/Compilers/Core/Portable/SpecialType.cs +++ b/src/Compilers/Core/Portable/SpecialType.cs @@ -263,9 +263,14 @@ public enum SpecialType : sbyte /// System_Runtime_CompilerServices_InlineArrayAttribute = 46, + /// + /// Indicates that the type is + /// + System_ReadOnlySpan_T = 47, + /// /// Count of special types. This is not a count of enum members. /// - Count = System_Runtime_CompilerServices_InlineArrayAttribute + Count = System_ReadOnlySpan_T } } diff --git a/src/Compilers/Core/Portable/SpecialTypes.cs b/src/Compilers/Core/Portable/SpecialTypes.cs index 163237fbfd8f3..0d1ae1dd58ab7 100644 --- a/src/Compilers/Core/Portable/SpecialTypes.cs +++ b/src/Compilers/Core/Portable/SpecialTypes.cs @@ -70,6 +70,7 @@ internal static class SpecialTypes "System.Runtime.CompilerServices.RuntimeFeature", "System.Runtime.CompilerServices.PreserveBaseOverridesAttribute", "System.Runtime.CompilerServices.InlineArrayAttribute", + "System.ReadOnlySpan`1", }; private static readonly Dictionary s_nameToTypeIdMap; diff --git a/src/Compilers/Core/Portable/WellKnownMembers.cs b/src/Compilers/Core/Portable/WellKnownMembers.cs index 1047fdf76a5d6..9bf6adb71985e 100644 --- a/src/Compilers/Core/Portable/WellKnownMembers.cs +++ b/src/Compilers/Core/Portable/WellKnownMembers.cs @@ -2654,7 +2654,7 @@ static WellKnownMembers() // System_ValueTuple_T1__Item1 (byte)MemberFlags.Field, // Flags - (byte)WellKnownType.System_ValueTuple_T1, // DeclaringTypeId + (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_ValueTuple_T1 - WellKnownType.ExtSentinel), // DeclaringTypeId 0, // Arity (byte)SignatureTypeCode.GenericTypeParameter, 0, // Field Signature @@ -2870,7 +2870,7 @@ static WellKnownMembers() // System_ValueTuple_T1__ctor (byte)MemberFlags.Constructor, // Flags - (byte)WellKnownType.System_ValueTuple_T1, // DeclaringTypeId + (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_ValueTuple_T1 - WellKnownType.ExtSentinel), // DeclaringTypeId 0, // Arity 1, // Method Signature (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, // Return Type diff --git a/src/Compilers/Core/Portable/WellKnownTypes.cs b/src/Compilers/Core/Portable/WellKnownTypes.cs index 690a8b4feac8b..833341385b53c 100644 --- a/src/Compilers/Core/Portable/WellKnownTypes.cs +++ b/src/Compilers/Core/Portable/WellKnownTypes.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Collections.Generic; using System.Diagnostics; @@ -252,10 +251,10 @@ internal enum WellKnownType CSharp7Sentinel = System_IFormatProvider, // all types that were known before CSharp7 should remain above this sentinel System_ValueTuple, - System_ValueTuple_T1, ExtSentinel, // Not a real type, just a marker for types above 255 and strictly below 512 + System_ValueTuple_T1, System_ValueTuple_T2, System_ValueTuple_T3, System_ValueTuple_T4, @@ -581,10 +580,10 @@ internal static class WellKnownTypes "System.IFormatProvider", "System.ValueTuple", - "System.ValueTuple`1", "", // extension marker + "System.ValueTuple`1", "System.ValueTuple`2", "System.ValueTuple`3", "System.ValueTuple`4", From 0b650eedb159b20abbe18f15c77a4156e343a493 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sun, 18 Feb 2024 17:38:35 +0300 Subject: [PATCH 50/71] Move to special members --- .../LocalRewriter_StringConcat.cs | 22 +- .../Portable/Lowering/SpillSequenceSpiller.cs | 4 +- .../CodeGenSpanBasedStringConcatTests.cs | 336 +++++++++--------- .../Symbol/Symbols/MissingSpecialMember.cs | 14 +- src/Compilers/Core/Portable/SpecialMember.cs | 8 + src/Compilers/Core/Portable/SpecialMembers.cs | 71 ++++ .../Core/Portable/WellKnownMember.cs | 6 - .../Core/Portable/WellKnownMembers.cs | 82 ----- .../WellKnownTypeValidationTests.vb | 10 - 9 files changed, 267 insertions(+), 286 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index 5d317d195edb2..9c9b76ce60445 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -215,9 +215,9 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr break; case BoundSequence { SideEffects.Length: 0, Value: BoundCall sequenceCall } sequence: - if ((object)sequenceCall.Method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_2ReadOnlySpans) || - (object)sequenceCall.Method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_3ReadOnlySpans) || - (object)sequenceCall.Method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_4ReadOnlySpans)) + if ((object)sequenceCall.Method == _compilation.GetSpecialTypeMember(SpecialMember.System_String__Concat_2ReadOnlySpans) || + (object)sequenceCall.Method == _compilation.GetSpecialTypeMember(SpecialMember.System_String__Concat_3ReadOnlySpans) || + (object)sequenceCall.Method == _compilation.GetSpecialTypeMember(SpecialMember.System_String__Concat_4ReadOnlySpans)) { // Faced a span-based `string.Concat` call. Since we can produce such call on the previous iterations ourselves, we need to unwrap it. // The key thing is that we need not to only extract arguments, but also unwrap them from being spans and for chars also wrap them into `ToString` calls. @@ -232,7 +232,7 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr switch (wrappedArg) { // Check whether a call is an implicit `string -> ReadOnlySpan` conversion - case BoundCall { Method: var argMethod, Arguments: [var singleArgument] } when (object)argMethod == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar): + case BoundCall { Method: var argMethod, Arguments: [var singleArgument] } when (object)argMethod == _compilation.GetSpecialTypeMember(SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar): unwrappedArgsBuilder.Add(singleArgument); break; // This complicated check is for a sequence, which wraps a span around single char. @@ -244,7 +244,7 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr Value: BoundObjectCreationExpression { Constructor: var objectCreationConstructor, Arguments: [BoundLocal constructorLocal] } } when constructorLocal == assignment.Left && locals.Remove(constructorLocal.LocalSymbol) && - (object)objectCreationConstructor.OriginalDefinition == _compilation.GetWellKnownTypeMember(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference) && + (object)objectCreationConstructor.OriginalDefinition == _compilation.GetSpecialTypeMember(SpecialMember.System_ReadOnlySpan_T__ctor_Reference) && objectCreationConstructor.ContainingType.IsReadOnlySpanChar(): var wrappedExpr = ConvertConcatExprToString(assignment.Right); unwrappedArgsBuilder.Add(wrappedExpr); @@ -466,13 +466,13 @@ private bool TryRewriteStringConcatenationWithSpanBasedConcat(SyntaxNode syntax, var concatMember = preparedArgs.Count switch { - 2 => WellKnownMember.System_String__Concat_2ReadOnlySpans, - 3 => WellKnownMember.System_String__Concat_3ReadOnlySpans, - 4 => WellKnownMember.System_String__Concat_4ReadOnlySpans, + 2 => SpecialMember.System_String__Concat_2ReadOnlySpans, + 3 => SpecialMember.System_String__Concat_3ReadOnlySpans, + 4 => SpecialMember.System_String__Concat_4ReadOnlySpans, _ => throw ExceptionUtilities.Unreachable(), }; - if (TryGetWellKnownTypeMember(syntax, concatMember, out MethodSymbol? spanConcat, isOptional: true) && + if (TryGetSpecialTypeMethod(syntax, concatMember, out MethodSymbol? spanConcat, isOptional: true) && tryGetNeededToSpanMembers(this, syntax, needsImplicitConversionFromStringToSpan, charType, out MethodSymbol? readOnlySpanCtorRefParamChar, out MethodSymbol? stringImplicitConversionToReadOnlySpan)) { result = rewriteStringConcatenationWithSpanBasedConcat( @@ -500,7 +500,7 @@ static bool tryGetNeededToSpanMembers( readOnlySpanCtorRefParamChar = null; stringImplicitConversionToReadOnlySpan = null; - if (self.TryGetWellKnownTypeMember(syntax, WellKnownMember.System_ReadOnlySpan_T__ctor_Reference, out MethodSymbol? readOnlySpanCtorRefParamGeneric, isOptional: true) && + if (self.TryGetSpecialTypeMethod(syntax, SpecialMember.System_ReadOnlySpan_T__ctor_Reference, out MethodSymbol? readOnlySpanCtorRefParamGeneric, isOptional: true) && readOnlySpanCtorRefParamGeneric.Parameters[0].RefKind != RefKind.Out) { var readOnlySpanOfChar = readOnlySpanCtorRefParamGeneric.ContainingType.Construct(charType); @@ -513,7 +513,7 @@ static bool tryGetNeededToSpanMembers( if (needsImplicitConversionFromStringToSpan) { - return self.TryGetWellKnownTypeMember(syntax, WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar, out stringImplicitConversionToReadOnlySpan, isOptional: true); + return self.TryGetSpecialTypeMethod(syntax, SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar, out stringImplicitConversionToReadOnlySpan, isOptional: true); } return true; diff --git a/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs b/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs index f704dca8f8134..922dc8bcfa30a 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs @@ -468,7 +468,7 @@ private BoundExpression Spill( call.Method, ImmutableArray.Create(Spill(builder, call.Arguments[0]), Spill(builder, call.Arguments[1]))); } - else if (call.Method == _F.Compilation.GetWellKnownTypeMember(WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)) + else if (call.Method == _F.Compilation.GetSpecialTypeMember(SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar)) { Debug.Assert(call.Arguments.Length == 1); return call.Update([Spill(builder, call.Arguments[0])]); @@ -481,7 +481,7 @@ private BoundExpression Spill( if (refKind == RefKind.None && objectCreationExpression.InitializerExpressionOpt is null && - objectCreationExpression.Constructor.OriginalDefinition == _F.Compilation.GetWellKnownTypeMember(WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)) + objectCreationExpression.Constructor.OriginalDefinition == _F.Compilation.GetSpecialTypeMember(SpecialMember.System_ReadOnlySpan_T__ctor_Reference)) { Debug.Assert(objectCreationExpression.Arguments.Length == 1); var argRefKinds = objectCreationExpression.ArgumentRefKindsOpt; diff --git a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs index 87480e9f490aa..b8d855f71f8cd 100644 --- a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs @@ -306,10 +306,10 @@ .locals init (char V_0, //c [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] - public void ConcatTwo_ConstantCharToString(int? missingUnimportantWellKnownMember) + [InlineData((int)SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)SpecialMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + public void ConcatTwo_ConstantCharToString(int? missingUnimportantMember) { var source = """ using System; @@ -330,9 +330,9 @@ static void Main() var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - if (missingUnimportantWellKnownMember.HasValue) + if (missingUnimportantMember.HasValue) { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.Value); } var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "sccs" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); @@ -364,10 +364,10 @@ .maxstack 2 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] - public void ConcatTwo_AllConstantCharToStrings(int? missingUnimportantWellKnownMember) + [InlineData((int)SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)SpecialMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + public void ConcatTwo_AllConstantCharToStrings(int? missingUnimportantMember) { var source = """ using System; @@ -385,9 +385,9 @@ static void Main() var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - if (missingUnimportantWellKnownMember.HasValue) + if (missingUnimportantMember.HasValue) { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.Value); } var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "ab" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); @@ -408,8 +408,8 @@ .maxstack 1 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - public void ConcatTwoCharToStrings(int? missingUnimportantWellKnownMember) + [InlineData((int)SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + public void ConcatTwoCharToStrings(int? missingUnimportantMember) { var source = """ using System; @@ -429,9 +429,9 @@ static void Main() var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - if (missingUnimportantWellKnownMember.HasValue) + if (missingUnimportantMember.HasValue) { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.Value); } var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "ab" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); @@ -458,9 +458,9 @@ .locals init (char V_0, } [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] - [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + [InlineData((int)SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)SpecialMember.System_ReadOnlySpan_T__ctor_Reference)] public void ConcatTwo_ReadOnlySpan_MissingMemberForOptimization(int member) { var source = """ @@ -482,7 +482,7 @@ static void Main() """; var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - comp.MakeMemberMissing((WellKnownMember)member); + comp.MakeMemberMissing((SpecialMember)member); var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "sccs" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); @@ -923,8 +923,8 @@ .locals init (int V_0, [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] - public void ConcatThree_ReadOnlySpan1(int? missingUnimportantWellKnownMember) + [InlineData((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + public void ConcatThree_ReadOnlySpan1(int? missingUnimportantMember) { var source = """ using System; @@ -950,9 +950,9 @@ static void Main() var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - if (missingUnimportantWellKnownMember.HasValue) + if (missingUnimportantMember.HasValue) { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.Value); } var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "cssscsssccsc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); @@ -1033,8 +1033,8 @@ .locals init (char V_0, [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] - public void ConcatThree_ReadOnlySpan2(int? missingUnimportantWellKnownMember) + [InlineData((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + public void ConcatThree_ReadOnlySpan2(int? missingUnimportantMember) { var source = """ using System; @@ -1060,9 +1060,9 @@ static void Main() var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - if (missingUnimportantWellKnownMember.HasValue) + if (missingUnimportantMember.HasValue) { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.Value); } var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "cssscsssccsc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); @@ -1193,8 +1193,8 @@ .locals init (char V_0) [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] - public void ConcatThree_ReadOnlySpan_SideEffect(int? missingUnimportantWellKnownMember) + [InlineData((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + public void ConcatThree_ReadOnlySpan_SideEffect(int? missingUnimportantMember) { var source = """ using System; @@ -1240,9 +1240,9 @@ private static char GetCharWithSideEffect() var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - if (missingUnimportantWellKnownMember.HasValue) + if (missingUnimportantMember.HasValue) { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.Value); } var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? expectedOutput : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); @@ -1323,8 +1323,8 @@ .locals init (char V_0, [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] - public void ConcatThree_ReadOnlySpan_ReferenceToSameLocation(int? missingUnimportantWellKnownMember) + [InlineData((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + public void ConcatThree_ReadOnlySpan_ReferenceToSameLocation(int? missingUnimportantMember) { var source = """ using System; @@ -1351,9 +1351,9 @@ public void M() var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - if (missingUnimportantWellKnownMember.HasValue) + if (missingUnimportantMember.HasValue) { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.Value); } var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "aab" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); @@ -1390,8 +1390,8 @@ .locals init (char V_0, [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] - public void ConcatThree_ReadOnlySpan_MutateLocal(int? missingUnimportantWellKnownMember) + [InlineData((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + public void ConcatThree_ReadOnlySpan_MutateLocal(int? missingUnimportantMember) { var source = """ using System; @@ -1417,9 +1417,9 @@ private char SneakyLocalChange(ref char local) var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - if (missingUnimportantWellKnownMember.HasValue) + if (missingUnimportantMember.HasValue) { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.Value); } var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "aab" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); @@ -1457,11 +1457,11 @@ .locals init (char V_0, //c [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] - [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] - public void ConcatThree_ConstantCharToString(int? missingUnimportantWellKnownMember) + [InlineData((int)SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)SpecialMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + [InlineData((int)SpecialMember.System_String__Concat_3ReadOnlySpans)] + public void ConcatThree_ConstantCharToString(int? missingUnimportantMember) { var source = """ using System; @@ -1486,9 +1486,9 @@ static void Main() var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - if (missingUnimportantWellKnownMember.HasValue) + if (missingUnimportantMember.HasValue) { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.Value); } var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "cssscsssccsc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); @@ -1544,11 +1544,11 @@ .maxstack 3 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] - [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] - public void ConcatThree_AllConstantCharToStrings(int? missingUnimportantWellKnownMember) + [InlineData((int)SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)SpecialMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + [InlineData((int)SpecialMember.System_String__Concat_3ReadOnlySpans)] + public void ConcatThree_AllConstantCharToStrings(int? missingUnimportantMember) { var source = """ using System; @@ -1566,9 +1566,9 @@ static void Main() var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - if (missingUnimportantWellKnownMember.HasValue) + if (missingUnimportantMember.HasValue) { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.Value); } var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); @@ -1589,9 +1589,9 @@ .maxstack 1 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] - public void ConcatThreeCharToStrings(int? missingUnimportantWellKnownMember) + [InlineData((int)SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + public void ConcatThreeCharToStrings(int? missingUnimportantMember) { var source = """ using System; @@ -1612,9 +1612,9 @@ static void Main() var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - if (missingUnimportantWellKnownMember.HasValue) + if (missingUnimportantMember.HasValue) { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.Value); } var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); @@ -1646,9 +1646,9 @@ .locals init (char V_0, } [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] - [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)SpecialMember.System_String__Concat_3ReadOnlySpans)] + [InlineData((int)SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)SpecialMember.System_ReadOnlySpan_T__ctor_Reference)] public void ConcatThree_ReadOnlySpan_MissingMemberForOptimization(int member) { var source = """ @@ -1674,7 +1674,7 @@ static void Main() """; var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - comp.MakeMemberMissing((WellKnownMember)member); + comp.MakeMemberMissing((SpecialMember)member); var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "cssscsssccsc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); @@ -1732,10 +1732,10 @@ .maxstack 3 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] - public void ConcatThree_UserInputOfSpanBasedConcat_ConcatWithString(int? missingUnimportantWellKnownMember) + [InlineData((int)SpecialMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + public void ConcatThree_UserInputOfSpanBasedConcat_ConcatWithString(int? missingUnimportantMember) { var source = """ using System; @@ -1758,9 +1758,9 @@ static void Main() var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - if (missingUnimportantWellKnownMember.HasValue) + if (missingUnimportantMember.HasValue) { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.Value); } var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abccab" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); @@ -1858,8 +1858,8 @@ .locals init (char V_0) } [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] - [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)SpecialMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] public void ConcatThree_UserInputOfSpanBasedConcat_ConcatWithChar_MissingMemberForSpanBasedConcat(int member) { var source = """ @@ -1882,7 +1882,7 @@ static void Main() """; var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - comp.MakeMemberMissing((WellKnownMember)member); + comp.MakeMemberMissing((SpecialMember)member); var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abccab" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); @@ -2391,9 +2391,9 @@ .locals init (int V_0, [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] - [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] - public void ConcatFour_ReadOnlySpan1(int? missingUnimportantWellKnownMember) + [InlineData((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + [InlineData((int)SpecialMember.System_String__Concat_3ReadOnlySpans)] + public void ConcatFour_ReadOnlySpan1(int? missingUnimportantMember) { var source = """ using System; @@ -2425,9 +2425,9 @@ static void Main() var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - if (missingUnimportantWellKnownMember.HasValue) + if (missingUnimportantMember.HasValue) { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.Value); } var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "csssscsssscssssccscsscsccssc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); @@ -2579,9 +2579,9 @@ .locals init (char V_0, [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] - [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] - public void ConcatFour_ReadOnlySpan2(int? missingUnimportantWellKnownMember) + [InlineData((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + [InlineData((int)SpecialMember.System_String__Concat_3ReadOnlySpans)] + public void ConcatFour_ReadOnlySpan2(int? missingUnimportantMember) { var source = """ using System; @@ -2613,9 +2613,9 @@ static void Main() var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - if (missingUnimportantWellKnownMember.HasValue) + if (missingUnimportantMember.HasValue) { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.Value); } var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "csssscsssscssssccscsscsccssc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); @@ -2832,9 +2832,9 @@ .locals init (char V_0) [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] - [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] - public void ConcatFour_ReadOnlySpan_SideEffect(int? missingUnimportantWellKnownMember) + [InlineData((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + [InlineData((int)SpecialMember.System_String__Concat_3ReadOnlySpans)] + public void ConcatFour_ReadOnlySpan_SideEffect(int? missingUnimportantMember) { var source = """ using System; @@ -2889,9 +2889,9 @@ private static char GetCharWithSideEffect() var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - if (missingUnimportantWellKnownMember.HasValue) + if (missingUnimportantMember.HasValue) { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.Value); } var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? expectedOutput : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); @@ -3043,9 +3043,9 @@ .locals init (char V_0, [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] - [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] - public void ConcatFour_ReadOnlySpan_ReferenceToSameLocation(int? missingUnimportantWellKnownMember) + [InlineData((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + [InlineData((int)SpecialMember.System_String__Concat_3ReadOnlySpans)] + public void ConcatFour_ReadOnlySpan_ReferenceToSameLocation(int? missingUnimportantMember) { var source = """ using System; @@ -3074,9 +3074,9 @@ public void M() var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - if (missingUnimportantWellKnownMember.HasValue) + if (missingUnimportantMember.HasValue) { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.Value); } var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "aaab" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); @@ -3120,9 +3120,9 @@ .locals init (char V_0, [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] - [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] - public void ConcatFour_ReadOnlySpan_MutateLocal(int? missingUnimportantWellKnownMember) + [InlineData((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + [InlineData((int)SpecialMember.System_String__Concat_3ReadOnlySpans)] + public void ConcatFour_ReadOnlySpan_MutateLocal(int? missingUnimportantMember) { var source = """ using System; @@ -3149,9 +3149,9 @@ private string SneakyLocalChange(ref char local) var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - if (missingUnimportantWellKnownMember.HasValue) + if (missingUnimportantMember.HasValue) { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.Value); } var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abab" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); @@ -3193,12 +3193,12 @@ .locals init (char V_0, //c1 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] - [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] - [InlineData((int)WellKnownMember.System_String__Concat_4ReadOnlySpans)] - public void ConcatFour_ConstantCharToString(int? missingUnimportantWellKnownMember) + [InlineData((int)SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)SpecialMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + [InlineData((int)SpecialMember.System_String__Concat_3ReadOnlySpans)] + [InlineData((int)SpecialMember.System_String__Concat_4ReadOnlySpans)] + public void ConcatFour_ConstantCharToString(int? missingUnimportantMember) { var source = """ using System; @@ -3229,9 +3229,9 @@ static void Main() var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - if (missingUnimportantWellKnownMember.HasValue) + if (missingUnimportantMember.HasValue) { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.Value); } var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "csssscsssscssssccscsscsccssc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); @@ -3327,12 +3327,12 @@ .maxstack 4 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] - [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] - [InlineData((int)WellKnownMember.System_String__Concat_4ReadOnlySpans)] - public void ConcatFour_AllConstantCharToStrings(int? missingUnimportantWellKnownMember) + [InlineData((int)SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)SpecialMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + [InlineData((int)SpecialMember.System_String__Concat_3ReadOnlySpans)] + [InlineData((int)SpecialMember.System_String__Concat_4ReadOnlySpans)] + public void ConcatFour_AllConstantCharToStrings(int? missingUnimportantMember) { var source = """ using System; @@ -3350,9 +3350,9 @@ static void Main() var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - if (missingUnimportantWellKnownMember.HasValue) + if (missingUnimportantMember.HasValue) { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.Value); } var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abcd" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); @@ -3373,10 +3373,10 @@ .maxstack 1 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] - [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] - public void ConcatFourCharToStrings(int? missingUnimportantWellKnownMember) + [InlineData((int)SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + [InlineData((int)SpecialMember.System_String__Concat_3ReadOnlySpans)] + public void ConcatFourCharToStrings(int? missingUnimportantMember) { var source = """ using System; @@ -3398,9 +3398,9 @@ static void Main() var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - if (missingUnimportantWellKnownMember.HasValue) + if (missingUnimportantMember.HasValue) { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.Value); } var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abcd" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); @@ -3437,9 +3437,9 @@ .locals init (char V_0, } [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] - [InlineData((int)WellKnownMember.System_String__Concat_4ReadOnlySpans)] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)SpecialMember.System_String__Concat_4ReadOnlySpans)] + [InlineData((int)SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)SpecialMember.System_ReadOnlySpan_T__ctor_Reference)] public void ConcatFour_ReadOnlySpan_MissingMemberForOptimization(int member) { var source = """ @@ -3471,7 +3471,7 @@ static void Main() """; var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - comp.MakeMemberMissing((WellKnownMember)member); + comp.MakeMemberMissing((SpecialMember)member); var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "csssscsssscssssccscsscsccssc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); @@ -3574,10 +3574,10 @@ .maxstack 4 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] - public void ConcatFour_UserInputOfSpanBasedConcatOf2_ConcatWithString(int? missingUnimportantWellKnownMember) + [InlineData((int)SpecialMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)SpecialMember.System_String__Concat_3ReadOnlySpans)] + public void ConcatFour_UserInputOfSpanBasedConcatOf2_ConcatWithString(int? missingUnimportantMember) { var source = """ using System; @@ -3602,9 +3602,9 @@ static void Main() var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - if (missingUnimportantWellKnownMember.HasValue) + if (missingUnimportantMember.HasValue) { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.Value); } var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abccccabcabc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); @@ -3910,9 +3910,9 @@ .locals init (char V_0) [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - public void ConcatFour_TwoUserInputsOfSpanBasedConcatOf2(int? missingUnimportantWellKnownMember) + [InlineData((int)SpecialMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + public void ConcatFour_TwoUserInputsOfSpanBasedConcatOf2(int? missingUnimportantMember) { var source = """ using System; @@ -3934,9 +3934,9 @@ static void Main() var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - if (missingUnimportantWellKnownMember.HasValue) + if (missingUnimportantMember.HasValue) { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.Value); } var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abcd" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); @@ -3960,10 +3960,10 @@ .maxstack 3 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] - public void ConcatFour_UserInputOfSpanBasedConcatOf3_ConcatWithString(int? missingUnimportantWellKnownMember) + [InlineData((int)SpecialMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + public void ConcatFour_UserInputOfSpanBasedConcatOf3_ConcatWithString(int? missingUnimportantMember) { var source = """ using System; @@ -3987,9 +3987,9 @@ static void Main() var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - if (missingUnimportantWellKnownMember.HasValue) + if (missingUnimportantMember.HasValue) { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.Value); } var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abcddabc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); @@ -4092,9 +4092,9 @@ .locals init (char V_0) } [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] - [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] + [InlineData((int)SpecialMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)SpecialMember.System_String__Concat_3ReadOnlySpans)] public void ConcatFour_UserInputOfSpanBasedConcatOf2_ConcatWithChar_MissingMemberForSpanBasedConcatConcat(int member) { var source = """ @@ -4119,7 +4119,7 @@ static void Main() """; var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - comp.MakeMemberMissing((WellKnownMember)member); + comp.MakeMemberMissing((SpecialMember)member); var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abccccabcabc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); @@ -4175,9 +4175,9 @@ .maxstack 3 } [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] - [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] + [InlineData((int)SpecialMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)SpecialMember.System_String__Concat_3ReadOnlySpans)] public void ConcatFour_UserInputOfSpanBasedConcatOf2_ConcatWithStringAndChar_MissingMemberForSpanBasedConcat(int member) { var source = """ @@ -4209,7 +4209,7 @@ static void Main() """; var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - comp.MakeMemberMissing((WellKnownMember)member); + comp.MakeMemberMissing((SpecialMember)member); var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abcdabdccdabdcabcabddabc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); @@ -4307,9 +4307,9 @@ .maxstack 3 } [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] - [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] + [InlineData((int)SpecialMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] public void ConcatFour_UserInputOfSpanBasedConcatOf3_ConcatWithChar_MissingMemberForSpanBasedConcat(int member) { var source = """ @@ -4333,7 +4333,7 @@ static void Main() """; var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - comp.MakeMemberMissing((WellKnownMember)member); + comp.MakeMemberMissing((SpecialMember)member); var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abcddabc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); @@ -4915,12 +4915,12 @@ .locals init (int V_0, [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] - [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] - [InlineData((int)WellKnownMember.System_String__Concat_4ReadOnlySpans)] - public void ConcatFive_Char(int? missingUnimportantWellKnownMember) + [InlineData((int)SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)SpecialMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + [InlineData((int)SpecialMember.System_String__Concat_3ReadOnlySpans)] + [InlineData((int)SpecialMember.System_String__Concat_4ReadOnlySpans)] + public void ConcatFive_Char(int? missingUnimportantMember) { var source = """ using System; @@ -4942,9 +4942,9 @@ static void Main() var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - if (missingUnimportantWellKnownMember.HasValue) + if (missingUnimportantMember.HasValue) { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.Value); } var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "scsssssssc" : null, verify: ExecutionConditionUtil.IsCoreClr ? default : Verification.Skipped); @@ -5104,12 +5104,12 @@ .maxstack 4 [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] - [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] - [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] - [InlineData((int)WellKnownMember.System_String__Concat_2ReadOnlySpans)] - [InlineData((int)WellKnownMember.System_String__Concat_3ReadOnlySpans)] - [InlineData((int)WellKnownMember.System_String__Concat_4ReadOnlySpans)] - public void ConcatFiveCharToStrings(int? missingUnimportantWellKnownMember) + [InlineData((int)SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)SpecialMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + [InlineData((int)SpecialMember.System_String__Concat_3ReadOnlySpans)] + [InlineData((int)SpecialMember.System_String__Concat_4ReadOnlySpans)] + public void ConcatFiveCharToStrings(int? missingUnimportantMember) { var source = """ using System; @@ -5132,9 +5132,9 @@ static void Main() var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); - if (missingUnimportantWellKnownMember.HasValue) + if (missingUnimportantMember.HasValue) { - comp.MakeMemberMissing((WellKnownMember)missingUnimportantWellKnownMember.Value); + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.Value); } var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abcde" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs index 1612fe8682137..e01ce105c20e7 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs @@ -555,14 +555,19 @@ public void AllSpecialTypeMembers() if (special == SpecialMember.Count) continue; // Not a real value; var symbol = comp.GetSpecialTypeMember(special); - if (special == SpecialMember.System_Runtime_CompilerServices_RuntimeFeature__DefaultImplementationsOfInterfaces + if (special == SpecialMember.System_String__Concat_2ReadOnlySpans + || special == SpecialMember.System_String__Concat_3ReadOnlySpans + || special == SpecialMember.System_String__Concat_4ReadOnlySpans + || special == SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar + || special == SpecialMember.System_Runtime_CompilerServices_RuntimeFeature__DefaultImplementationsOfInterfaces || special == SpecialMember.System_Runtime_CompilerServices_RuntimeFeature__CovariantReturnsOfClasses || special == SpecialMember.System_Runtime_CompilerServices_RuntimeFeature__VirtualStaticsInInterfaces || special == SpecialMember.System_Runtime_CompilerServices_RuntimeFeature__UnmanagedSignatureCallingConvention || special == SpecialMember.System_Runtime_CompilerServices_RuntimeFeature__NumericIntPtr || special == SpecialMember.System_Runtime_CompilerServices_RuntimeFeature__ByRefFields || special == SpecialMember.System_Runtime_CompilerServices_PreserveBaseOverridesAttribute__ctor - || special == SpecialMember.System_Runtime_CompilerServices_InlineArrayAttribute__ctor) + || special == SpecialMember.System_Runtime_CompilerServices_InlineArrayAttribute__ctor + || special == SpecialMember.System_ReadOnlySpan_T__ctor_Reference) { Assert.Null(symbol); // Not available } @@ -959,14 +964,9 @@ public void AllWellKnownTypeMembers() case WellKnownMember.System_ReadOnlySpan_T__ctor_Pointer: case WellKnownMember.System_ReadOnlySpan_T__ctor_Array: case WellKnownMember.System_ReadOnlySpan_T__ctor_Array_Start_Length: - case WellKnownMember.System_ReadOnlySpan_T__ctor_Reference: case WellKnownMember.System_ReadOnlySpan_T__get_Item: case WellKnownMember.System_ReadOnlySpan_T__get_Length: case WellKnownMember.System_ReadOnlySpan_T__Slice_Int_Int: - case WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar: - case WellKnownMember.System_String__Concat_2ReadOnlySpans: - case WellKnownMember.System_String__Concat_3ReadOnlySpans: - case WellKnownMember.System_String__Concat_4ReadOnlySpans: case WellKnownMember.System_Index__ctor: case WellKnownMember.System_Index__GetOffset: case WellKnownMember.System_Range__ctor: diff --git a/src/Compilers/Core/Portable/SpecialMember.cs b/src/Compilers/Core/Portable/SpecialMember.cs index 67d1817f74472..b75843664d326 100644 --- a/src/Compilers/Core/Portable/SpecialMember.cs +++ b/src/Compilers/Core/Portable/SpecialMember.cs @@ -19,6 +19,10 @@ internal enum SpecialMember System_String__ConcatObjectObjectObject, System_String__ConcatObjectArray, + System_String__Concat_2ReadOnlySpans, + System_String__Concat_3ReadOnlySpans, + System_String__Concat_4ReadOnlySpans, + System_String__op_Equality, System_String__op_Inequality, System_String__Length, @@ -26,6 +30,8 @@ internal enum SpecialMember System_String__Format, System_String__Substring, + System_String__op_Implicit_ToReadOnlySpanOfChar, + System_Double__IsNaN, System_Single__IsNaN, @@ -159,6 +165,8 @@ internal enum SpecialMember System_Runtime_CompilerServices_PreserveBaseOverridesAttribute__ctor, System_Runtime_CompilerServices_InlineArrayAttribute__ctor, + System_ReadOnlySpan_T__ctor_Reference, + Count } } diff --git a/src/Compilers/Core/Portable/SpecialMembers.cs b/src/Compilers/Core/Portable/SpecialMembers.cs index a84c53e905f9d..776dfbb7d5b0b 100644 --- a/src/Compilers/Core/Portable/SpecialMembers.cs +++ b/src/Compilers/Core/Portable/SpecialMembers.cs @@ -97,6 +97,54 @@ static SpecialMembers() (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, (byte)SignatureTypeCode.SZArray, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Object, + // System_String__Concat_2ReadOnlySpans + (byte)(MemberFlags.Method | MemberFlags.Static), // Flags + (byte)SpecialType.System_String, // DeclaringTypeId + 0, // Arity + 2, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, + (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_ReadOnlySpan_T, + 1, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, + (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_ReadOnlySpan_T, + 1, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, + + // System_String__Concat_3ReadOnlySpans + (byte)(MemberFlags.Method | MemberFlags.Static), // Flags + (byte)SpecialType.System_String, // DeclaringTypeId + 0, // Arity + 3, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, + (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_ReadOnlySpan_T, + 1, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, + (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_ReadOnlySpan_T, + 1, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, + (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_ReadOnlySpan_T, + 1, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, + + // System_String__Concat_4ReadOnlySpans + (byte)(MemberFlags.Method | MemberFlags.Static), // Flags + (byte)SpecialType.System_String, // DeclaringTypeId + 0, // Arity + 4, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, + (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_ReadOnlySpan_T, + 1, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, + (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_ReadOnlySpan_T, + 1, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, + (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_ReadOnlySpan_T, + 1, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, + (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_ReadOnlySpan_T, + 1, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, + // System_String__op_Equality (byte)(MemberFlags.Method | MemberFlags.Static), // Flags (byte)SpecialType.System_String, // DeclaringTypeId @@ -148,6 +196,16 @@ static SpecialMembers() (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Int32, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Int32, + // System_String__op_Implicit_ToReadOnlySpanOfChar + (byte)(MemberFlags.Method | MemberFlags.Static), // Flags + (byte)SpecialType.System_String, // DeclaringTypeId + 0, // Arity + 1, // Method Signature + (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_ReadOnlySpan_T, + 1, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, + // System_Double__IsNaN (byte)(MemberFlags.Method | MemberFlags.Static), // Flags (byte)SpecialType.System_Double, // DeclaringTypeId @@ -1060,6 +1118,14 @@ static SpecialMembers() 1, // Method Signature (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, // Return Type (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Int32, + + // System_ReadOnlySpan_T__ctor_Reference + (byte)MemberFlags.Constructor, // Flags + (byte)SpecialType.System_ReadOnlySpan_T, // DeclaringTypeId + 0, // Arity + 1, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, // Return Type + (byte)SignatureTypeCode.ByReference, (byte)SignatureTypeCode.GenericTypeParameter, 0, }; string[] allNames = new string[(int)SpecialMember.Count] @@ -1073,12 +1139,16 @@ static SpecialMembers() "Concat", // System_String__ConcatObjectObject "Concat", // System_String__ConcatObjectObjectObject "Concat", // System_String__ConcatObjectArray + "Concat", // System_String__Concat_2ReadOnlySpans + "Concat", // System_String__Concat_3ReadOnlySpans + "Concat", // System_String__Concat_4ReadOnlySpans "op_Equality", // System_String__op_Equality "op_Inequality", // System_String__op_Inequality "get_Length", // System_String__Length "get_Chars", // System_String__Chars "Format", // System_String__Format "Substring", // System_String__Substring + "op_Implicit", // System_String__op_Implicit_ToReadOnlySpanOfChar "IsNaN", // System_Double__IsNaN "IsNaN", // System_Single__IsNaN "Combine", // System_Delegate__Combine @@ -1193,6 +1263,7 @@ static SpecialMembers() "ByRefFields", // System_Runtime_CompilerServices_RuntimeFeature__ByRefFields ".ctor", // System_Runtime_CompilerServices_PreserveBaseOverridesAttribute__ctor ".ctor", // System_Runtime_CompilerServices_InlineArrayAttribute__ctor + ".ctor", // System_ReadOnlySpan_T__ctor_Reference }; s_descriptors = MemberDescriptor.InitializeFromStream(new System.IO.MemoryStream(initializationBytes, writable: false), allNames); diff --git a/src/Compilers/Core/Portable/WellKnownMember.cs b/src/Compilers/Core/Portable/WellKnownMember.cs index b4afdcd32ec9c..d3c38f6908576 100644 --- a/src/Compilers/Core/Portable/WellKnownMember.cs +++ b/src/Compilers/Core/Portable/WellKnownMember.cs @@ -431,11 +431,6 @@ internal enum WellKnownMember System_Runtime_CompilerServices_TupleElementNamesAttribute__ctorTransformNames, System_String__Format_IFormatProvider, - System_String__op_Implicit_ToReadOnlySpanOfChar, - - System_String__Concat_2ReadOnlySpans, - System_String__Concat_3ReadOnlySpans, - System_String__Concat_4ReadOnlySpans, Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningSingleFile, Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningMultipleFiles, @@ -500,7 +495,6 @@ internal enum WellKnownMember System_ReadOnlySpan_T__ctor_Pointer, System_ReadOnlySpan_T__ctor_Array, System_ReadOnlySpan_T__ctor_Array_Start_Length, - System_ReadOnlySpan_T__ctor_Reference, System_ReadOnlySpan_T__get_Item, System_ReadOnlySpan_T__get_Length, System_ReadOnlySpan_T__Slice_Int_Int, diff --git a/src/Compilers/Core/Portable/WellKnownMembers.cs b/src/Compilers/Core/Portable/WellKnownMembers.cs index 9bf6adb71985e..b4af69dfb75e4 100644 --- a/src/Compilers/Core/Portable/WellKnownMembers.cs +++ b/src/Compilers/Core/Portable/WellKnownMembers.cs @@ -2979,74 +2979,6 @@ static WellKnownMembers() (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, (byte)SignatureTypeCode.SZArray, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Object, - // System_String__op_Implicit_ToReadOnlySpanOfChar - (byte)(MemberFlags.Method | MemberFlags.Static), // Flags - (byte)SpecialType.System_String, // DeclaringTypeId - 0, // Arity - 1, // Method Signature - (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, // Return Type - (byte)WellKnownType.ExtSentinel, (WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), - 1, - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, - - // System_String__Concat_2ReadOnlySpans - (byte)(MemberFlags.Method | MemberFlags.Static), // Flags - (byte)SpecialType.System_String, // DeclaringTypeId - 0, // Arity - 2, // Method Signature - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, // Return Type - (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, - (byte)WellKnownType.ExtSentinel, (WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), - 1, - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, - (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, - (byte)WellKnownType.ExtSentinel, (WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), - 1, - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, - - // System_String__Concat_3ReadOnlySpans - (byte)(MemberFlags.Method | MemberFlags.Static), // Flags - (byte)SpecialType.System_String, // DeclaringTypeId - 0, // Arity - 3, // Method Signature - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, // Return Type - (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, - (byte)WellKnownType.ExtSentinel, (WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), - 1, - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, - (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, - (byte)WellKnownType.ExtSentinel, (WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), - 1, - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, - (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, - (byte)WellKnownType.ExtSentinel, (WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), - 1, - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, - - // System_String__Concat_4ReadOnlySpans - (byte)(MemberFlags.Method | MemberFlags.Static), // Flags - (byte)SpecialType.System_String, // DeclaringTypeId - 0, // Arity - 4, // Method Signature - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, // Return Type - (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, - (byte)WellKnownType.ExtSentinel, (WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), - 1, - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, - (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, - (byte)WellKnownType.ExtSentinel, (WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), - 1, - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, - (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, - (byte)WellKnownType.ExtSentinel, (WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), - 1, - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, - (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, - (byte)WellKnownType.ExtSentinel, (WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), - 1, - (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, - // Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningSingleFile (byte)(MemberFlags.Method | MemberFlags.Static), // Flags (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.Microsoft_CodeAnalysis_Runtime_Instrumentation - WellKnownType.ExtSentinel), // DeclaringTypeId @@ -3507,14 +3439,6 @@ static WellKnownMembers() (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Int32, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Int32, - // System_ReadOnlySpan_T__ctor_Reference - (byte)(MemberFlags.Constructor), // 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.ByReference, (byte)SignatureTypeCode.GenericTypeParameter, 0, - // System_ReadOnlySpan_T__get_Item (byte)(MemberFlags.PropertyGet), // Flags (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), // DeclaringTypeId @@ -4888,11 +4812,6 @@ static WellKnownMembers() ".ctor", // System_Runtime_CompilerServices_TupleElementNamesAttribute__ctorTransformNames "Format", // System_String__Format_IFormatProvider - "op_Implicit", // System_String__op_Implicit_ToReadOnlySpanOfChar - - "Concat", // System_String__Concat_2ReadOnlySpans - "Concat", // System_String__Concat_3ReadOnlySpans - "Concat", // System_String__Concat_4ReadOnlySpans "CreatePayload", // Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningSingleFile "CreatePayload", // Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningMultipleFiles @@ -4948,7 +4867,6 @@ static WellKnownMembers() ".ctor", // System_ReadOnlySpan_T__ctor_Pointer ".ctor", // System_ReadOnlySpan_T__ctor_Array ".ctor", // System_ReadOnlySpan_T__ctor_Array_Start_Length - ".ctor", // System_ReadOnlySpan_T__ctor_Reference "get_Item", // System_ReadOnlySpan_T__get_Item "get_Length", // System_ReadOnlySpan_T__get_Length "Slice", // System_ReadOnlySpan_T__Slice_Int_Int diff --git a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb index bcdf8da06faa1..401a122243bd4 100644 --- a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb +++ b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb @@ -702,14 +702,9 @@ End Namespace WellKnownMember.System_ReadOnlySpan_T__ctor_Pointer, WellKnownMember.System_ReadOnlySpan_T__ctor_Array, WellKnownMember.System_ReadOnlySpan_T__ctor_Array_Start_Length, - WellKnownMember.System_ReadOnlySpan_T__ctor_Reference, WellKnownMember.System_ReadOnlySpan_T__get_Item, WellKnownMember.System_ReadOnlySpan_T__get_Length, WellKnownMember.System_ReadOnlySpan_T__Slice_Int_Int, - WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar, - WellKnownMember.System_String__Concat_2ReadOnlySpans, - WellKnownMember.System_String__Concat_3ReadOnlySpans, - WellKnownMember.System_String__Concat_4ReadOnlySpans, WellKnownMember.System_Runtime_CompilerServices_AsyncIteratorStateMachineAttribute__ctor, WellKnownMember.System_IAsyncDisposable__DisposeAsync, WellKnownMember.System_Collections_Generic_IAsyncEnumerable_T__GetAsyncEnumerator, @@ -912,14 +907,9 @@ End Namespace WellKnownMember.System_ReadOnlySpan_T__ctor_Pointer, WellKnownMember.System_ReadOnlySpan_T__ctor_Array, WellKnownMember.System_ReadOnlySpan_T__ctor_Array_Start_Length, - WellKnownMember.System_ReadOnlySpan_T__ctor_Reference, WellKnownMember.System_ReadOnlySpan_T__get_Item, WellKnownMember.System_ReadOnlySpan_T__get_Length, WellKnownMember.System_ReadOnlySpan_T__Slice_Int_Int, - WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar, - WellKnownMember.System_String__Concat_2ReadOnlySpans, - WellKnownMember.System_String__Concat_3ReadOnlySpans, - WellKnownMember.System_String__Concat_4ReadOnlySpans, WellKnownMember.System_Runtime_CompilerServices_AsyncIteratorStateMachineAttribute__ctor, WellKnownMember.System_IAsyncDisposable__DisposeAsync, WellKnownMember.System_Collections_Generic_IAsyncEnumerable_T__GetAsyncEnumerator, From 19ed7b628ff7f216719d0735fdfc785e0b42c9ef Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sun, 18 Feb 2024 17:47:18 +0300 Subject: [PATCH 51/71] Add test cases --- .../CodeGenSpanBasedStringConcatTests.cs | 359 ++++++++++++++++++ 1 file changed, 359 insertions(+) diff --git a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs index b8d855f71f8cd..a7019f0684c97 100644 --- a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs @@ -921,6 +921,70 @@ .locals init (int V_0, """); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatTwo_UserDefinedReadOnlySpan() + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s = "s"; + var c = 'c'; + Console.Write(M1(s, c)); + Console.Write(M2(s, c)); + } + + static string M1(string s, char c) => s + c; + static string M2(string s, char c) => c + s; + } + + namespace System + { + public struct ReadOnlySpan + { + public ReadOnlySpan(ref readonly T reference) { } + } + } + """; + + var comp = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "sccs" : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.M1", """ + { + // Code size 21 (0x15) + .maxstack 2 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.1 + IL_0007: stloc.0 + IL_0008: ldloca.s V_0 + IL_000a: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000f: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0014: ret + } + """); + comp.VerifyIL("Test.M2", """ + { + // Code size 21 (0x15) + .maxstack 2 + .locals init (char V_0) + IL_0000: ldarg.1 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0009: ldarg.0 + IL_000a: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000f: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0014: ret + } + """); + } + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] [InlineData((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] @@ -2389,6 +2453,115 @@ .locals init (int V_0, """); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatThree_UserDefinedReadOnlySpan() + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s = "s"; + var c = 'c'; + Console.Write(M1(s, c)); + Console.Write(M2(s, c)); + Console.Write(M3(s, c)); + Console.Write(M4(s, c)); + } + + static string M1(string s, char c) => c + s + s; + static string M2(string s, char c) => s + c + s; + static string M3(string s, char c) => s + s + c; + static string M4(string s, char c) => c + s + c; + } + + namespace System + { + public struct ReadOnlySpan + { + public ReadOnlySpan(ref readonly T reference) { } + } + } + """; + + var comp = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "cssscsssccsc" : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.M1", """ + { + // Code size 27 (0x1b) + .maxstack 3 + .locals init (char V_0) + IL_0000: ldarg.1 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0009: ldarg.0 + IL_000a: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000f: ldarg.0 + IL_0010: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0015: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001a: ret + } + """); + comp.VerifyIL("Test.M2", """ + { + // Code size 27 (0x1b) + .maxstack 3 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.1 + IL_0007: stloc.0 + IL_0008: ldloca.s V_0 + IL_000a: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000f: ldarg.0 + IL_0010: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0015: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001a: ret + } + """); + comp.VerifyIL("Test.M3", """ + { + // Code size 27 (0x1b) + .maxstack 3 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.0 + IL_0007: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000c: ldarg.1 + IL_000d: stloc.0 + IL_000e: ldloca.s V_0 + IL_0010: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0015: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001a: ret + } + """); + comp.VerifyIL("Test.M4", """ + { + // Code size 30 (0x1e) + .maxstack 3 + .locals init (char V_0, + char V_1) + IL_0000: ldarg.1 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0009: ldarg.0 + IL_000a: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000f: ldarg.1 + IL_0010: stloc.1 + IL_0011: ldloca.s V_1 + IL_0013: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0018: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001d: ret + } + """); + } + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] [InlineData((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] @@ -4913,6 +5086,192 @@ .locals init (int V_0, """); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatFour_UserDefinedReadOnlySpan() + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s = "s"; + var c = 'c'; + Console.Write(M1(s, c)); + Console.Write(M2(s, c)); + Console.Write(M3(s, c)); + Console.Write(M4(s, c)); + Console.Write(M5(s, c)); + Console.Write(M6(s, c)); + Console.Write(M7(s, c)); + } + + static string M1(string s, char c) => c + s + s + s; + static string M2(string s, char c) => s + c + s + s; + static string M3(string s, char c) => s + s + c + s; + static string M4(string s, char c) => s + s + s + c; + static string M5(string s, char c) => c + s + c + s; + static string M6(string s, char c) => s + c + s + c; + static string M7(string s, char c) => c + s + s + c; + } + + namespace System + { + public struct ReadOnlySpan + { + public ReadOnlySpan(ref readonly T reference) { } + } + } + """; + + var comp = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "csssscsssscssssccscsscsccssc" : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.M1", """ + { + // Code size 33 (0x21) + .maxstack 4 + .locals init (char V_0) + IL_0000: ldarg.1 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0009: ldarg.0 + IL_000a: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000f: ldarg.0 + IL_0010: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0015: ldarg.0 + IL_0016: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_001b: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0020: ret + } + """); + comp.VerifyIL("Test.M2", """ + { + // Code size 33 (0x21) + .maxstack 4 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.1 + IL_0007: stloc.0 + IL_0008: ldloca.s V_0 + IL_000a: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000f: ldarg.0 + IL_0010: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0015: ldarg.0 + IL_0016: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_001b: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0020: ret + } + """); + comp.VerifyIL("Test.M3", """ + { + // Code size 33 (0x21) + .maxstack 4 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.0 + IL_0007: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000c: ldarg.1 + IL_000d: stloc.0 + IL_000e: ldloca.s V_0 + IL_0010: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0015: ldarg.0 + IL_0016: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_001b: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0020: ret + } + """); + comp.VerifyIL("Test.M4", """ + { + // Code size 33 (0x21) + .maxstack 4 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.0 + IL_0007: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000c: ldarg.0 + IL_000d: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0012: ldarg.1 + IL_0013: stloc.0 + IL_0014: ldloca.s V_0 + IL_0016: newobj "System.ReadOnlySpan..ctor(in char)" + IL_001b: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0020: ret + } + """); + comp.VerifyIL("Test.M5", """ + { + // Code size 36 (0x24) + .maxstack 4 + .locals init (char V_0, + char V_1) + IL_0000: ldarg.1 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0009: ldarg.0 + IL_000a: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000f: ldarg.1 + IL_0010: stloc.1 + IL_0011: ldloca.s V_1 + IL_0013: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0018: ldarg.0 + IL_0019: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_001e: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0023: ret + } + """); + comp.VerifyIL("Test.M6", """ + { + // Code size 36 (0x24) + .maxstack 4 + .locals init (char V_0, + char V_1) + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.1 + IL_0007: stloc.0 + IL_0008: ldloca.s V_0 + IL_000a: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000f: ldarg.0 + IL_0010: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0015: ldarg.1 + IL_0016: stloc.1 + IL_0017: ldloca.s V_1 + IL_0019: newobj "System.ReadOnlySpan..ctor(in char)" + IL_001e: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0023: ret + } + """); + comp.VerifyIL("Test.M7", """ + { + // Code size 36 (0x24) + .maxstack 4 + .locals init (char V_0, + char V_1) + IL_0000: ldarg.1 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0009: ldarg.0 + IL_000a: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000f: ldarg.0 + IL_0010: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0015: ldarg.1 + IL_0016: stloc.1 + IL_0017: ldloca.s V_1 + IL_0019: newobj "System.ReadOnlySpan..ctor(in char)" + IL_001e: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0023: ret + } + """); + } + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] [InlineData((int)SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] From 26bce1e5693b720ce7742a7eeaefc1f9c9f38dfd Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sun, 18 Feb 2024 17:50:10 +0300 Subject: [PATCH 52/71] Align --- src/Compilers/Core/Portable/WellKnownMembers.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Compilers/Core/Portable/WellKnownMembers.cs b/src/Compilers/Core/Portable/WellKnownMembers.cs index b4af69dfb75e4..26f26c6bd5ed8 100644 --- a/src/Compilers/Core/Portable/WellKnownMembers.cs +++ b/src/Compilers/Core/Portable/WellKnownMembers.cs @@ -2654,7 +2654,7 @@ static WellKnownMembers() // System_ValueTuple_T1__Item1 (byte)MemberFlags.Field, // Flags - (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_ValueTuple_T1 - WellKnownType.ExtSentinel), // DeclaringTypeId + (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_ValueTuple_T1 - WellKnownType.ExtSentinel), // DeclaringTypeId 0, // Arity (byte)SignatureTypeCode.GenericTypeParameter, 0, // Field Signature @@ -2870,7 +2870,7 @@ static WellKnownMembers() // System_ValueTuple_T1__ctor (byte)MemberFlags.Constructor, // Flags - (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_ValueTuple_T1 - WellKnownType.ExtSentinel), // DeclaringTypeId + (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_ValueTuple_T1 - WellKnownType.ExtSentinel), // DeclaringTypeId 0, // Arity 1, // Method Signature (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, // Return Type From a3b12eca062ecd5a89101c1d9fb77aba98e8bff3 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sun, 18 Feb 2024 22:16:45 +0300 Subject: [PATCH 53/71] Fixes --- src/Compilers/Core/Portable/SpecialTypeExtensions.cs | 1 + .../Symbol/SymbolsTests/CompilationCreationTests.vb | 3 ++- .../SymbolsTests/WellKnownTypeValidationTests.vb | 12 +++++++++--- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Compilers/Core/Portable/SpecialTypeExtensions.cs b/src/Compilers/Core/Portable/SpecialTypeExtensions.cs index 39ff0ce5f346d..ec688fbd4c9a7 100644 --- a/src/Compilers/Core/Portable/SpecialTypeExtensions.cs +++ b/src/Compilers/Core/Portable/SpecialTypeExtensions.cs @@ -87,6 +87,7 @@ public static bool IsValueType(this SpecialType specialType) case SpecialType.System_RuntimeFieldHandle: case SpecialType.System_RuntimeMethodHandle: case SpecialType.System_RuntimeTypeHandle: + case SpecialType.System_ReadOnlySpan_T: return true; default: return false; diff --git a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/CompilationCreationTests.vb b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/CompilationCreationTests.vb index 5bba005131b8d..a1ec028db8ab2 100644 --- a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/CompilationCreationTests.vb +++ b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/CompilationCreationTests.vb @@ -96,7 +96,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests If i = SpecialType.System_Runtime_CompilerServices_RuntimeFeature OrElse i = SpecialType.System_Runtime_CompilerServices_PreserveBaseOverridesAttribute OrElse - i = SpecialType.System_Runtime_CompilerServices_InlineArrayAttribute Then + i = SpecialType.System_Runtime_CompilerServices_InlineArrayAttribute OrElse + i = SpecialType.System_ReadOnlySpan_T Then Assert.Equal(type.Kind, SymbolKind.ErrorType) ' Not available Else Assert.NotEqual(type.Kind, SymbolKind.ErrorType) diff --git a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb index 401a122243bd4..cdda6bfff5767 100644 --- a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb +++ b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb @@ -455,7 +455,8 @@ End Namespace If special = SpecialType.System_Runtime_CompilerServices_RuntimeFeature OrElse special = SpecialType.System_Runtime_CompilerServices_PreserveBaseOverridesAttribute OrElse - special = SpecialType.System_Runtime_CompilerServices_InlineArrayAttribute Then + special = SpecialType.System_Runtime_CompilerServices_InlineArrayAttribute OrElse + special = SpecialType.System_ReadOnlySpan_T Then Assert.Equal(SymbolKind.ErrorType, symbol.Kind) ' Not available Else Assert.NotEqual(SymbolKind.ErrorType, symbol.Kind) @@ -483,14 +484,19 @@ End Namespace Dim symbol = comp.GetSpecialTypeMember(special) - If special = SpecialMember.System_Runtime_CompilerServices_RuntimeFeature__DefaultImplementationsOfInterfaces OrElse + If special = SpecialMember.System_String__Concat_2ReadOnlySpans OrElse + special = SpecialMember.System_String__Concat_3ReadOnlySpans OrElse + special = SpecialMember.System_String__Concat_4ReadOnlySpans OrElse + special = SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar OrElse + special = SpecialMember.System_Runtime_CompilerServices_RuntimeFeature__DefaultImplementationsOfInterfaces OrElse special = SpecialMember.System_Runtime_CompilerServices_RuntimeFeature__UnmanagedSignatureCallingConvention OrElse special = SpecialMember.System_Runtime_CompilerServices_RuntimeFeature__CovariantReturnsOfClasses OrElse special = SpecialMember.System_Runtime_CompilerServices_RuntimeFeature__VirtualStaticsInInterfaces OrElse special = SpecialMember.System_Runtime_CompilerServices_RuntimeFeature__NumericIntPtr OrElse special = SpecialMember.System_Runtime_CompilerServices_RuntimeFeature__ByRefFields OrElse special = SpecialMember.System_Runtime_CompilerServices_PreserveBaseOverridesAttribute__ctor OrElse - special = SpecialMember.System_Runtime_CompilerServices_InlineArrayAttribute__ctor Then + special = SpecialMember.System_Runtime_CompilerServices_InlineArrayAttribute__ctor OrElse + special = SpecialMember.System_ReadOnlySpan_T__ctor_Reference Then Assert.Null(symbol) ' Not available Else Assert.NotNull(symbol) From facd5b18051a7ad3f55d861c7d93fcf9c2d46964 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Mon, 19 Feb 2024 23:14:55 +0300 Subject: [PATCH 54/71] Fix --- .../CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs index 922bfa11143a8..938463632a806 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs @@ -1911,7 +1911,8 @@ private MultiDictionary CreateFields(ArrayBuilder Date: Mon, 19 Feb 2024 23:25:37 +0300 Subject: [PATCH 55/71] Doc --- src/Compilers/Core/Portable/SpecialType.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Compilers/Core/Portable/SpecialType.cs b/src/Compilers/Core/Portable/SpecialType.cs index b64ba45ae3ae0..1da1f1fdbbb17 100644 --- a/src/Compilers/Core/Portable/SpecialType.cs +++ b/src/Compilers/Core/Portable/SpecialType.cs @@ -264,8 +264,13 @@ public enum SpecialType : sbyte System_Runtime_CompilerServices_InlineArrayAttribute = 46, /// - /// Indicates that the type is + /// Indicates that the type is from the COR library. /// + /// + /// Check for this special type cannot be used to find the "canonical" definition of + /// since it is fully legal for it to come from sources other than the COR library, e.g. from `System.Memory` package. + /// The special type entry mostly exist so that compiler can tell this type apart when resolving other members of the COR library + /// System_ReadOnlySpan_T = 47, /// From d23446f26cc6283a07c7540b449f16b57add6172 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Tue, 20 Feb 2024 13:55:00 +0300 Subject: [PATCH 56/71] System --- src/Compilers/Core/Portable/SpecialType.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/Core/Portable/SpecialType.cs b/src/Compilers/Core/Portable/SpecialType.cs index 1da1f1fdbbb17..86ac8e8ec772f 100644 --- a/src/Compilers/Core/Portable/SpecialType.cs +++ b/src/Compilers/Core/Portable/SpecialType.cs @@ -264,7 +264,7 @@ public enum SpecialType : sbyte System_Runtime_CompilerServices_InlineArrayAttribute = 46, /// - /// Indicates that the type is from the COR library. + /// Indicates that the type is System. from the COR library. /// /// /// Check for this special type cannot be used to find the "canonical" definition of From 91a47301602dde21ad7259ded7d1e5f381f4c883 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Tue, 20 Feb 2024 14:01:49 +0300 Subject: [PATCH 57/71] Adjust --- .../Symbols/Metadata/PE/PENamedTypeSymbol.cs | 36 ++++++++++++++++--- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs index 938463632a806..dc4c06cecd0a2 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs @@ -1906,13 +1906,39 @@ private MultiDictionary CreateFields(ArrayBuilder Date: Tue, 20 Feb 2024 23:09:56 +0300 Subject: [PATCH 58/71] System? --- src/Compilers/Core/Portable/SpecialType.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/Core/Portable/SpecialType.cs b/src/Compilers/Core/Portable/SpecialType.cs index 86ac8e8ec772f..4d102127ab415 100644 --- a/src/Compilers/Core/Portable/SpecialType.cs +++ b/src/Compilers/Core/Portable/SpecialType.cs @@ -264,7 +264,7 @@ public enum SpecialType : sbyte System_Runtime_CompilerServices_InlineArrayAttribute = 46, /// - /// Indicates that the type is System. from the COR library. + /// Indicates that the type is from the COR library. /// /// /// Check for this special type cannot be used to find the "canonical" definition of From 92de03400feb32b98b126ccba65337ac893ba36c Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Tue, 27 Feb 2024 10:28:00 +0300 Subject: [PATCH 59/71] Add tests with `null` concatenation argument --- .../CodeGenSpanBasedStringConcatTests.cs | 177 ++++++++++++++++++ 1 file changed, 177 insertions(+) diff --git a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs index a7019f0684c97..41f609f3facbf 100644 --- a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs @@ -304,6 +304,57 @@ .locals init (char V_0, //c """); } + [Theory] + [InlineData(null)] + [InlineData((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + public void ConcatTwo_ReadOnlySpan_NullConcatArgument(int? missingUnimportantMember) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var c = 'c'; + Console.Write(M1(c)); + Console.Write(M2(c)); + } + + static string M1(char c) => c + (string)null; + static string M2(char c) => (string)null + c; + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantMember.HasValue) + { + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "cc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + + var expectedEquivalentIL = """ + { + // Code size 17 (0x11) + .maxstack 2 + IL_0000: ldarga.s V_0 + IL_0002: call "string char.ToString()" + IL_0007: dup + IL_0008: brtrue.s IL_0010 + IL_000a: pop + IL_000b: ldstr "" + IL_0010: ret + } + """; + + verifier.VerifyIL("Test.M1", expectedEquivalentIL); + verifier.VerifyIL("Test.M2", expectedEquivalentIL); + } + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] [InlineData((int)SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] @@ -1519,6 +1570,63 @@ .locals init (char V_0, //c """); } + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData((int)SpecialMember.System_String__Concat_3ReadOnlySpans)] + public void ConcatThree_ReadOnlySpan_NullConcatArgument(int? missingUnimportantMember) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s = "s"; + var c = 'c'; + Console.Write(M1(s, c)); + Console.Write(M2(s, c)); + Console.Write(M3(s, c)); + } + + static string M1(string s, char c) => (string)null + s + c; + static string M2(string s, char c) => s + (c + (string)null); + static string M3(string s, char c) => (s + c) + (string)null; + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantMember.HasValue) + { + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "scscsc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + + var expectedEquivalentIL = """ + { + // Code size 21 (0x15) + .maxstack 2 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.1 + IL_0007: stloc.0 + IL_0008: ldloca.s V_0 + IL_000a: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000f: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0014: ret + } + """; + + verifier.VerifyIL("Test.M1", expectedEquivalentIL); + verifier.VerifyIL("Test.M2", expectedEquivalentIL); + verifier.VerifyIL("Test.M3", expectedEquivalentIL); + } + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] [InlineData((int)SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] @@ -3364,6 +3472,75 @@ .locals init (char V_0, //c1 """); } + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + [InlineData((int)SpecialMember.System_String__Concat_4ReadOnlySpans)] + public void ConcatFour_ReadOnlySpan_NullConcatArgument(int? missingUnimportantMember) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s = "s"; + var c = 'c'; + Console.Write(M1(s, c)); + Console.Write(M2(s, c)); + Console.Write(M3(s, c)); + Console.Write(M4(s, c)); + Console.Write(M5(s, c)); + Console.Write(M6(s, c)); + } + + static string M1(string s, char c) => (string)null + s + c + s; + static string M2(string s, char c) => s + (string)null + c + s; + static string M3(string s, char c) => s + ((string)null + c) + s; + static string M4(string s, char c) => s + c + (string)null + s; + static string M5(string s, char c) => s + (c + (string)null) + s; + static string M6(string s, char c) => s + c + s + (string)null; + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + + if (missingUnimportantMember.HasValue) + { + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.Value); + } + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "scsscsscsscsscsscs" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + + var expectedEquivalentIL = """ + { + // Code size 27 (0x1b) + .maxstack 3 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.1 + IL_0007: stloc.0 + IL_0008: ldloca.s V_0 + IL_000a: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000f: ldarg.0 + IL_0010: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0015: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001a: ret + } + """; + + verifier.VerifyIL("Test.M1", expectedEquivalentIL); + verifier.VerifyIL("Test.M2", expectedEquivalentIL); + verifier.VerifyIL("Test.M3", expectedEquivalentIL); + verifier.VerifyIL("Test.M4", expectedEquivalentIL); + verifier.VerifyIL("Test.M5", expectedEquivalentIL); + verifier.VerifyIL("Test.M6", expectedEquivalentIL); + } + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] [InlineData(null)] [InlineData((int)SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] From 91549243bf239b260559c83d6a3915fba54ca833 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Tue, 27 Feb 2024 10:40:09 +0300 Subject: [PATCH 60/71] Fix leaks --- .../Lowering/LocalRewriter/LocalRewriter_StringConcat.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index 9c9b76ce60445..0704e2360de02 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -457,6 +457,7 @@ private bool TryRewriteStringConcatenationWithSpanBasedConcat(SyntaxNode syntax, // which is unnecessary IL bloat. Thus we require `needsSpanRefParamConstructor` to be true if (!needsSpanRefParamConstructor) { + preparedArgs.Free(); result = null; return false; } @@ -486,6 +487,7 @@ private bool TryRewriteStringConcatenationWithSpanBasedConcat(SyntaxNode syntax, return true; } + preparedArgs.Free(); result = null; return false; From 3d4e2732c6937f624f7b7e5fc448ae1914308ed4 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Wed, 28 Feb 2024 09:42:46 +0300 Subject: [PATCH 61/71] Add `ReadOnlySpan` field loading test for C# --- .../Symbol/Symbols/Metadata/PE/LoadingFields.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Metadata/PE/LoadingFields.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Metadata/PE/LoadingFields.cs index 1c0bc08a0f255..9390c37adf39a 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Metadata/PE/LoadingFields.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Metadata/PE/LoadingFields.cs @@ -4,12 +4,12 @@ #nullable disable -using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; +using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; -using System.Linq; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Symbols.Metadata.PE @@ -140,5 +140,18 @@ static void Main() CompileAndVerify(compilation, expectedOutput: @"Value1 Value2"); } + + [Fact] + public void TestLoadFieldsOfReadOnlySpanFromCorlib() + { + var comp = CreateCompilation("", targetFramework: TargetFramework.Net60); + + var readOnlySpanType = comp.GetTypeByMetadataName("System.ReadOnlySpan`1"); + Assert.NotNull(readOnlySpanType); + Assert.Equal(SpecialType.System_ReadOnlySpan_T, readOnlySpanType.SpecialType); + + var fields = readOnlySpanType.GetMembers().OfType(); + Assert.NotEmpty(fields); + } } } From f61c656cec710e9d91280bf6219655dba59fe845 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Wed, 28 Feb 2024 10:01:53 +0300 Subject: [PATCH 62/71] Fix and test VB --- .../Symbols/Metadata/PE/PENamedTypeSymbol.vb | 37 +++++++++++++++---- .../SymbolsTests/Metadata/PE/LoadingFields.vb | 19 +++++++--- 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/src/Compilers/VisualBasic/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.vb index ca0198c7aca6b..7f75caa280d6a 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.vb @@ -1137,13 +1137,36 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE If Not import Then Select Case Me.TypeKind Case TypeKind.Structure - Dim specialType = Me.SpecialType - If specialType = SpecialType.None OrElse specialType = SpecialType.System_Nullable_T Then - ' This is an ordinary struct - If ([module].GetFieldDefFlagsOrThrow(fieldDef) And FieldAttributes.Static) = 0 Then - import = True - End If - End If + Select Case Me.SpecialType + Case SpecialType.System_Void, + SpecialType.System_Boolean, + SpecialType.System_Char, + SpecialType.System_Byte, + SpecialType.System_SByte, + SpecialType.System_Int16, + SpecialType.System_UInt16, + SpecialType.System_Int32, + SpecialType.System_UInt32, + SpecialType.System_Int64, + SpecialType.System_UInt64, + SpecialType.System_Single, + SpecialType.System_Double, + SpecialType.System_Decimal, + SpecialType.System_IntPtr, + SpecialType.System_UIntPtr, + SpecialType.System_DateTime, + SpecialType.System_TypedReference, + SpecialType.System_ArgIterator, + SpecialType.System_RuntimeArgumentHandle, + SpecialType.System_RuntimeFieldHandle, + SpecialType.System_RuntimeMethodHandle, + SpecialType.System_RuntimeTypeHandle + Case Else + ' This is an ordinary struct + If ([module].GetFieldDefFlagsOrThrow(fieldDef) And FieldAttributes.Static) = 0 Then + import = True + End If + End Select Case TypeKind.Enum If ([module].GetFieldDefFlagsOrThrow(fieldDef) And FieldAttributes.Static) = 0 Then diff --git a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/Metadata/PE/LoadingFields.vb b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/Metadata/PE/LoadingFields.vb index 9faf012c72cdf..329f2f0cd379c 100644 --- a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/Metadata/PE/LoadingFields.vb +++ b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/Metadata/PE/LoadingFields.vb @@ -2,14 +2,9 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. -Imports System.Runtime.CompilerServices -Imports CompilationCreationTestHelpers Imports Microsoft.CodeAnalysis.Test.Utilities -Imports Microsoft.CodeAnalysis.Text -Imports Microsoft.CodeAnalysis.VisualBasic -Imports Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE Imports Microsoft.CodeAnalysis.VisualBasic.Symbols -Imports Microsoft.CodeAnalysis.VisualBasic.Syntax +Imports Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE Imports Roslyn.Test.Utilities Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests.Symbols.Metadata.PE @@ -276,6 +271,18 @@ End Module Value2") End Sub + + Public Sub TestLoadFieldsOfReadOnlySpanFromCorlib() + Dim comp = CreateCompilation("", targetFramework:=TargetFramework.Net60) + + Dim readOnlySpanType = comp.GetTypeByMetadataName("System.ReadOnlySpan`1") + Assert.NotNull(readOnlySpanType) + Assert.Equal(SpecialType.System_ReadOnlySpan_T, readOnlySpanType.SpecialType) + + Dim fields = readOnlySpanType.GetMembers().OfType(Of FieldSymbol)() + Assert.NotEmpty(fields) + End Sub + End Class End Namespace From 71ddb7bd41707758f3c9003dec95253600302997 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Wed, 28 Feb 2024 10:07:05 +0300 Subject: [PATCH 63/71] WorkItem --- .../Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs index 41f609f3facbf..48efeed6dbc2b 100644 --- a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs @@ -304,7 +304,7 @@ .locals init (char V_0, //c """); } - [Theory] + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/72232")] [InlineData(null)] [InlineData((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] public void ConcatTwo_ReadOnlySpan_NullConcatArgument(int? missingUnimportantMember) From 3a8a619d442c7d8d7b677072cda8c3b3cda698e3 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Wed, 28 Feb 2024 11:29:12 +0300 Subject: [PATCH 64/71] Avoid comparisons to `SpecialType.None` due to extensibility issues --- .../CSharp/Portable/CodeGen/EmitExpression.cs | 2 +- .../FlowAnalysis/DefiniteAssignment.cs | 3 +- .../FlowAnalysis/EmptyStructTypeCache.cs | 8 +--- .../Portable/FlowAnalysis/NullableWalker.cs | 2 +- .../DiagnosticsPass_ExpressionTrees.cs | 2 +- .../LocalRewriter_StringConcat.cs | 2 +- .../MethodToStateMachineRewriter.cs | 3 +- .../Portable/Symbols/TypeSymbolExtensions.cs | 3 +- .../Analysis/FlowAnalysis/AbstractFlowPass.vb | 39 ++++++++++++------- 9 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs b/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs index f188d21d0876a..98c11e079af29 100644 --- a/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs +++ b/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs @@ -2286,7 +2286,7 @@ internal static bool MayUseCallForStructMethod(MethodSymbol method) var containingType = method.ContainingType; // overrides in structs that are special types can be called directly. // we can assume that special types will not be removing overrides - return containingType.SpecialType != SpecialType.None; + return containingType.SpecialType is >= SpecialType.System_Object and <= SpecialType.System_Runtime_CompilerServices_InlineArrayAttribute; } /// diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs index 90abbd1cc148c..4833cd19124e3 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs @@ -17,7 +17,6 @@ #define REFERENCE_STATE #endif -using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; @@ -362,7 +361,7 @@ protected override ImmutableArray Scan(ref bool badRegion) if ((object)methodThisParameter != null) { EnterParameter(methodThisParameter); - if (methodThisParameter.Type.SpecialType != SpecialType.None) + if (methodThisParameter.Type.SpecialType is >= SpecialType.System_Object and <= SpecialType.System_Runtime_CompilerServices_InlineArrayAttribute) { int slot = GetOrCreateSlot(methodThisParameter); SetSlotState(slot, true); diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/EmptyStructTypeCache.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/EmptyStructTypeCache.cs index 8a09d4519c02a..db26ec6d7d067 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/EmptyStructTypeCache.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/EmptyStructTypeCache.cs @@ -4,16 +4,10 @@ #nullable disable -using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; -using System.Text; using Microsoft.CodeAnalysis.CSharp.Symbols; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; -using System.Threading; namespace Microsoft.CodeAnalysis.CSharp { @@ -131,7 +125,7 @@ public static bool IsTrackableStructType(TypeSymbol type) if ((object)type == null) return false; var nts = type.OriginalDefinition as NamedTypeSymbol; if ((object)nts == null) return false; - return nts.IsStructType() && nts.SpecialType == SpecialType.None && !nts.KnownCircularStruct; + return nts.IsStructType() && nts.SpecialType is not (>= SpecialType.System_Object and <= SpecialType.System_Runtime_CompilerServices_InlineArrayAttribute) && !nts.KnownCircularStruct; } /// diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 46a5fdc4680c0..3f695b690fb91 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -4302,7 +4302,7 @@ protected override bool IsEmptyStructType(TypeSymbol type) return false; } - if (type.SpecialType != SpecialType.None) + if (type.SpecialType is >= SpecialType.System_Object and <= SpecialType.System_Runtime_CompilerServices_InlineArrayAttribute) { return true; } diff --git a/src/Compilers/CSharp/Portable/Lowering/DiagnosticsPass_ExpressionTrees.cs b/src/Compilers/CSharp/Portable/Lowering/DiagnosticsPass_ExpressionTrees.cs index 4f189d184026c..4c55a96065fd4 100644 --- a/src/Compilers/CSharp/Portable/Lowering/DiagnosticsPass_ExpressionTrees.cs +++ b/src/Compilers/CSharp/Portable/Lowering/DiagnosticsPass_ExpressionTrees.cs @@ -90,7 +90,7 @@ public override BoundNode VisitArrayAccess(BoundArrayAccess node) { if (_inExpressionLambda && node.Indices.Length == 1 && - node.Indices[0].Type!.SpecialType == SpecialType.None) + node.Indices[0].Type!.SpecialType is not (>= SpecialType.System_Object and <= SpecialType.System_Runtime_CompilerServices_InlineArrayAttribute)) { Error(ErrorCode.ERR_ExpressionTreeContainsPatternImplicitIndexer, node); } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index 0704e2360de02..96fee30e8d8f9 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -679,7 +679,7 @@ private BoundExpression ConvertConcatExprToString(BoundExpression expr) // it if object.ToString is missing). Assume that this won't be removed, and emit a direct call rather // than a constrained virtual call. This keeps in the spirit of #7079, but expands the range of // types to all special value types. - if (structToStringMethod != null && (expr.Type.SpecialType != SpecialType.None && !isFieldOfMarshalByRef(expr, _compilation))) + if (structToStringMethod != null && (expr.Type.SpecialType is >= SpecialType.System_Object and <= SpecialType.System_Runtime_CompilerServices_InlineArrayAttribute && !isFieldOfMarshalByRef(expr, _compilation))) { return BoundCall.Synthesized(syntax, expr, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, structToStringMethod); } diff --git a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs index 88240a3be3e5b..e476eff30da02 100644 --- a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs @@ -11,7 +11,6 @@ using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Symbols; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp @@ -440,7 +439,7 @@ private bool MightContainReferences(TypeSymbol type) if (type.IsReferenceType || type.TypeKind == TypeKind.TypeParameter) return true; // type parameter or reference type if (type.TypeKind != TypeKind.Struct) return false; // enums, etc if (type.SpecialType == SpecialType.System_TypedReference) return true; - if (type.SpecialType != SpecialType.None) return false; // int, etc + if (type.SpecialType is >= SpecialType.System_Object and <= SpecialType.System_Runtime_CompilerServices_InlineArrayAttribute) return false; // int, etc if (!type.IsFromCompilation(this.CompilationState.ModuleBuilderOpt.Compilation)) return true; // perhaps from ref assembly foreach (var f in _emptyStructTypeCache.GetStructInstanceFields(type)) { diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs index 98b3b953330d2..10ae49a4eb88b 100644 --- a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs +++ b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs @@ -7,7 +7,6 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Text; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; @@ -218,7 +217,7 @@ public static bool IsValidEnumType(this TypeSymbol type) { var underlyingType = type.GetEnumUnderlyingType(); // SpecialType will be None if the underlying type is invalid. - return (underlyingType is object) && (underlyingType.SpecialType != SpecialType.None); + return underlyingType is { SpecialType: >= SpecialType.System_Object and <= SpecialType.System_Runtime_CompilerServices_InlineArrayAttribute }; } /// diff --git a/src/Compilers/VisualBasic/Portable/Analysis/FlowAnalysis/AbstractFlowPass.vb b/src/Compilers/VisualBasic/Portable/Analysis/FlowAnalysis/AbstractFlowPass.vb index 06e77e63f2e2e..e5d1cafab45ad 100644 --- a/src/Compilers/VisualBasic/Portable/Analysis/FlowAnalysis/AbstractFlowPass.vb +++ b/src/Compilers/VisualBasic/Portable/Analysis/FlowAnalysis/AbstractFlowPass.vb @@ -2,18 +2,10 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. -Imports System -Imports System.Collections.Generic Imports System.Collections.Immutable -Imports System.Diagnostics -Imports System.Linq -Imports System.Text +Imports System.Runtime.InteropServices Imports Microsoft.CodeAnalysis.PooledObjects -Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Symbols -Imports Microsoft.CodeAnalysis.VisualBasic.Syntax -Imports Roslyn.Utilities -Imports System.Runtime.InteropServices ' NOTE: VB does not support constant expressions in flow analysis during command-line compilation, but supports them when ' analysis is being called via public API. This distinction is governed by 'suppressConstantExpressions' flag @@ -339,14 +331,31 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Return False End If Select Case type.SpecialType - Case SpecialType.None, - SpecialType.System_Nullable_T, - SpecialType.System_IntPtr, - SpecialType.System_UIntPtr - Return True + Case SpecialType.System_Void, + SpecialType.System_Boolean, + SpecialType.System_Char, + SpecialType.System_SByte, + SpecialType.System_Byte, + SpecialType.System_Int16, + SpecialType.System_UInt16, + SpecialType.System_Int32, + SpecialType.System_UInt32, + SpecialType.System_Int64, + SpecialType.System_UInt64, + SpecialType.System_Decimal, + SpecialType.System_Single, + SpecialType.System_Double, + SpecialType.System_DateTime, + SpecialType.System_TypedReference, + SpecialType.System_ArgIterator, + SpecialType.System_RuntimeArgumentHandle, + SpecialType.System_RuntimeFieldHandle, + SpecialType.System_RuntimeMethodHandle, + SpecialType.System_RuntimeTypeHandle + Return False Case Else - Return False + Return True End Select End Function From c62c748243c3ba43054f821142970e45f484318e Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Wed, 28 Feb 2024 19:54:18 +0300 Subject: [PATCH 65/71] Use `GetSpecialType` API in tests --- .../CSharp/Test/Symbol/Symbols/Metadata/PE/LoadingFields.cs | 5 ++--- .../Test/Symbol/SymbolsTests/Metadata/PE/LoadingFields.vb | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Metadata/PE/LoadingFields.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Metadata/PE/LoadingFields.cs index 9390c37adf39a..6b426817b160f 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Metadata/PE/LoadingFields.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Metadata/PE/LoadingFields.cs @@ -146,9 +146,8 @@ public void TestLoadFieldsOfReadOnlySpanFromCorlib() { var comp = CreateCompilation("", targetFramework: TargetFramework.Net60); - var readOnlySpanType = comp.GetTypeByMetadataName("System.ReadOnlySpan`1"); - Assert.NotNull(readOnlySpanType); - Assert.Equal(SpecialType.System_ReadOnlySpan_T, readOnlySpanType.SpecialType); + var readOnlySpanType = comp.GetSpecialType(SpecialType.System_ReadOnlySpan_T); + Assert.False(readOnlySpanType.IsErrorType()); var fields = readOnlySpanType.GetMembers().OfType(); Assert.NotEmpty(fields); diff --git a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/Metadata/PE/LoadingFields.vb b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/Metadata/PE/LoadingFields.vb index 329f2f0cd379c..ac6a1308e615b 100644 --- a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/Metadata/PE/LoadingFields.vb +++ b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/Metadata/PE/LoadingFields.vb @@ -275,9 +275,8 @@ Value2") Public Sub TestLoadFieldsOfReadOnlySpanFromCorlib() Dim comp = CreateCompilation("", targetFramework:=TargetFramework.Net60) - Dim readOnlySpanType = comp.GetTypeByMetadataName("System.ReadOnlySpan`1") - Assert.NotNull(readOnlySpanType) - Assert.Equal(SpecialType.System_ReadOnlySpan_T, readOnlySpanType.SpecialType) + Dim readOnlySpanType = comp.GetSpecialType(SpecialType.System_ReadOnlySpan_T) + Assert.False(readOnlySpanType.IsErrorType()) Dim fields = readOnlySpanType.GetMembers().OfType(Of FieldSymbol)() Assert.NotEmpty(fields) From 82101b973f21bf346ddc291b220b346f48d13caf Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Wed, 28 Feb 2024 19:57:38 +0300 Subject: [PATCH 66/71] Comment --- src/Compilers/Core/Portable/SpecialType.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Compilers/Core/Portable/SpecialType.cs b/src/Compilers/Core/Portable/SpecialType.cs index 4d102127ab415..aa98a07955669 100644 --- a/src/Compilers/Core/Portable/SpecialType.cs +++ b/src/Compilers/Core/Portable/SpecialType.cs @@ -276,6 +276,9 @@ public enum SpecialType : sbyte /// /// Count of special types. This is not a count of enum members. /// + /// + /// The underlying numeric value of this member is expected to change every time a new special type is added + /// Count = System_ReadOnlySpan_T } } From 1a2907bebab768dae16190494bf9baac04e1beee Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Wed, 28 Feb 2024 20:10:47 +0300 Subject: [PATCH 67/71] Update comment --- src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs b/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs index 98c11e079af29..250f0f9fa15bd 100644 --- a/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs +++ b/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs @@ -2284,8 +2284,11 @@ internal static bool MayUseCallForStructMethod(MethodSymbol method) } var containingType = method.ContainingType; - // overrides in structs that are special types can be called directly. - // we can assume that special types will not be removing overrides + // Overrides in structs of some special types can be called directly. + // We can assume that these special types will not be removing overrides. + // This pattern can probably be applied to all special types, + // but that would introduce a silent change every time a new special type is added, + // so we constrain the check to a fixed range of types return containingType.SpecialType is >= SpecialType.System_Object and <= SpecialType.System_Runtime_CompilerServices_InlineArrayAttribute; } From e99de5d17e6c9e40458c0e0fd8b0665931ae563f Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Thu, 29 Feb 2024 09:32:29 +0300 Subject: [PATCH 68/71] Feedback --- .../LocalRewriter/LocalRewriter_StringConcat.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index 96fee30e8d8f9..3e0f45e31ed80 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -675,11 +675,16 @@ private BoundExpression ConvertConcatExprToString(BoundExpression expr) } } - // If it's a special value type (and not a field of a MarshalByRef object), it should have its own ToString method (but we might fail to find - // it if object.ToString is missing). Assume that this won't be removed, and emit a direct call rather - // than a constrained virtual call. This keeps in the spirit of #7079, but expands the range of - // types to all special value types. - if (structToStringMethod != null && (expr.Type.SpecialType is >= SpecialType.System_Object and <= SpecialType.System_Runtime_CompilerServices_InlineArrayAttribute && !isFieldOfMarshalByRef(expr, _compilation))) + // If it's one of special value types in the given range (and not a field of a MarshalByRef object), + // it should have its own ToString method (but we might fail to find it if object.ToString is missing). + // Assume that this won't be removed, and emit a direct call rather than a constrained virtual call. + // This logic can probably be applied to all special types, + // but that would introduce a silent change every time a new special type is added, + // and if at some point the assumption no longer holds, this would be a bug, which might not get noticed. + // So to be extra safe we we constrain the check to a fixed range of special types + if (structToStringMethod != null && + expr.Type.SpecialType is (>= SpecialType.System_Object and <= SpecialType.System_Runtime_CompilerServices_InlineArrayAttribute) && + !isFieldOfMarshalByRef(expr, _compilation)) { return BoundCall.Synthesized(syntax, expr, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, structToStringMethod); } From 608c31014bde6dcfd8edd4e1d71f36a5a32d3fce Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Thu, 29 Feb 2024 19:08:21 +0300 Subject: [PATCH 69/71] Typo --- .../Lowering/LocalRewriter/LocalRewriter_StringConcat.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index 3e0f45e31ed80..c445e1e23b601 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -681,7 +681,7 @@ private BoundExpression ConvertConcatExprToString(BoundExpression expr) // This logic can probably be applied to all special types, // but that would introduce a silent change every time a new special type is added, // and if at some point the assumption no longer holds, this would be a bug, which might not get noticed. - // So to be extra safe we we constrain the check to a fixed range of special types + // So to be extra safe we constrain the check to a fixed range of special types if (structToStringMethod != null && expr.Type.SpecialType is (>= SpecialType.System_Object and <= SpecialType.System_Runtime_CompilerServices_InlineArrayAttribute) && !isFieldOfMarshalByRef(expr, _compilation)) From 3b96ce685f4607d513810de91e30dd1a7995005c Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sun, 17 Mar 2024 13:09:21 +0300 Subject: [PATCH 70/71] Extract helper method --- .../CSharp/Portable/CodeGen/EmitExpression.cs | 2 +- .../CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs | 2 +- .../Portable/FlowAnalysis/EmptyStructTypeCache.cs | 2 +- .../CSharp/Portable/FlowAnalysis/NullableWalker.cs | 2 +- .../Lowering/DiagnosticsPass_ExpressionTrees.cs | 2 +- .../LocalRewriter/LocalRewriter_StringConcat.cs | 2 +- .../MethodToStateMachineRewriter.cs | 2 +- .../CSharp/Portable/Symbols/TypeSymbolExtensions.cs | 2 +- src/Compilers/Core/Portable/SpecialTypeExtensions.cs | 10 ++++++++++ 9 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs b/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs index 250f0f9fa15bd..3de807a69460e 100644 --- a/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs +++ b/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs @@ -2289,7 +2289,7 @@ internal static bool MayUseCallForStructMethod(MethodSymbol method) // This pattern can probably be applied to all special types, // but that would introduce a silent change every time a new special type is added, // so we constrain the check to a fixed range of types - return containingType.SpecialType is >= SpecialType.System_Object and <= SpecialType.System_Runtime_CompilerServices_InlineArrayAttribute; + return containingType.SpecialType.CanOptimizeBehavior(); } /// diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs index 4833cd19124e3..fbe73cad70c46 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs @@ -361,7 +361,7 @@ protected override ImmutableArray Scan(ref bool badRegion) if ((object)methodThisParameter != null) { EnterParameter(methodThisParameter); - if (methodThisParameter.Type.SpecialType is >= SpecialType.System_Object and <= SpecialType.System_Runtime_CompilerServices_InlineArrayAttribute) + if (methodThisParameter.Type.SpecialType.CanOptimizeBehavior()) { int slot = GetOrCreateSlot(methodThisParameter); SetSlotState(slot, true); diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/EmptyStructTypeCache.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/EmptyStructTypeCache.cs index db26ec6d7d067..ba93a2a660399 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/EmptyStructTypeCache.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/EmptyStructTypeCache.cs @@ -125,7 +125,7 @@ public static bool IsTrackableStructType(TypeSymbol type) if ((object)type == null) return false; var nts = type.OriginalDefinition as NamedTypeSymbol; if ((object)nts == null) return false; - return nts.IsStructType() && nts.SpecialType is not (>= SpecialType.System_Object and <= SpecialType.System_Runtime_CompilerServices_InlineArrayAttribute) && !nts.KnownCircularStruct; + return nts.IsStructType() && !nts.SpecialType.CanOptimizeBehavior() && !nts.KnownCircularStruct; } /// diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 3f695b690fb91..ed0119291e0a3 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -4302,7 +4302,7 @@ protected override bool IsEmptyStructType(TypeSymbol type) return false; } - if (type.SpecialType is >= SpecialType.System_Object and <= SpecialType.System_Runtime_CompilerServices_InlineArrayAttribute) + if (type.SpecialType.CanOptimizeBehavior()) { return true; } diff --git a/src/Compilers/CSharp/Portable/Lowering/DiagnosticsPass_ExpressionTrees.cs b/src/Compilers/CSharp/Portable/Lowering/DiagnosticsPass_ExpressionTrees.cs index 4c55a96065fd4..14dc63cc04d8d 100644 --- a/src/Compilers/CSharp/Portable/Lowering/DiagnosticsPass_ExpressionTrees.cs +++ b/src/Compilers/CSharp/Portable/Lowering/DiagnosticsPass_ExpressionTrees.cs @@ -90,7 +90,7 @@ public override BoundNode VisitArrayAccess(BoundArrayAccess node) { if (_inExpressionLambda && node.Indices.Length == 1 && - node.Indices[0].Type!.SpecialType is not (>= SpecialType.System_Object and <= SpecialType.System_Runtime_CompilerServices_InlineArrayAttribute)) + !node.Indices[0].Type!.SpecialType.CanOptimizeBehavior()) { Error(ErrorCode.ERR_ExpressionTreeContainsPatternImplicitIndexer, node); } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index c445e1e23b601..9b0daebe8a387 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -683,7 +683,7 @@ private BoundExpression ConvertConcatExprToString(BoundExpression expr) // and if at some point the assumption no longer holds, this would be a bug, which might not get noticed. // So to be extra safe we constrain the check to a fixed range of special types if (structToStringMethod != null && - expr.Type.SpecialType is (>= SpecialType.System_Object and <= SpecialType.System_Runtime_CompilerServices_InlineArrayAttribute) && + expr.Type.SpecialType.CanOptimizeBehavior() && !isFieldOfMarshalByRef(expr, _compilation)) { return BoundCall.Synthesized(syntax, expr, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, structToStringMethod); diff --git a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs index e476eff30da02..25bb0a30587e4 100644 --- a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs @@ -439,7 +439,7 @@ private bool MightContainReferences(TypeSymbol type) if (type.IsReferenceType || type.TypeKind == TypeKind.TypeParameter) return true; // type parameter or reference type if (type.TypeKind != TypeKind.Struct) return false; // enums, etc if (type.SpecialType == SpecialType.System_TypedReference) return true; - if (type.SpecialType is >= SpecialType.System_Object and <= SpecialType.System_Runtime_CompilerServices_InlineArrayAttribute) return false; // int, etc + if (type.SpecialType.CanOptimizeBehavior()) return false; // int, etc if (!type.IsFromCompilation(this.CompilationState.ModuleBuilderOpt.Compilation)) return true; // perhaps from ref assembly foreach (var f in _emptyStructTypeCache.GetStructInstanceFields(type)) { diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs index 10ae49a4eb88b..4a36afa21c421 100644 --- a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs +++ b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs @@ -217,7 +217,7 @@ public static bool IsValidEnumType(this TypeSymbol type) { var underlyingType = type.GetEnumUnderlyingType(); // SpecialType will be None if the underlying type is invalid. - return underlyingType is { SpecialType: >= SpecialType.System_Object and <= SpecialType.System_Runtime_CompilerServices_InlineArrayAttribute }; + return underlyingType is not null && underlyingType.SpecialType.CanOptimizeBehavior(); } /// diff --git a/src/Compilers/Core/Portable/SpecialTypeExtensions.cs b/src/Compilers/Core/Portable/SpecialTypeExtensions.cs index ec688fbd4c9a7..afa94c7888812 100644 --- a/src/Compilers/Core/Portable/SpecialTypeExtensions.cs +++ b/src/Compilers/Core/Portable/SpecialTypeExtensions.cs @@ -355,5 +355,15 @@ public static SpecialType FromRuntimeTypeOfLiteralValue(object value) return SpecialType.None; } + + /// + /// Tells whether a different code path can be taken based on the fact, that a given type is a special type. + /// This method is called in places where conditions like specialType != SpecialType.None were previously used. + /// The main reason for this method to exist is to prevent such conditions, which introduce silent code changes every time a new special type is added. + /// This doesn't mean the checked special type range of this method cannot be modified, + /// but rather that each usage of this method needs to be reviewed to make sure everything works as expected in such cases + /// + public static bool CanOptimizeBehavior(this SpecialType specialType) + => specialType is >= SpecialType.System_Object and <= SpecialType.System_Runtime_CompilerServices_InlineArrayAttribute; } } From 482bcaae16679bef0057ef79d0be70d117cb348a Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Mon, 18 Mar 2024 21:55:04 +0300 Subject: [PATCH 71/71] Fix --- .../CSharp/Portable/Lowering/SpillSequenceSpiller.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs b/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs index 922dc8bcfa30a..5f5eae68a4cf5 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs @@ -485,9 +485,10 @@ objectCreationExpression.InitializerExpressionOpt is null && { Debug.Assert(objectCreationExpression.Arguments.Length == 1); var argRefKinds = objectCreationExpression.ArgumentRefKindsOpt; - return objectCreationExpression.UpdateArgumentsAndInitializer([Spill(builder, objectCreationExpression.Arguments[0], argRefKinds.IsDefault ? RefKind.None : argRefKinds[0])], - objectCreationExpression.ArgumentRefKindsOpt, - newInitializerExpression: null); + return objectCreationExpression.Update(objectCreationExpression.Constructor, + [Spill(builder, objectCreationExpression.Arguments[0], argRefKinds.IsDefault ? RefKind.None : argRefKinds[0])], + objectCreationExpression.ArgumentRefKindsOpt, + newInitializerExpression: null); } goto default;