diff --git a/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs b/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs index a01873aa52b36..1a4fae886824a 100644 --- a/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs +++ b/src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs @@ -2294,9 +2294,12 @@ 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; + // 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.CanOptimizeBehavior(); } /// diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs index fb7e7fe234c61..67cae8265b05e 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.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 8a09d4519c02a..ba93a2a660399 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.CanOptimizeBehavior() && !nts.KnownCircularStruct; } /// diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 552a45e0a15b9..5f7eacbfddf06 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -4322,7 +4322,7 @@ protected override bool IsEmptyStructType(TypeSymbol type) return false; } - if (type.SpecialType != SpecialType.None) + 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 712657a43d62a..1f401b2ef6eff 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.CanOptimizeBehavior()) { Error(ErrorCode.ERR_ExpressionTreeContainsPatternImplicitIndexer, node); } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs index 33a63f198d00e..b97db610386a9 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs @@ -593,11 +593,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 { }; } @@ -652,7 +652,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); @@ -669,7 +669,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 b0f418eec38f4..635043ef290bd 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 eebe254102294..7e90157dc9e37 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_LockStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_LockStatement.cs @@ -100,7 +100,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( @@ -117,7 +117,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. @@ -195,7 +195,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 13f59f7b5e44a..dd6498eef7b0d 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectCreationExpression.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ObjectCreationExpression.cs @@ -330,7 +330,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); } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index 9dbcb2383016a..9b0daebe8a387 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,7 +80,7 @@ private BoundExpression RewriteStringConcatenation(SyntaxNode syntax, BinaryOper leftFlattened.AddRange(rightFlattened); rightFlattened.Free(); - BoundExpression result; + BoundExpression? result; switch (leftFlattened.Count) { @@ -96,7 +97,11 @@ private BoundExpression RewriteStringConcatenation(SyntaxNode syntax, BinaryOper case 2: var left = leftFlattened[0]; var right = leftFlattened[1]; - result = RewriteStringConcatenationTwoExprs(syntax, left, right); + + if (!TryRewriteStringConcatenationWithSpanBasedConcat(syntax, leftFlattened, out result)) + { + result = RewriteStringConcatenationTwoExprs(syntax, left, right); + } break; case 3: @@ -104,7 +109,11 @@ private BoundExpression RewriteStringConcatenation(SyntaxNode syntax, BinaryOper var first = leftFlattened[0]; var second = leftFlattened[1]; var third = leftFlattened[2]; - result = RewriteStringConcatenationThreeExprs(syntax, first, second, third); + + if (!TryRewriteStringConcatenationWithSpanBasedConcat(syntax, leftFlattened, out result)) + { + result = RewriteStringConcatenationThreeExprs(syntax, first, second, third); + } } break; @@ -114,7 +123,11 @@ private BoundExpression RewriteStringConcatenation(SyntaxNode syntax, BinaryOper var second = leftFlattened[1]; var third = leftFlattened[2]; var fourth = leftFlattened[3]; - result = RewriteStringConcatenationFourExprs(syntax, first, second, third, fourth); + + if (!TryRewriteStringConcatenationWithSpanBasedConcat(syntax, leftFlattened, out result)) + { + result = RewriteStringConcatenationFourExprs(syntax, first, second, third, fourth); + } } break; @@ -150,13 +163,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) { @@ -184,9 +196,7 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr } break; - case BoundKind.NullCoalescingOperator: - var boundCoalesce = (BoundNullCoalescingOperator)lowered; - + case BoundNullCoalescingOperator boundCoalesce: Debug.Assert(boundCoalesce.LeftPlaceholder is null); Debug.Assert(boundCoalesce.LeftConversion is null); @@ -202,6 +212,65 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr return true; } + break; + + case BoundSequence { SideEffects.Length: 0, Value: BoundCall sequenceCall } sequence: + 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. + 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) + { + // Check whether a call is an implicit `string -> ReadOnlySpan` conversion + 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. + // 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.GetSpecialTypeMember(SpecialMember.System_ReadOnlySpan_T__ctor_Reference) && + objectCreationConstructor.ContainingType.IsReadOnlySpanChar(): + var wrappedExpr = ConvertConcatExprToString(assignment.Right); + unwrappedArgsBuilder.Add(wrappedExpr); + break; + default: + locals.Free(); + unwrappedArgsBuilder.Free(); + arguments = default; + return false; + } + } + + if (locals.Count > 0) + { + // Not all locals are part of a known shape + locals.Free(); + unwrappedArgsBuilder.Free(); + arguments = default; + return false; + } + + locals.Free(); + arguments = unwrappedArgsBuilder.ToImmutableAndFree(); + return true; + } + break; } @@ -346,6 +415,177 @@ private BoundExpression RewriteStringConcatenationManyExprs(SyntaxNode syntax, I return BoundCall.Synthesized(syntax, receiverOpt: null, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, method, array); } + private bool TryRewriteStringConcatenationWithSpanBasedConcat(SyntaxNode syntax, ArrayBuilder args, [NotNullWhen(true)] out BoundExpression? result) + { + // 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); + + var needsSpanRefParamConstructor = false; + var needsImplicitConversionFromStringToSpan = false; + + NamedTypeSymbol? charType = null; + + foreach (var arg in args) + { + 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) == _compilation.GetSpecialTypeMember(SpecialMember.System_Object__ToString)) + { + needsSpanRefParamConstructor = true; + charType = receiverCharType; + preparedArgs.Add(receiver); + continue; + } + else if (arg.ConstantValueOpt is { IsString: true, StringValue: [char c] }) + { + 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; + } + + preparedArgs.Add(arg); + 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) + { + preparedArgs.Free(); + 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 => SpecialMember.System_String__Concat_2ReadOnlySpans, + 3 => SpecialMember.System_String__Concat_3ReadOnlySpans, + 4 => SpecialMember.System_String__Concat_4ReadOnlySpans, + _ => throw ExceptionUtilities.Unreachable(), + }; + + if (TryGetSpecialTypeMethod(syntax, concatMember, out MethodSymbol? spanConcat, isOptional: true) && + tryGetNeededToSpanMembers(this, syntax, needsImplicitConversionFromStringToSpan, charType, out MethodSymbol? readOnlySpanCtorRefParamChar, out MethodSymbol? stringImplicitConversionToReadOnlySpan)) + { + result = rewriteStringConcatenationWithSpanBasedConcat( + syntax, + _factory, + spanConcat, + stringImplicitConversionToReadOnlySpan, + readOnlySpanCtorRefParamChar, + preparedArgs.ToImmutableAndFree()); + + return true; + } + + preparedArgs.Free(); + result = null; + return false; + + static bool tryGetNeededToSpanMembers( + LocalRewriter self, + SyntaxNode syntax, + bool needsImplicitConversionFromStringToSpan, + NamedTypeSymbol charType, + [NotNullWhen(true)] out MethodSymbol? readOnlySpanCtorRefParamChar, + out MethodSymbol? stringImplicitConversionToReadOnlySpan) + { + readOnlySpanCtorRefParamChar = null; + stringImplicitConversionToReadOnlySpan = null; + + 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); + readOnlySpanCtorRefParamChar = readOnlySpanCtorRefParamGeneric.AsMember(readOnlySpanOfChar); + } + else + { + return false; + } + + if (needsImplicitConversionFromStringToSpan) + { + return self.TryGetSpecialTypeMethod(syntax, SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar, out stringImplicitConversionToReadOnlySpan, isOptional: true); + } + + return true; + } + + 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); + + if (arg.Type.SpecialType == SpecialType.System_Char) + { + 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: [readOnlySpanCtorRefParamChar.Parameters[0].RefKind == RefKind.Ref ? RefKind.Ref : 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.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)); + } + } + + 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. @@ -381,7 +621,13 @@ private BoundExpression ConvertConcatExprToString(BoundExpression expr) } } - Debug.Assert(expr.Type is { }); + // 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 @@ -399,6 +645,8 @@ private BoundExpression ConvertConcatExprToString(BoundExpression expr) } } + Debug.Assert(expr.Type is not null); + // If it's a string already, just return it if (expr.Type.IsStringType()) { @@ -427,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 != SpecialType.None && !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 constrain the check to a fixed range of special types + if (structToStringMethod != null && + expr.Type.SpecialType.CanOptimizeBehavior() && + !isFieldOfMarshalByRef(expr, _compilation)) { return BoundCall.Synthesized(syntax, expr, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, structToStringMethod); } diff --git a/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs b/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs index 9bfb3a387e2f3..5f5eae68a4cf5 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,28 @@ private BoundExpression Spill( call.Method, ImmutableArray.Create(Spill(builder, call.Arguments[0]), Spill(builder, call.Arguments[1]))); } + 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])]); + } + + goto default; + + case BoundKind.ObjectCreationExpression: + var objectCreationExpression = (BoundObjectCreationExpression)expression; + + if (refKind == RefKind.None && + objectCreationExpression.InitializerExpressionOpt is null && + objectCreationExpression.Constructor.OriginalDefinition == _F.Compilation.GetSpecialTypeMember(SpecialMember.System_ReadOnlySpan_T__ctor_Reference)) + { + Debug.Assert(objectCreationExpression.Arguments.Length == 1); + var argRefKinds = objectCreationExpression.ArgumentRefKindsOpt; + return objectCreationExpression.Update(objectCreationExpression.Constructor, + [Spill(builder, objectCreationExpression.Arguments[0], argRefKinds.IsDefault ? RefKind.None : argRefKinds[0])], + objectCreationExpression.ArgumentRefKindsOpt, + newInitializerExpression: null); + } goto default; diff --git a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs index 88240a3be3e5b..25bb0a30587e4 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.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/Metadata/PE/PENamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs index 922bfa11143a8..dc4c06cecd0a2 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PENamedTypeSymbol.cs @@ -1906,12 +1906,39 @@ private MultiDictionary CreateFields(ArrayBuilder 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..48efeed6dbc2b --- /dev/null +++ b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenSpanBasedStringConcatTests.cs @@ -0,0 +1,5715 @@ +// 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 + } + """); + } + + [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/72232")] + [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)] + [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; + + 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 (missingUnimportantMember.HasValue) + { + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.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((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; + + 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 (missingUnimportantMember.HasValue) + { + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.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((int)SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + public void ConcatTwoCharToStrings(int? missingUnimportantMember) + { + 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 (missingUnimportantMember.HasValue) + { + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.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)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 = """ + 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)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 + } + """); + } + + [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)); + } + + [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, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + verifier.VerifyDiagnostics(); + + // 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) + .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 readonly 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_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 + } + """); + } + + [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 + } + """); + } + + [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)] + public void ConcatThree_ReadOnlySpan1(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)); + } + + 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 (missingUnimportantMember.HasValue) + { + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.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((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + public void ConcatThree_ReadOnlySpan2(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)); + } + + 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 (missingUnimportantMember.HasValue) + { + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.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((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + public void ConcatThree_ReadOnlySpan_SideEffect(int? missingUnimportantMember) + { + 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 (missingUnimportantMember.HasValue) + { + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.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((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + public void ConcatThree_ReadOnlySpan_ReferenceToSameLocation(int? missingUnimportantMember) + { + 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 (missingUnimportantMember.HasValue) + { + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.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, + 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.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.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)" + IL_0031: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [InlineData((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + public void ConcatThree_ReadOnlySpan_MutateLocal(int? missingUnimportantMember) + { + 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 (missingUnimportantMember.HasValue) + { + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.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, + 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: ldloca.s V_1 + IL_0008: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000d: ldloc.0 + 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.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 + } + """); + } + + [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)] + [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; + + 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 (missingUnimportantMember.HasValue) + { + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.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((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; + + 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 (missingUnimportantMember.HasValue) + { + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.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((int)SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + public void ConcatThreeCharToStrings(int? missingUnimportantMember) + { + 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 (missingUnimportantMember.HasValue) + { + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.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)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 = """ + 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)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 + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [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; + + 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 (missingUnimportantMember.HasValue) + { + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.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 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 + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatThree_UserInputOfSpanBasedConcat_ConcatWithChar() + { + 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 = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abccab" : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.M1", """ + { + // 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: 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 + } + """); + comp.VerifyIL("Test.M2", """ + { + // Code size 32 (0x20) + .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)" + 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)SpecialMember.System_ReadOnlySpan_T__ctor_Reference)] + [InlineData((int)SpecialMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + public void ConcatThree_UserInputOfSpanBasedConcat_ConcatWithChar_MissingMemberForSpanBasedConcat(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((SpecialMember)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 + } + """); + } + + [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)); + } + + [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, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + verifier.VerifyDiagnostics(); + + // No matter whether `char` directly overrides `ToString` or not, we still produce span-based concat if we can + 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 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 + } + """); + } + + [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 + } + """); + } + + [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__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 + 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__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 + 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__wrap2" + IL_0142: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0147: ldarg.0 + 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)" + 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 + } + """); + } + + [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)] + [InlineData((int)SpecialMember.System_String__Concat_3ReadOnlySpans)] + public void ConcatFour_ReadOnlySpan1(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)); + 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 (missingUnimportantMember.HasValue) + { + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.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((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + [InlineData((int)SpecialMember.System_String__Concat_3ReadOnlySpans)] + public void ConcatFour_ReadOnlySpan2(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)); + 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 (missingUnimportantMember.HasValue) + { + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.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("(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 = $$""" + 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((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + [InlineData((int)SpecialMember.System_String__Concat_3ReadOnlySpans)] + public void ConcatFour_ReadOnlySpan_SideEffect(int? missingUnimportantMember) + { + 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 (missingUnimportantMember.HasValue) + { + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.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((int)SpecialMember.System_String__Concat_2ReadOnlySpans)] + [InlineData((int)SpecialMember.System_String__Concat_3ReadOnlySpans)] + public void ConcatFour_ReadOnlySpan_ReferenceToSameLocation(int? missingUnimportantMember) + { + 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 (missingUnimportantMember.HasValue) + { + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.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, + 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.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.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.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)" + IL_0040: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [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; + + 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 (missingUnimportantMember.HasValue) + { + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.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((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)] + [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; + + 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 (missingUnimportantMember.HasValue) + { + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.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((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; + + 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 (missingUnimportantMember.HasValue) + { + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.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((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; + + 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 (missingUnimportantMember.HasValue) + { + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.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)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 = """ + 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)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 + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [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; + + 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); + + if (missingUnimportantMember.HasValue) + { + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.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 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 + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatFour_UserInputOfSpanBasedConcatOf2_ConcatWithChar() + { + 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 = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abccccabcabc" : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.M1", """ + { + // 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: 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 + } + """); + comp.VerifyIL("Test.M2", """ + { + // Code size 41 (0x29) + .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)" + IL_001e: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0023: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0028: ret + } + """); + comp.VerifyIL("Test.M3", """ + { + // Code size 41 (0x29) + .maxstack 3 + .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: 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 + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatFour_UserInputOfSpanBasedConcatOf2_ConcatWithStringAndChar() + { + 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 = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abcdabdccdabdcabcabddabc" : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.M1", """ + { + // 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: 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 + } + """); + comp.VerifyIL("Test.M2", """ + { + // 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: 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 + } + """); + comp.VerifyIL("Test.M3", """ + { + // Code size 38 (0x26) + .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)" + IL_001b: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0020: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0025: ret + } + """); + comp.VerifyIL("Test.M4", """ + { + // Code size 38 (0x26) + .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)" + IL_001b: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0020: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0025: ret + } + """); + comp.VerifyIL("Test.M5", """ + { + // 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: 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 + } + """); + comp.VerifyIL("Test.M6", """ + { + // Code size 38 (0x26) + .maxstack 3 + .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: 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 + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData(null)] + [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; + + 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 (missingUnimportantMember.HasValue) + { + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.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 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(null)] + [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; + + 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 (missingUnimportantMember.HasValue) + { + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.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 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 + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatFour_UserInputOfSpanBasedConcatOf3_ConcatWithChar() + { + 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 = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abcddabc" : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.M1", """ + { + // 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: 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 + } + """); + comp.VerifyIL("Test.M2", """ + { + // Code size 33 (0x21) + .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)" + 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)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 = """ + 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); + comp.MakeMemberMissing((SpecialMember)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)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 = """ + 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); + comp.MakeMemberMissing((SpecialMember)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)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 = """ + 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((SpecialMember)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() + { + 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)); + } + + [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, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + verifier.VerifyDiagnostics(); + + // No matter whether `char` directly overrides `ToString` or not, we still produce span-based concat if we can + 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 readonly 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_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 + } + """); + } + + [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__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 + 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__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 + 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__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 + 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__wrap3" + IL_01af: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_01b4: ldarg.0 + 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__wrap2" + 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 + } + """); + } + + [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)] + [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; + + 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 (missingUnimportantMember.HasValue) + { + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.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((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; + + 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 (missingUnimportantMember.HasValue) + { + comp.MakeMemberMissing((SpecialMember)missingUnimportantMember.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 + } + """); + } +} 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/Metadata/PE/LoadingFields.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Metadata/PE/LoadingFields.cs index 1c0bc08a0f255..6b426817b160f 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,17 @@ static void Main() CompileAndVerify(compilation, expectedOutput: @"Value1 Value2"); } + + [Fact] + public void TestLoadFieldsOfReadOnlySpanFromCorlib() + { + var comp = CreateCompilation("", targetFramework: TargetFramework.Net60); + + 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/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs index d8dbb3d409388..645100119fac1 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 } @@ -554,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 } 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 e5fe82d74f013..f2e84ea506234 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -13,6 +13,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 Microsoft.CodeAnalysis.SeparatedSyntaxList Microsoft.CodeAnalysis.SyntaxList static Microsoft.CodeAnalysis.SeparatedSyntaxList.Create(System.ReadOnlySpan nodes) -> Microsoft.CodeAnalysis.SeparatedSyntaxList 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/SpecialType.cs b/src/Compilers/Core/Portable/SpecialType.cs index 66e5b3eb9d84d..aa98a07955669 100644 --- a/src/Compilers/Core/Portable/SpecialType.cs +++ b/src/Compilers/Core/Portable/SpecialType.cs @@ -263,9 +263,22 @@ public enum SpecialType : sbyte /// System_Runtime_CompilerServices_InlineArrayAttribute = 46, + /// + /// 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, + /// /// Count of special types. This is not a count of enum members. /// - Count = System_Runtime_CompilerServices_InlineArrayAttribute + /// + /// The underlying numeric value of this member is expected to change every time a new special type is added + /// + Count = System_ReadOnlySpan_T } } diff --git a/src/Compilers/Core/Portable/SpecialTypeExtensions.cs b/src/Compilers/Core/Portable/SpecialTypeExtensions.cs index 39ff0ce5f346d..afa94c7888812 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; @@ -354,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; } } 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 8543293c72eb2..5bd237bc8f986 100644 --- a/src/Compilers/Core/Portable/WellKnownMembers.cs +++ b/src/Compilers/Core/Portable/WellKnownMembers.cs @@ -506,7 +506,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 @@ -2663,7 +2663,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 @@ -2879,7 +2879,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 960ee403d9e1d..2b93fedb5b26a 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, @@ -585,10 +584,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", 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 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/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/Metadata/PE/LoadingFields.vb b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/Metadata/PE/LoadingFields.vb index 9faf012c72cdf..ac6a1308e615b 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,17 @@ End Module Value2") End Sub + + Public Sub TestLoadFieldsOfReadOnlySpanFromCorlib() + Dim comp = CreateCompilation("", targetFramework:=TargetFramework.Net60) + + Dim readOnlySpanType = comp.GetSpecialType(SpecialType.System_ReadOnlySpan_T) + Assert.False(readOnlySpanType.IsErrorType()) + + Dim fields = readOnlySpanType.GetMembers().OfType(Of FieldSymbol)() + Assert.NotEmpty(fields) + End Sub + End Class End Namespace diff --git a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb index 0417509193df9..b4c46b675673a 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 @@ -456,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) @@ -484,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)