diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index 62ac0d538b0b0..f0bf0913d197e 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -4,10 +4,8 @@ using System.Collections.Immutable; using System.Diagnostics; -using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.PooledObjects; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp { @@ -46,11 +44,11 @@ private BoundExpression RewriteStringConcatenation(SyntaxNode syntax, BinaryOper } // Convert both sides to a string (calling ToString if necessary) - loweredLeft = ConvertConcatExprToString(syntax, loweredLeft); - loweredRight = ConvertConcatExprToString(syntax, loweredRight); + loweredLeft = ConvertConcatExprToStringOrLeaveItAsChar(syntax, loweredLeft); + loweredRight = ConvertConcatExprToStringOrLeaveItAsChar(syntax, loweredRight); - Debug.Assert(loweredLeft.Type is { } && (loweredLeft.Type.IsStringType() || loweredLeft.Type.IsErrorType()) || loweredLeft.ConstantValueOpt?.IsNull == true); - Debug.Assert(loweredRight.Type is { } && (loweredRight.Type.IsStringType() || loweredRight.Type.IsErrorType()) || loweredRight.ConstantValueOpt?.IsNull == true); + Debug.Assert(loweredLeft.Type is { } && (loweredLeft.Type.IsStringType() || loweredLeft.Type.IsCharType() || loweredLeft.Type.IsErrorType()) || loweredLeft.ConstantValueOpt?.IsNull == true); + Debug.Assert(loweredRight.Type is { } && (loweredRight.Type.IsStringType() || loweredRight.Type.IsCharType() || loweredRight.Type.IsErrorType()) || loweredRight.ConstantValueOpt?.IsNull == true); // try fold two args without flattening. var folded = TryFoldTwoConcatOperands(loweredLeft, loweredRight); @@ -63,8 +61,10 @@ private BoundExpression RewriteStringConcatenation(SyntaxNode syntax, BinaryOper ArrayBuilder leftFlattened = ArrayBuilder.GetInstance(); ArrayBuilder rightFlattened = ArrayBuilder.GetInstance(); - FlattenConcatArg(loweredLeft, leftFlattened); - FlattenConcatArg(loweredRight, rightFlattened); + var previousLocals = ArrayBuilder.GetInstance(); + + FlattenConcatArg(loweredLeft, leftFlattened, previousLocals); + FlattenConcatArg(loweredRight, rightFlattened, previousLocals); if (leftFlattened.Any() && rightFlattened.Any()) { @@ -96,6 +96,7 @@ private BoundExpression RewriteStringConcatenation(SyntaxNode syntax, BinaryOper case 2: var left = leftFlattened[0]; var right = leftFlattened[1]; + Debug.Assert(previousLocals.Count == 0); result = RewriteStringConcatenationTwoExprs(syntax, left, right); break; @@ -104,7 +105,8 @@ private BoundExpression RewriteStringConcatenation(SyntaxNode syntax, BinaryOper var first = leftFlattened[0]; var second = leftFlattened[1]; var third = leftFlattened[2]; - result = RewriteStringConcatenationThreeExprs(syntax, first, second, third); + Debug.Assert(previousLocals.Count <= 1); + result = RewriteStringConcatenationThreeExprs(syntax, first, second, third, previousLocals); } break; @@ -114,16 +116,19 @@ 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); + Debug.Assert(previousLocals.Count <= 2); + result = RewriteStringConcatenationFourExprs(syntax, first, second, third, fourth, previousLocals); } break; default: + Debug.Assert(previousLocals.Count == 0); result = RewriteStringConcatenationManyExprs(syntax, leftFlattened.ToImmutable()); break; } leftFlattened.Free(); + previousLocals.Free(); return result; } @@ -133,11 +138,14 @@ private BoundExpression RewriteStringConcatenation(SyntaxNode syntax, BinaryOper /// /// Generally we only need to recognize same node patterns that we create as a result of concatenation rewrite. /// - private void FlattenConcatArg(BoundExpression lowered, ArrayBuilder flattened) + private void FlattenConcatArg(BoundExpression lowered, ArrayBuilder flattened, ArrayBuilder localsBuilder) { - if (TryExtractStringConcatArgs(lowered, out var arguments)) + if (TryExtractStringConcatArgs(lowered, out ImmutableArray arguments, out ImmutableArray previousLocals)) { flattened.AddRange(arguments); + + if (!previousLocals.IsDefaultOrEmpty) + localsBuilder.AddRange(previousLocals); } else { @@ -151,12 +159,13 @@ private void FlattenConcatArg(BoundExpression lowered, ArrayBuilder /// True if this is a call to a known string concat operator, false otherwise - private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableArray arguments) + private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableArray arguments, out ImmutableArray previousLocals) { - switch (lowered.Kind) + previousLocals = default; + + 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) { @@ -168,6 +177,24 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr return true; } + // Faced `string.Concat(ReadOnlySpan, ReadOnlySpan) + string` (e.g. as a result of a previous rewrite). + // We can only unwrap this if we can later rewrite it to `string.Concat(ReadOnlySpan, ReadOnlySpan, ReadOnlySpan)` + if ((object)method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan) && + TryGetWellKnownTypeMember(lowered.Syntax, WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan, out _, isOptional: true)) + { + arguments = boundCall.Arguments; + return true; + } + + // Faced `string.Concat(ReadOnlySpan, ReadOnlySpan, ReadOnlySpan) + string` (e.g. as a result of a previous rewrite). + // We can only unwrap this if we can later rewrite it to `string.Concat(ReadOnlySpan, ReadOnlySpan, ReadOnlySpan, ReadOnlySpan)` + if ((object)method == _compilation.GetWellKnownTypeMember(WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan) && + TryGetWellKnownTypeMember(lowered.Syntax, WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan, out _, isOptional: true)) + { + arguments = boundCall.Arguments; + return true; + } + if ((object)method == (object)_compilation.GetSpecialTypeMember(SpecialMember.System_String__ConcatStringArray)) { var args = boundCall.Arguments[0] as BoundArrayCreation; @@ -184,9 +211,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 +227,16 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr return true; } + break; + + case BoundSequence { Value: BoundCall previousCall, SideEffects.IsEmpty: true } boundSequence: + if (TryExtractStringConcatArgs(previousCall, out var previousArguments, out _)) + { + arguments = previousArguments; + previousLocals = boundSequence.Locals; + return true; + } + break; } @@ -250,7 +285,7 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr private static bool IsNullOrEmptyStringConstant(BoundExpression operand) { - return (operand.ConstantValueOpt != null && string.IsNullOrEmpty(operand.ConstantValueOpt.StringValue)) || + return (operand.ConstantValueOpt?.IsString == true && string.IsNullOrEmpty(operand.ConstantValueOpt.StringValue)) || operand.IsDefaultValue(); } @@ -287,7 +322,7 @@ private BoundExpression RewriteStringConcatenationOneExpr(BoundExpression lowere { // If it's a call to 'string.Concat' (or is something which ends in '?? ""', which this method also extracts), // we know the result cannot be null. Otherwise return loweredOperand ?? "" - if (TryExtractStringConcatArgs(loweredOperand, out _)) + if (TryExtractStringConcatArgs(loweredOperand, arguments: out _, previousLocals: out _)) { return loweredOperand; } @@ -299,8 +334,64 @@ private BoundExpression RewriteStringConcatenationOneExpr(BoundExpression lowere private BoundExpression RewriteStringConcatenationTwoExprs(SyntaxNode syntax, BoundExpression loweredLeft, BoundExpression loweredRight) { - Debug.Assert(loweredLeft.HasAnyErrors || loweredLeft.Type is { } && loweredLeft.Type.IsStringType()); - Debug.Assert(loweredRight.HasAnyErrors || loweredRight.Type is { } && loweredRight.Type.IsStringType()); + var leftType = loweredLeft.Type; + var rightType = loweredRight.Type; + + Debug.Assert(loweredLeft.HasAnyErrors || leftType is not null && (leftType.IsStringType() || leftType.IsCharType())); + Debug.Assert(loweredRight.HasAnyErrors || rightType is not null && (rightType.IsStringType() || rightType.IsCharType())); + + var leftIsChar = leftType?.IsCharType() == true; + var rightIsChar = rightType?.IsCharType() == true; + + if ((leftIsChar || rightIsChar) && + TryGetWellKnownTypeMember(syntax, WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan, out MethodSymbol stringConcatSpans, isOptional: true) && + TryGetWellKnownTypeMember(syntax, WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar, out MethodSymbol stringImplicitConversionToReadOnlySpan, isOptional: true) && + TryGetWellKnownTypeMember(syntax, WellKnownMember.System_ReadOnlySpan_T__ctor_Reference, out MethodSymbol readOnlySpanCtorRefParamGeneric, isOptional: true)) + { + // One of args is a char. We can use span-based concatenation, which takes a bit more IL, but avoids allocation of a string from ToString call. + // And since implicit conversion from string to span is a JIT intrinsic, the resulting code ends up being faster than the one generated from the "naive" case with ToString call + var readOnlySpanOfChar = readOnlySpanCtorRefParamGeneric.ContainingType.Construct(_compilation.GetSpecialType(SpecialType.System_Char)); + var readOnlySpanCtorRefParamChar = readOnlySpanCtorRefParamGeneric.AsMember(readOnlySpanOfChar); + + var locals = ArrayBuilder.GetInstance(capacity: 1); + + var wrappedLeft = WrapIntoReadOnlySpanOfCharIfNecessary(loweredLeft, stringImplicitConversionToReadOnlySpan, readOnlySpanCtorRefParamChar, out var leftTempLocal); + var wrappedRight = WrapIntoReadOnlySpanOfCharIfNecessary(loweredRight, stringImplicitConversionToReadOnlySpan, readOnlySpanCtorRefParamChar, out var rightTempLocal); + + // We expect that only 1 of the operands is of char type here since ` + ` leads to operands being implicitly converted to ints and added as numbers. + // As a consequence of that we can produce at max 1 additional temp local variable due to the fact that only some char operands require a temp + // while a string operand is just always wrapped into an implicit conversion call + Debug.Assert(leftIsChar != rightIsChar); + Debug.Assert(leftTempLocal is null || rightTempLocal is null); + + locals.AddIfNotNull(leftTempLocal); + locals.AddIfNotNull(rightTempLocal); + + var concatCall = BoundCall.Synthesized(syntax, receiverOpt: null, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, stringConcatSpans, wrappedLeft, wrappedRight); + + var oldSyntax = _factory.Syntax; + _factory.Syntax = syntax; + + var sequence = _factory.Sequence( + locals.ToImmutableAndFree(), + ImmutableArray.Empty, + concatCall); + + _factory.Syntax = oldSyntax; + return sequence; + } + + Debug.Assert(!leftType.IsReadOnlySpanChar() && !rightType.IsReadOnlySpanChar()); + + if (leftIsChar) + { + loweredLeft = WrapCharExprIntoToStringCall(loweredLeft); + } + + if (rightIsChar) + { + loweredRight = WrapCharExprIntoToStringCall(loweredRight); + } var method = UnsafeGetSpecialTypeMethod(syntax, SpecialMember.System_String__ConcatStringString); Debug.Assert((object)method != null); @@ -308,11 +399,72 @@ private BoundExpression RewriteStringConcatenationTwoExprs(SyntaxNode syntax, Bo return BoundCall.Synthesized(syntax, receiverOpt: null, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, method, loweredLeft, loweredRight); } - private BoundExpression RewriteStringConcatenationThreeExprs(SyntaxNode syntax, BoundExpression loweredFirst, BoundExpression loweredSecond, BoundExpression loweredThird) + private BoundExpression RewriteStringConcatenationThreeExprs(SyntaxNode syntax, BoundExpression loweredFirst, BoundExpression loweredSecond, BoundExpression loweredThird, ArrayBuilder previousLocals) { - Debug.Assert(loweredFirst.HasAnyErrors || loweredFirst.Type is { } && loweredFirst.Type.IsStringType()); - Debug.Assert(loweredSecond.HasAnyErrors || loweredSecond.Type is { } && loweredSecond.Type.IsStringType()); - Debug.Assert(loweredThird.HasAnyErrors || loweredThird.Type is { } && loweredThird.Type.IsStringType()); + var firstType = loweredFirst.Type; + var secondType = loweredSecond.Type; + var thirdType = loweredThird.Type; + + Debug.Assert(loweredFirst.HasAnyErrors || firstType is not null && (firstType.IsStringType() || firstType.IsCharType() || firstType.IsReadOnlySpanChar())); + Debug.Assert(loweredSecond.HasAnyErrors || secondType is not null && (secondType.IsStringType() || secondType.IsCharType() || secondType.IsReadOnlySpanChar())); + Debug.Assert(loweredThird.HasAnyErrors || thirdType is not null && (thirdType.IsStringType() || thirdType.IsCharType() || thirdType.IsReadOnlySpanChar())); + + var firstIsChar = firstType?.IsCharType() == true; + var secondIsChar = secondType?.IsCharType() == true; + var thirdIsChar = thirdType?.IsCharType() == true; + + if ((firstIsChar || secondIsChar || thirdIsChar || firstType.IsReadOnlySpanChar() || secondType.IsReadOnlySpanChar() || thirdType.IsReadOnlySpanChar()) && + TryGetWellKnownTypeMember(syntax, WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan, out MethodSymbol stringConcatSpans, isOptional: true) && + TryGetWellKnownTypeMember(syntax, WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar, out MethodSymbol stringImplicitConversionToReadOnlySpan, isOptional: true) && + TryGetWellKnownTypeMember(syntax, WellKnownMember.System_ReadOnlySpan_T__ctor_Reference, out MethodSymbol readOnlySpanWrappingCtorGeneric, isOptional: true)) + { + // One of args is a char or a ReadOnlySpan (e.g. from previous rewrite). We can use span-based concatenation, which takes a bit more IL, but avoids allocation of a string from ToString call in case of char. + // And since implicit conversion from string to span is a JIT intrinsic, the resulting code ends up being faster than the one generated from the "naive" case with ToString call + var readOnlySpanOfChar = readOnlySpanWrappingCtorGeneric.ContainingType.Construct(_compilation.GetSpecialType(SpecialType.System_Char)); + var readOnlySpanCtorRefParamChar = readOnlySpanWrappingCtorGeneric.AsMember(readOnlySpanOfChar); + + var locals = ArrayBuilder.GetInstance(capacity: previousLocals.Count + 1); + locals.AddRange(previousLocals); + + var wrappedFirst = WrapIntoReadOnlySpanOfCharIfNecessary(loweredFirst, stringImplicitConversionToReadOnlySpan, readOnlySpanCtorRefParamChar, out var firstTempLocal); + var wrappedSecond = WrapIntoReadOnlySpanOfCharIfNecessary(loweredSecond, stringImplicitConversionToReadOnlySpan, readOnlySpanCtorRefParamChar, out var secondTempLocal); + var wrappedThird = WrapIntoReadOnlySpanOfCharIfNecessary(loweredThird, stringImplicitConversionToReadOnlySpan, readOnlySpanCtorRefParamChar, out var thirdTempLocal); + + locals.AddIfNotNull(firstTempLocal); + locals.AddIfNotNull(secondTempLocal); + locals.AddIfNotNull(thirdTempLocal); + + var concatCall = BoundCall.Synthesized(syntax, receiverOpt: null, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, stringConcatSpans, ImmutableArray.Create(wrappedFirst, wrappedSecond, wrappedThird)); + + var oldSyntax = _factory.Syntax; + _factory.Syntax = syntax; + + var sequence = _factory.Sequence( + locals.ToImmutableAndFree(), + ImmutableArray.Empty, + concatCall); + + _factory.Syntax = oldSyntax; + return sequence; + } + + Debug.Assert(!firstType.IsReadOnlySpanChar() && !secondType.IsReadOnlySpanChar() && !thirdType.IsReadOnlySpanChar()); + Debug.Assert(previousLocals.Count == 0); + + if (firstIsChar) + { + loweredFirst = WrapCharExprIntoToStringCall(loweredFirst); + } + + if (secondIsChar) + { + loweredSecond = WrapCharExprIntoToStringCall(loweredSecond); + } + + if (thirdIsChar) + { + loweredThird = WrapCharExprIntoToStringCall(loweredThird); + } var method = UnsafeGetSpecialTypeMethod(syntax, SpecialMember.System_String__ConcatStringStringString); Debug.Assert((object)method != null); @@ -320,12 +472,82 @@ private BoundExpression RewriteStringConcatenationThreeExprs(SyntaxNode syntax, return BoundCall.Synthesized(syntax, receiverOpt: null, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, method, ImmutableArray.Create(loweredFirst, loweredSecond, loweredThird)); } - private BoundExpression RewriteStringConcatenationFourExprs(SyntaxNode syntax, BoundExpression loweredFirst, BoundExpression loweredSecond, BoundExpression loweredThird, BoundExpression loweredFourth) + private BoundExpression RewriteStringConcatenationFourExprs(SyntaxNode syntax, BoundExpression loweredFirst, BoundExpression loweredSecond, BoundExpression loweredThird, BoundExpression loweredFourth, ArrayBuilder previousLocals) { - Debug.Assert(loweredFirst.HasAnyErrors || loweredFirst.Type is { } && loweredFirst.Type.IsStringType()); - Debug.Assert(loweredSecond.HasAnyErrors || loweredSecond.Type is { } && loweredSecond.Type.IsStringType()); - Debug.Assert(loweredThird.HasAnyErrors || loweredThird.Type is { } && loweredThird.Type.IsStringType()); - Debug.Assert(loweredFourth.HasAnyErrors || loweredFourth.Type is { } && loweredFourth.Type.IsStringType()); + var firstType = loweredFirst.Type; + var secondType = loweredSecond.Type; + var thirdType = loweredThird.Type; + var fourthType = loweredFourth.Type; + + Debug.Assert(loweredFirst.HasAnyErrors || firstType is not null && (firstType.IsStringType() || firstType.IsCharType() || firstType.IsReadOnlySpanChar())); + Debug.Assert(loweredSecond.HasAnyErrors || secondType is not null && (secondType.IsStringType() || secondType.IsCharType() || secondType.IsReadOnlySpanChar())); + Debug.Assert(loweredThird.HasAnyErrors || thirdType is not null && (thirdType.IsStringType() || thirdType.IsCharType() || thirdType.IsReadOnlySpanChar())); + Debug.Assert(loweredFourth.HasAnyErrors || fourthType is not null && (fourthType.IsStringType() || fourthType.IsCharType() || fourthType.IsReadOnlySpanChar())); + + var firstIsChar = firstType?.IsCharType() == true; + var secondIsChar = secondType?.IsCharType() == true; + var thirdIsChar = thirdType?.IsCharType() == true; + var fourthIsChar = fourthType?.IsCharType() == true; + + if ((firstIsChar || secondIsChar || thirdIsChar || fourthIsChar || firstType.IsReadOnlySpanChar() || secondType.IsReadOnlySpanChar() || thirdType.IsReadOnlySpanChar() || fourthType.IsReadOnlySpanChar()) && + TryGetWellKnownTypeMember(syntax, WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan, out MethodSymbol stringConcatSpans, isOptional: true) && + TryGetWellKnownTypeMember(syntax, WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar, out MethodSymbol stringImplicitConversionToReadOnlySpan, isOptional: true) && + TryGetWellKnownTypeMember(syntax, WellKnownMember.System_ReadOnlySpan_T__ctor_Reference, out MethodSymbol readOnlySpanWrappingCtorGeneric, isOptional: true)) + { + // One of args is a char or a ReadOnlySpan (e.g. from previous rewrite). We can use span-based concatenation, which takes a bit more IL, but avoids allocation of a string from ToString call in case of char. + // And since implicit conversion from string to span is a JIT intrinsic, the resulting code ends up being faster than the one generated from the "naive" case with ToString call + var readOnlySpanOfChar = readOnlySpanWrappingCtorGeneric.ContainingType.Construct(_compilation.GetSpecialType(SpecialType.System_Char)); + var readOnlySpanCtorRefParamChar = readOnlySpanWrappingCtorGeneric.AsMember(readOnlySpanOfChar); + + var locals = ArrayBuilder.GetInstance(capacity: previousLocals.Count + 1); + locals.AddRange(previousLocals); + + var wrappedFirst = WrapIntoReadOnlySpanOfCharIfNecessary(loweredFirst, stringImplicitConversionToReadOnlySpan, readOnlySpanCtorRefParamChar, out var firstTempLocal); + var wrappedSecond = WrapIntoReadOnlySpanOfCharIfNecessary(loweredSecond, stringImplicitConversionToReadOnlySpan, readOnlySpanCtorRefParamChar, out var secondTempLocal); + var wrappedThird = WrapIntoReadOnlySpanOfCharIfNecessary(loweredThird, stringImplicitConversionToReadOnlySpan, readOnlySpanCtorRefParamChar, out var thirdTempLocal); + var wrappedFourth = WrapIntoReadOnlySpanOfCharIfNecessary(loweredFourth, stringImplicitConversionToReadOnlySpan, readOnlySpanCtorRefParamChar, out var fourthTempLocal); + + locals.AddIfNotNull(firstTempLocal); + locals.AddIfNotNull(secondTempLocal); + locals.AddIfNotNull(thirdTempLocal); + locals.AddIfNotNull(fourthTempLocal); + + var concatCall = BoundCall.Synthesized(syntax, receiverOpt: null, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, stringConcatSpans, ImmutableArray.Create(wrappedFirst, wrappedSecond, wrappedThird, wrappedFourth)); + + var oldSyntax = _factory.Syntax; + _factory.Syntax = syntax; + + var sequence = _factory.Sequence( + locals.ToImmutableAndFree(), + ImmutableArray.Empty, + concatCall); + + _factory.Syntax = oldSyntax; + return sequence; + } + + Debug.Assert(!firstType.IsReadOnlySpanChar() && !secondType.IsReadOnlySpanChar() && !thirdType.IsReadOnlySpanChar() && !fourthType.IsReadOnlySpanChar()); + Debug.Assert(previousLocals.Count == 0); + + if (firstIsChar) + { + loweredFirst = WrapCharExprIntoToStringCall(loweredFirst); + } + + if (secondIsChar) + { + loweredSecond = WrapCharExprIntoToStringCall(loweredSecond); + } + + if (thirdIsChar) + { + loweredThird = WrapCharExprIntoToStringCall(loweredThird); + } + + if (fourthIsChar) + { + loweredFourth = WrapCharExprIntoToStringCall(loweredFourth); + } var method = UnsafeGetSpecialTypeMethod(syntax, SpecialMember.System_String__ConcatStringStringStringString); Debug.Assert((object)method != null); @@ -336,16 +558,91 @@ private BoundExpression RewriteStringConcatenationFourExprs(SyntaxNode syntax, B private BoundExpression RewriteStringConcatenationManyExprs(SyntaxNode syntax, ImmutableArray loweredArgs) { Debug.Assert(loweredArgs.Length > 4); - Debug.Assert(loweredArgs.All(a => a.HasErrors || a.Type is { } && a.Type.IsStringType())); + + var stringifiedArgs = ArrayBuilder.GetInstance(loweredArgs.Length); + + foreach (var loweredArg in loweredArgs) + { + Debug.Assert(loweredArg.HasErrors || loweredArg.Type is { } argType && (argType.IsStringType() || argType.IsCharType())); + + if (loweredArg.Type?.IsCharType() == true) + { + stringifiedArgs.Add(WrapCharExprIntoToStringCall(loweredArg)); + } + else + { + stringifiedArgs.Add(loweredArg); + } + } var method = UnsafeGetSpecialTypeMethod(syntax, SpecialMember.System_String__ConcatStringArray); Debug.Assert((object)method != null); - var array = _factory.ArrayOrEmpty(_factory.SpecialType(SpecialType.System_String), loweredArgs); + var array = _factory.ArrayOrEmpty(_factory.SpecialType(SpecialType.System_String), stringifiedArgs.ToImmutableAndFree()); return BoundCall.Synthesized(syntax, receiverOpt: null, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, method, array); } + private BoundExpression WrapCharExprIntoToStringCall(BoundExpression expr) + { + var exprType = expr.Type; + Debug.Assert(exprType is not null && exprType.IsCharType()); + + var objectToString = UnsafeGetSpecialTypeMethod(expr.Syntax, SpecialMember.System_Object__ToString); + var charToString = FindSpecificToStringOfStructType(exprType, objectToString); + + if (charToString is not null && !IsFieldOfMarshalByRef(expr)) + { + return BoundCall.Synthesized(expr.Syntax, expr, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, charToString); + } + + return expr; + } + + private BoundExpression WrapIntoReadOnlySpanOfCharIfNecessary(BoundExpression expr, MethodSymbol stringImplicitConversionToReadOnlySpan, MethodSymbol readOnlySpanCtorRefParamChar, out LocalSymbol? tempLocal) + { + tempLocal = null; + var exprType = expr.Type; + + Debug.Assert(exprType is not null && (exprType.IsStringType() || exprType.IsCharType() || exprType.IsReadOnlySpanChar())); + + if (expr.Type.IsReadOnlySpanChar()) + { + return expr; + } + else if (exprType.IsStringType()) + { + return BoundCall.Synthesized(expr.Syntax, receiverOpt: null, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, stringImplicitConversionToReadOnlySpan, expr); + } + else + { + var temp = _factory.StoreToTemp(expr, out var tempAssignment); + tempLocal = temp.LocalSymbol; + + var wrappedChar = new BoundObjectCreationExpression( + expr.Syntax, + readOnlySpanCtorRefParamChar, + ImmutableArray.Create(temp), + argumentNamesOpt: default, + argumentRefKindsOpt: ImmutableArray.Create(RefKindExtensions.StrictIn), + expanded: false, + argsToParamsOpt: default, + defaultArguments: default, + constantValueOpt: null, + initializerExpressionOpt: null, + type: readOnlySpanCtorRefParamChar.ContainingType); + + // var temp = ; + // new ReadOnlySpan(in temp) + return new BoundSequence( + expr.Syntax, + ImmutableArray.Empty, + ImmutableArray.Create(tempAssignment), + wrappedChar, + wrappedChar.Type); + } + } + /// /// 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. @@ -363,10 +660,11 @@ private BoundExpression RewriteStringConcatInExpressionLambda(SyntaxNode syntax, } /// - /// Returns an expression which converts the given expression into a string (or null). + /// Returns an expression which converts the given expression into a string (or null) or, if the type of expression is char, returns it as is. /// If necessary, this invokes .ToString() on the expression, to avoid boxing value types. + /// Char type is the only exception since we can later apply span-based optimization based on the amount of arguments we get. /// - private BoundExpression ConvertConcatExprToString(SyntaxNode syntax, BoundExpression expr) + private BoundExpression ConvertConcatExprToStringOrLeaveItAsChar(SyntaxNode syntax, BoundExpression expr) { // If it's a value type, it'll have been boxed by the +(string, object) or +(object, string) // operator. Undo that. @@ -398,7 +696,8 @@ private BoundExpression ConvertConcatExprToString(SyntaxNode syntax, BoundExpres } // If it's a string already, just return it - if (expr.Type.IsStringType()) + // If it's a char, return it here, so we can apply span-based optimization later + if (expr.Type.IsStringType() || expr.Type.IsCharType()) { return expr; } @@ -409,27 +708,13 @@ private BoundExpression ConvertConcatExprToString(SyntaxNode syntax, BoundExpres // If it's a struct which has overridden ToString, find that method. Note that we might fail to // find it, e.g. if object.ToString is missing - MethodSymbol? structToStringMethod = null; - if (expr.Type.IsValueType && !expr.Type.IsTypeParameter()) - { - var type = (NamedTypeSymbol)expr.Type; - var typeToStringMembers = type.GetMembers(objectToStringMethod.Name); - foreach (var member in typeToStringMembers) - { - if (member is MethodSymbol toStringMethod && - toStringMethod.GetLeastOverriddenMethod(type) == (object)objectToStringMethod) - { - structToStringMethod = toStringMethod; - break; - } - } - } + MethodSymbol? structToStringMethod = FindSpecificToStringOfStructType(expr.Type, objectToStringMethod); // If it's a special value type (and not a field of a MarshalByRef object), it should have its own ToString method (but we might fail to find // it if object.ToString is missing). Assume that this won't be removed, and emit a direct call rather // than a constrained virtual call. This keeps in the spirit of #7079, but expands the range of // types to all special value types. - if (structToStringMethod != null && (expr.Type.SpecialType != SpecialType.None && !isFieldOfMarshalByRef(expr, _compilation))) + if (structToStringMethod != null && (expr.Type.SpecialType != SpecialType.None && !IsFieldOfMarshalByRef(expr))) { return BoundCall.Synthesized(expr.Syntax, expr, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, structToStringMethod); } @@ -494,17 +779,39 @@ BoundExpression makeConditionalAccess(BoundExpression receiver) forceCopyOfNullableValueType: false, type: _compilation.GetSpecialType(SpecialType.System_String)); } + } - static bool isFieldOfMarshalByRef(BoundExpression expr, CSharpCompilation compilation) + private static MethodSymbol? FindSpecificToStringOfStructType(TypeSymbol exprType, MethodSymbol objectToStringMethod) + { + MethodSymbol? structToStringMethod = null; + if (exprType.IsValueType && !exprType.IsTypeParameter()) { - Debug.Assert(!IsCapturedPrimaryConstructorParameter(expr)); - - if (expr is BoundFieldAccess fieldAccess) + var type = (NamedTypeSymbol)exprType; + var typeToStringMembers = type.GetMembers(objectToStringMethod.Name); + foreach (var member in typeToStringMembers) { - return DiagnosticsPass.IsNonAgileFieldAccess(fieldAccess, compilation); + if (member is MethodSymbol toStringMethod && + toStringMethod.GetLeastOverriddenMethod(type) == (object)objectToStringMethod) + { + structToStringMethod = toStringMethod; + break; + } } - return false; } + + return structToStringMethod; + } + + private bool IsFieldOfMarshalByRef(BoundExpression expr) + { + Debug.Assert(!IsCapturedPrimaryConstructorParameter(expr)); + + if (expr is BoundFieldAccess fieldAccess) + { + return DiagnosticsPass.IsNonAgileFieldAccess(fieldAccess, _compilation); + } + + return false; } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs index 98b3b953330d2..2133a36bf9096 100644 --- a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs +++ b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs @@ -7,7 +7,6 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Text; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; @@ -1345,7 +1344,7 @@ internal static bool IsSpanChar(this TypeSymbol type) && arguments[0].SpecialType == SpecialType.System_Char; } - internal static bool IsReadOnlySpanChar(this TypeSymbol type) + internal static bool IsReadOnlySpanChar(this TypeSymbol? type) { return type is NamedTypeSymbol { diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenStringConcat.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenStringConcat.cs deleted file mode 100644 index 7716182596f00..0000000000000 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenStringConcat.cs +++ /dev/null @@ -1,2108 +0,0 @@ -// 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 Roslyn.Test.Utilities; -using Xunit; - -namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen -{ - public class StringConcatTests : CSharpTestBase - { - [Fact] - public void ConcatConsts() - { - var source = @" -using System; - -public class Test -{ - static void Main() - { - Console.WriteLine(""A"" + ""B""); - - Console.WriteLine(""A"" + (string)null); - Console.WriteLine(""A"" + (object)null); - Console.WriteLine(""A"" + (object)null + ""A"" + (object)null); - - Console.WriteLine((string)null + ""B""); - Console.WriteLine((object)null + ""B""); - - Console.WriteLine((string)null + (object)null); - Console.WriteLine(""#""); - Console.WriteLine((object)null + (string)null); - Console.WriteLine(""#""); - } -} -"; - var comp = CompileAndVerify(source, expectedOutput: @"AB -A -A -AA -B -B - -# - -#"); - - comp.VerifyDiagnostics(); - comp.VerifyIL("Test.Main", @" -{ - // Code size 101 (0x65) - .maxstack 1 - IL_0000: ldstr ""AB"" - IL_0005: call ""void System.Console.WriteLine(string)"" - IL_000a: ldstr ""A"" - IL_000f: call ""void System.Console.WriteLine(string)"" - IL_0014: ldstr ""A"" - IL_0019: call ""void System.Console.WriteLine(string)"" - IL_001e: ldstr ""AA"" - IL_0023: call ""void System.Console.WriteLine(string)"" - IL_0028: ldstr ""B"" - IL_002d: call ""void System.Console.WriteLine(string)"" - IL_0032: ldstr ""B"" - IL_0037: call ""void System.Console.WriteLine(string)"" - IL_003c: ldstr """" - IL_0041: call ""void System.Console.WriteLine(string)"" - IL_0046: ldstr ""#"" - IL_004b: call ""void System.Console.WriteLine(string)"" - IL_0050: ldstr """" - IL_0055: call ""void System.Console.WriteLine(string)"" - IL_005a: ldstr ""#"" - IL_005f: call ""void System.Console.WriteLine(string)"" - IL_0064: ret -} -"); - } - - [Fact, WorkItem(38858, "https://github.com/dotnet/roslyn/issues/38858")] - public void ConcatEnumWithToString() - { - var source = @" -public class C -{ - public static void Main() - { - System.Console.Write(M(Enum.A)); - } - public static string M(Enum e) - { - return e + """"; - } -} -public enum Enum { A = 0, ToString = 1 } -"; - var comp = CompileAndVerify(source, expectedOutput: "A"); - comp.VerifyDiagnostics(); - } - - [Fact, WorkItem(38858, "https://github.com/dotnet/roslyn/issues/38858")] - public void ConcatStructWithToString() - { - var source = @" -public struct Bad -{ - public new int ToString; - - string Crash() - { - return """" + this; - } -} -"; - var comp = CompileAndVerify(source); - comp.VerifyDiagnostics(); - } - - [Fact] - public void ConcatDefaults() - { - var source = @" -using System; - -public class Test -{ - static void Main() - { - Console.WriteLine(""A"" + ""B""); - - Console.WriteLine(""A"" + default(string)); - Console.WriteLine(""A"" + default(object)); - Console.WriteLine(""A"" + default(object) + ""A"" + default(object)); - - Console.WriteLine(default(string) + ""B""); - Console.WriteLine(default(object) + ""B""); - - Console.WriteLine(default(string) + default(object)); - Console.WriteLine(""#""); - Console.WriteLine(default(object) + default(string)); - Console.WriteLine(""#""); - } -} -"; - var comp = CompileAndVerify(source, expectedOutput: @"AB -A -A -AA -B -B - -# - -#"); - - comp.VerifyDiagnostics(); - comp.VerifyIL("Test.Main", @" -{ - // Code size 101 (0x65) - .maxstack 1 - IL_0000: ldstr ""AB"" - IL_0005: call ""void System.Console.WriteLine(string)"" - IL_000a: ldstr ""A"" - IL_000f: call ""void System.Console.WriteLine(string)"" - IL_0014: ldstr ""A"" - IL_0019: call ""void System.Console.WriteLine(string)"" - IL_001e: ldstr ""AA"" - IL_0023: call ""void System.Console.WriteLine(string)"" - IL_0028: ldstr ""B"" - IL_002d: call ""void System.Console.WriteLine(string)"" - IL_0032: ldstr ""B"" - IL_0037: call ""void System.Console.WriteLine(string)"" - IL_003c: ldstr """" - IL_0041: call ""void System.Console.WriteLine(string)"" - IL_0046: ldstr ""#"" - IL_004b: call ""void System.Console.WriteLine(string)"" - IL_0050: ldstr """" - IL_0055: call ""void System.Console.WriteLine(string)"" - IL_005a: ldstr ""#"" - IL_005f: call ""void System.Console.WriteLine(string)"" - IL_0064: ret -} -"); - } - - [Fact] - public void ConcatFour() - { - var source = @" -using System; - -public class Test -{ - static void Main() - { - var s = ""qq""; - var ss = s + s + s + s; - Console.WriteLine(ss); - } -} -"; - var comp = CompileAndVerify(source, expectedOutput: @"qqqqqqqq" -); - - comp.VerifyDiagnostics(); - comp.VerifyIL("Test.Main", @" -{ - // Code size 19 (0x13) - .maxstack 4 - IL_0000: ldstr ""qq"" - IL_0005: dup - IL_0006: dup - IL_0007: dup - IL_0008: call ""string string.Concat(string, string, string, string)"" - IL_000d: call ""void System.Console.WriteLine(string)"" - IL_0012: ret -} -"); - } - - [Fact] - public void ConcatMerge() - { - var source = @" -using System; - -public class Test -{ - private static string S = ""F""; - private static object O = ""O""; - - static void Main() - { - Console.WriteLine( (S + ""A"") + (""B"" + S)); - Console.WriteLine( (O + ""A"") + (""B"" + O)); - Console.WriteLine( ((S + ""A"") + (""B"" + S)) + ((O + ""A"") + (""B"" + O))); - Console.WriteLine( ((O + ""A"") + (S + ""A"")) + ((""B"" + O) + (S + ""A""))); - } -} -"; - var comp = CompileAndVerify(source, expectedOutput: @"FABF -OABO -FABFOABO -OAFABOFA"); - - comp.VerifyDiagnostics(); - comp.VerifyIL("Test.Main", @" -{ - // Code size 259 (0x103) - .maxstack 5 - IL_0000: ldsfld ""string Test.S"" - IL_0005: ldstr ""AB"" - IL_000a: ldsfld ""string Test.S"" - IL_000f: call ""string string.Concat(string, string, string)"" - IL_0014: call ""void System.Console.WriteLine(string)"" - IL_0019: ldsfld ""object Test.O"" - IL_001e: dup - IL_001f: brtrue.s IL_0025 - IL_0021: pop - IL_0022: ldnull - IL_0023: br.s IL_002a - IL_0025: callvirt ""string object.ToString()"" - IL_002a: ldstr ""AB"" - IL_002f: ldsfld ""object Test.O"" - IL_0034: dup - IL_0035: brtrue.s IL_003b - IL_0037: pop - IL_0038: ldnull - IL_0039: br.s IL_0040 - IL_003b: callvirt ""string object.ToString()"" - IL_0040: call ""string string.Concat(string, string, string)"" - IL_0045: call ""void System.Console.WriteLine(string)"" - IL_004a: ldc.i4.6 - IL_004b: newarr ""string"" - IL_0050: dup - IL_0051: ldc.i4.0 - IL_0052: ldsfld ""string Test.S"" - IL_0057: stelem.ref - IL_0058: dup - IL_0059: ldc.i4.1 - IL_005a: ldstr ""AB"" - IL_005f: stelem.ref - IL_0060: dup - IL_0061: ldc.i4.2 - IL_0062: ldsfld ""string Test.S"" - IL_0067: stelem.ref - IL_0068: dup - IL_0069: ldc.i4.3 - IL_006a: ldsfld ""object Test.O"" - IL_006f: dup - IL_0070: brtrue.s IL_0076 - IL_0072: pop - IL_0073: ldnull - IL_0074: br.s IL_007b - IL_0076: callvirt ""string object.ToString()"" - IL_007b: stelem.ref - IL_007c: dup - IL_007d: ldc.i4.4 - IL_007e: ldstr ""AB"" - IL_0083: stelem.ref - IL_0084: dup - IL_0085: ldc.i4.5 - IL_0086: ldsfld ""object Test.O"" - IL_008b: dup - IL_008c: brtrue.s IL_0092 - IL_008e: pop - IL_008f: ldnull - IL_0090: br.s IL_0097 - IL_0092: callvirt ""string object.ToString()"" - IL_0097: stelem.ref - IL_0098: call ""string string.Concat(params string[])"" - IL_009d: call ""void System.Console.WriteLine(string)"" - IL_00a2: ldc.i4.7 - IL_00a3: newarr ""string"" - IL_00a8: dup - IL_00a9: ldc.i4.0 - IL_00aa: ldsfld ""object Test.O"" - IL_00af: dup - IL_00b0: brtrue.s IL_00b6 - IL_00b2: pop - IL_00b3: ldnull - IL_00b4: br.s IL_00bb - IL_00b6: callvirt ""string object.ToString()"" - IL_00bb: stelem.ref - IL_00bc: dup - IL_00bd: ldc.i4.1 - IL_00be: ldstr ""A"" - IL_00c3: stelem.ref - IL_00c4: dup - IL_00c5: ldc.i4.2 - IL_00c6: ldsfld ""string Test.S"" - IL_00cb: stelem.ref - IL_00cc: dup - IL_00cd: ldc.i4.3 - IL_00ce: ldstr ""AB"" - IL_00d3: stelem.ref - IL_00d4: dup - IL_00d5: ldc.i4.4 - IL_00d6: ldsfld ""object Test.O"" - IL_00db: dup - IL_00dc: brtrue.s IL_00e2 - IL_00de: pop - IL_00df: ldnull - IL_00e0: br.s IL_00e7 - IL_00e2: callvirt ""string object.ToString()"" - IL_00e7: stelem.ref - IL_00e8: dup - IL_00e9: ldc.i4.5 - IL_00ea: ldsfld ""string Test.S"" - IL_00ef: stelem.ref - IL_00f0: dup - IL_00f1: ldc.i4.6 - IL_00f2: ldstr ""A"" - IL_00f7: stelem.ref - IL_00f8: call ""string string.Concat(params string[])"" - IL_00fd: call ""void System.Console.WriteLine(string)"" - IL_0102: ret -} -"); - } - - [ConditionalFact(typeof(WindowsDesktopOnly), Reason = ConditionalSkipReason.TestExecutionNeedsDesktopTypes)] - [WorkItem(37830, "https://github.com/dotnet/roslyn/issues/37830")] - public void ConcatMerge_MarshalByRefObject() - { - var source = @" -using System; -using System.Runtime.Remoting; -using System.Runtime.Remoting.Messaging; -using System.Runtime.Remoting.Proxies; - -class MyProxy : RealProxy -{ - readonly MarshalByRefObject target; - - public MyProxy(MarshalByRefObject target) : base(target.GetType()) - { - this.target = target; - } - - public override IMessage Invoke(IMessage request) - { - IMethodCallMessage call = (IMethodCallMessage)request; - IMethodReturnMessage res = RemotingServices.ExecuteMessage(target, call); - return res; - } -} - -class R1 : MarshalByRefObject -{ - public int test_field = 5; -} - -class Test -{ - static void Main() - { - R1 myobj = new R1(); - MyProxy real_proxy = new MyProxy(myobj); - R1 o = (R1)real_proxy.GetTransparentProxy(); - o.test_field = 2; - Console.WriteLine(""test_field: "" + o.test_field); - } -} -"; - var comp = CompileAndVerify(source, expectedOutput: @"test_field: 2"); - comp.VerifyDiagnostics(); - // Note: we use ldfld on the field, but not ldflda, because the type is MarshalByRefObject - comp.VerifyIL("Test.Main", @" -{ - // Code size 64 (0x40) - .maxstack 2 - .locals init (R1 V_0, //o - int V_1) - IL_0000: newobj ""R1..ctor()"" - IL_0005: newobj ""MyProxy..ctor(System.MarshalByRefObject)"" - IL_000a: callvirt ""object System.Runtime.Remoting.Proxies.RealProxy.GetTransparentProxy()"" - IL_000f: castclass ""R1"" - IL_0014: stloc.0 - IL_0015: ldloc.0 - IL_0016: ldc.i4.2 - IL_0017: stfld ""int R1.test_field"" - IL_001c: ldstr ""test_field: "" - IL_0021: ldloc.0 - IL_0022: ldfld ""int R1.test_field"" - IL_0027: stloc.1 - IL_0028: ldloca.s V_1 - IL_002a: constrained. ""int"" - IL_0030: callvirt ""string object.ToString()"" - IL_0035: call ""string string.Concat(string, string)"" - IL_003a: call ""void System.Console.WriteLine(string)"" - IL_003f: ret -} -"); - } - - [Fact] - public void ConcatMergeFromOne() - { - var source = @" -using System; - -public class Test -{ - private static string S = ""F""; - - static void Main() - { - Console.WriteLine( (S + null) + (S + ""A"") + (""B"" + S) + (S + null)); - } -} -"; - var comp = CompileAndVerify(source, expectedOutput: @"FFABFF"); - - comp.VerifyDiagnostics(); - comp.VerifyIL("Test.Main", @" -{ - // Code size 57 (0x39) - .maxstack 4 - IL_0000: ldc.i4.5 - IL_0001: newarr ""string"" - IL_0006: dup - IL_0007: ldc.i4.0 - IL_0008: ldsfld ""string Test.S"" - IL_000d: stelem.ref - IL_000e: dup - IL_000f: ldc.i4.1 - IL_0010: ldsfld ""string Test.S"" - IL_0015: stelem.ref - IL_0016: dup - IL_0017: ldc.i4.2 - IL_0018: ldstr ""AB"" - IL_001d: stelem.ref - IL_001e: dup - IL_001f: ldc.i4.3 - IL_0020: ldsfld ""string Test.S"" - IL_0025: stelem.ref - IL_0026: dup - IL_0027: ldc.i4.4 - IL_0028: ldsfld ""string Test.S"" - IL_002d: stelem.ref - IL_002e: call ""string string.Concat(params string[])"" - IL_0033: call ""void System.Console.WriteLine(string)"" - IL_0038: ret -} -"); - } - - [Fact] - public void ConcatOneArg() - { - var source = @" -using System; - -public class Test -{ - private static string S = ""F""; - private static object O = ""O""; - - static void Main() - { - Console.WriteLine(O + null); - Console.WriteLine(S + null); - Console.WriteLine(O?.ToString() + null); - } -} -"; - var comp = CompileAndVerify(source, expectedOutput: @"O -F -O"); - - comp.VerifyDiagnostics(); - comp.VerifyIL("Test.Main", @" -{ - // Code size 82 (0x52) - .maxstack 2 - IL_0000: ldsfld ""object Test.O"" - IL_0005: dup - IL_0006: brtrue.s IL_000c - IL_0008: pop - IL_0009: ldnull - IL_000a: br.s IL_0011 - IL_000c: callvirt ""string object.ToString()"" - IL_0011: dup - IL_0012: brtrue.s IL_001a - IL_0014: pop - IL_0015: ldstr """" - IL_001a: call ""void System.Console.WriteLine(string)"" - IL_001f: ldsfld ""string Test.S"" - IL_0024: dup - IL_0025: brtrue.s IL_002d - IL_0027: pop - IL_0028: ldstr """" - IL_002d: call ""void System.Console.WriteLine(string)"" - IL_0032: ldsfld ""object Test.O"" - IL_0037: dup - IL_0038: brtrue.s IL_003e - IL_003a: pop - IL_003b: ldnull - IL_003c: br.s IL_0043 - IL_003e: callvirt ""string object.ToString()"" - IL_0043: dup - IL_0044: brtrue.s IL_004c - IL_0046: pop - IL_0047: ldstr """" - IL_004c: call ""void System.Console.WriteLine(string)"" - IL_0051: ret -} -"); - } - - [Fact] - public void ConcatOneArgWithNullToString() - { - var source = @" -using System; - -public class Test -{ - private static object C = new C(); - - static void Main() - { - Console.WriteLine((C + null) == """" ? ""Y"" : ""N""); - Console.WriteLine((C + null + null) == """" ? ""Y"" : ""N""); - } -} - -public class C -{ - public override string ToString() => null; -} -"; - var comp = CompileAndVerify(source, expectedOutput: @"Y -Y"); - - comp.VerifyDiagnostics(); - comp.VerifyIL("Test.Main", @" -{ - // Code size 111 (0x6f) - .maxstack 2 - IL_0000: ldsfld ""object Test.C"" - IL_0005: dup - IL_0006: brtrue.s IL_000c - IL_0008: pop - IL_0009: ldnull - IL_000a: br.s IL_0011 - IL_000c: callvirt ""string object.ToString()"" - IL_0011: dup - IL_0012: brtrue.s IL_001a - IL_0014: pop - IL_0015: ldstr """" - IL_001a: ldstr """" - IL_001f: call ""bool string.op_Equality(string, string)"" - IL_0024: brtrue.s IL_002d - IL_0026: ldstr ""N"" - IL_002b: br.s IL_0032 - IL_002d: ldstr ""Y"" - IL_0032: call ""void System.Console.WriteLine(string)"" - IL_0037: ldsfld ""object Test.C"" - IL_003c: dup - IL_003d: brtrue.s IL_0043 - IL_003f: pop - IL_0040: ldnull - IL_0041: br.s IL_0048 - IL_0043: callvirt ""string object.ToString()"" - IL_0048: dup - IL_0049: brtrue.s IL_0051 - IL_004b: pop - IL_004c: ldstr """" - IL_0051: ldstr """" - IL_0056: call ""bool string.op_Equality(string, string)"" - IL_005b: brtrue.s IL_0064 - IL_005d: ldstr ""N"" - IL_0062: br.s IL_0069 - IL_0064: ldstr ""Y"" - IL_0069: call ""void System.Console.WriteLine(string)"" - IL_006e: ret -} -"); - } - - [Fact] - public void ConcatOneArgWithExplicitConcatCall() - { - var source = @" -using System; - -public class Test -{ - private static object O = ""O""; - - static void Main() - { - Console.WriteLine(string.Concat(O) + null); - Console.WriteLine(string.Concat(O) + null + null); - } -} -"; - var comp = CompileAndVerify(source, expectedOutput: @"O -O"); - - comp.VerifyDiagnostics(); - comp.VerifyIL("Test.Main", @" -{ - // Code size 49 (0x31) - .maxstack 2 - IL_0000: ldsfld ""object Test.O"" - IL_0005: call ""string string.Concat(object)"" - IL_000a: dup - IL_000b: brtrue.s IL_0013 - IL_000d: pop - IL_000e: ldstr """" - IL_0013: call ""void System.Console.WriteLine(string)"" - IL_0018: ldsfld ""object Test.O"" - IL_001d: call ""string string.Concat(object)"" - IL_0022: dup - IL_0023: brtrue.s IL_002b - IL_0025: pop - IL_0026: ldstr """" - IL_002b: call ""void System.Console.WriteLine(string)"" - IL_0030: ret -} -"); - } - - [Fact] - public void ConcatEmptyString() - { - var source = @" -using System; - -public class Test -{ - private static string S = ""F""; - private static object O = ""O""; - - static void Main() - { - Console.WriteLine(O + """"); - Console.WriteLine(S + """"); - } -} -"; - var comp = CompileAndVerify(source, expectedOutput: @"O -F"); - - comp.VerifyDiagnostics(); - comp.VerifyIL("Test.Main", @" -{ - // Code size 51 (0x33) - .maxstack 2 - IL_0000: ldsfld ""object Test.O"" - IL_0005: dup - IL_0006: brtrue.s IL_000c - IL_0008: pop - IL_0009: ldnull - IL_000a: br.s IL_0011 - IL_000c: callvirt ""string object.ToString()"" - IL_0011: dup - IL_0012: brtrue.s IL_001a - IL_0014: pop - IL_0015: ldstr """" - IL_001a: call ""void System.Console.WriteLine(string)"" - IL_001f: ldsfld ""string Test.S"" - IL_0024: dup - IL_0025: brtrue.s IL_002d - IL_0027: pop - IL_0028: ldstr """" - IL_002d: call ""void System.Console.WriteLine(string)"" - IL_0032: ret -} -"); - } - - [Fact] - [WorkItem(679120, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/679120")] - public void ConcatEmptyArray() - { - var source = @" -using System; - -public class Test -{ - static void Main() - { - Console.WriteLine(""Start""); - Console.WriteLine(string.Concat(new string[] {})); - Console.WriteLine(string.Concat(new string[] {}) + string.Concat(new string[] {})); - Console.WriteLine(""A"" + string.Concat(new string[] {})); - Console.WriteLine(string.Concat(new string[] {}) + ""B""); - Console.WriteLine(""End""); - } -} -"; - var comp = CompileAndVerify(source, expectedOutput: @"Start - - -A -B -End"); - - comp.VerifyDiagnostics(); - // NOTE: Dev11 doesn't optimize away string.Concat(new string[0]) either. - // We could add an optimization, but it's unlikely to occur in real code. - comp.VerifyIL("Test.Main", @" -{ - // Code size 67 (0x43) - .maxstack 1 - IL_0000: ldstr ""Start"" - IL_0005: call ""void System.Console.WriteLine(string)"" - IL_000a: ldc.i4.0 - IL_000b: newarr ""string"" - IL_0010: call ""string string.Concat(params string[])"" - IL_0015: call ""void System.Console.WriteLine(string)"" - IL_001a: ldstr """" - IL_001f: call ""void System.Console.WriteLine(string)"" - IL_0024: ldstr ""A"" - IL_0029: call ""void System.Console.WriteLine(string)"" - IL_002e: ldstr ""B"" - IL_0033: call ""void System.Console.WriteLine(string)"" - IL_0038: ldstr ""End"" - IL_003d: call ""void System.Console.WriteLine(string)"" - IL_0042: ret -} -"); - } - - [WorkItem(529064, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/529064")] - [Fact] - public void TestStringConcatOnLiteralAndCompound() - { - var source = @" -public class Test -{ - static string field01 = ""A""; - static string field02 = ""B""; - static void Main() - { - field01 += field02 + ""C"" + ""D""; - } -} -"; - var comp = CompileAndVerify(source); - comp.VerifyDiagnostics(); - comp.VerifyIL("Test.Main", @" -{ - // Code size 26 (0x1a) - .maxstack 3 - IL_0000: ldsfld ""string Test.field01"" - IL_0005: ldsfld ""string Test.field02"" - IL_000a: ldstr ""CD"" - IL_000f: call ""string string.Concat(string, string, string)"" - IL_0014: stsfld ""string Test.field01"" - IL_0019: ret -} -"); - } - - [Fact] - public void ConcatGeneric() - { - var source = @" -using System; - -public class Test -{ - static void Main() - { - TestMethod(); - } - - private static void TestMethod() - { - Console.WriteLine(""A"" + default(T)); - Console.WriteLine(""A"" + default(T) + ""A"" + default(T)); - - Console.WriteLine(default(T) + ""B""); - - Console.WriteLine(default(string) + default(T)); - Console.WriteLine(""#""); - Console.WriteLine(default(T) + default(string)); - Console.WriteLine(""#""); - } -} - -"; - var comp = CompileAndVerify(source, expectedOutput: @"A0 -A0A0 -0B -0 -# -0 -#"); - - comp.VerifyDiagnostics(); - comp.VerifyIL("Test.TestMethod()", @" -{ - // Code size 291 (0x123) - .maxstack 4 - .locals init (T V_0) - IL_0000: ldstr ""A"" - IL_0005: ldloca.s V_0 - IL_0007: initobj ""T"" - IL_000d: ldloc.0 - IL_000e: box ""T"" - IL_0013: brtrue.s IL_0018 - IL_0015: ldnull - IL_0016: br.s IL_0025 - IL_0018: ldloca.s V_0 - IL_001a: constrained. ""T"" - IL_0020: callvirt ""string object.ToString()"" - IL_0025: call ""string string.Concat(string, string)"" - IL_002a: call ""void System.Console.WriteLine(string)"" - IL_002f: ldstr ""A"" - IL_0034: ldloca.s V_0 - IL_0036: initobj ""T"" - IL_003c: ldloc.0 - IL_003d: box ""T"" - IL_0042: brtrue.s IL_0047 - IL_0044: ldnull - IL_0045: br.s IL_0054 - IL_0047: ldloca.s V_0 - IL_0049: constrained. ""T"" - IL_004f: callvirt ""string object.ToString()"" - IL_0054: ldstr ""A"" - IL_0059: ldloca.s V_0 - IL_005b: initobj ""T"" - IL_0061: ldloc.0 - IL_0062: box ""T"" - IL_0067: brtrue.s IL_006c - IL_0069: ldnull - IL_006a: br.s IL_0079 - IL_006c: ldloca.s V_0 - IL_006e: constrained. ""T"" - IL_0074: callvirt ""string object.ToString()"" - IL_0079: call ""string string.Concat(string, string, string, string)"" - IL_007e: call ""void System.Console.WriteLine(string)"" - IL_0083: ldloca.s V_0 - IL_0085: initobj ""T"" - IL_008b: ldloc.0 - IL_008c: box ""T"" - IL_0091: brtrue.s IL_0096 - IL_0093: ldnull - IL_0094: br.s IL_00a3 - IL_0096: ldloca.s V_0 - IL_0098: constrained. ""T"" - IL_009e: callvirt ""string object.ToString()"" - IL_00a3: ldstr ""B"" - IL_00a8: call ""string string.Concat(string, string)"" - IL_00ad: call ""void System.Console.WriteLine(string)"" - IL_00b2: ldloca.s V_0 - IL_00b4: initobj ""T"" - IL_00ba: ldloc.0 - IL_00bb: box ""T"" - IL_00c0: brtrue.s IL_00c5 - IL_00c2: ldnull - IL_00c3: br.s IL_00d2 - IL_00c5: ldloca.s V_0 - IL_00c7: constrained. ""T"" - IL_00cd: callvirt ""string object.ToString()"" - IL_00d2: dup - IL_00d3: brtrue.s IL_00db - IL_00d5: pop - IL_00d6: ldstr """" - IL_00db: call ""void System.Console.WriteLine(string)"" - IL_00e0: ldstr ""#"" - IL_00e5: call ""void System.Console.WriteLine(string)"" - IL_00ea: ldloca.s V_0 - IL_00ec: initobj ""T"" - IL_00f2: ldloc.0 - IL_00f3: box ""T"" - IL_00f8: brtrue.s IL_00fd - IL_00fa: ldnull - IL_00fb: br.s IL_010a - IL_00fd: ldloca.s V_0 - IL_00ff: constrained. ""T"" - IL_0105: callvirt ""string object.ToString()"" - IL_010a: dup - IL_010b: brtrue.s IL_0113 - IL_010d: pop - IL_010e: ldstr """" - IL_0113: call ""void System.Console.WriteLine(string)"" - IL_0118: ldstr ""#"" - IL_011d: call ""void System.Console.WriteLine(string)"" - IL_0122: ret -} -"); - } - - [Fact] - public void ConcatGenericConstrained() - { - var source = @" -using System; - -public class Test -{ - static void Main() - { - TestMethod(); - } - - private static void TestMethod() where T:class where U: class - { - Console.WriteLine(""A"" + default(T)); - Console.WriteLine(""A"" + default(T) + ""A"" + default(T)); - - Console.WriteLine(default(T) + ""B""); - - Console.WriteLine(default(string) + default(T)); - Console.WriteLine(""#""); - Console.WriteLine(default(T) + default(string)); - Console.WriteLine(""#""); - - Console.WriteLine(""A"" + (U)null); - Console.WriteLine(""A"" + (U)null + ""A"" + (U)null); - - Console.WriteLine((U)null + ""B""); - - Console.WriteLine(default(string) + (U)null); - Console.WriteLine(""#""); - Console.WriteLine((U)null + default(string)); - Console.WriteLine(""#""); - } -} - -"; - var comp = CompileAndVerify(source, expectedOutput: @"A -AA -B - -# - -# -A -AA -B - -# - -#"); - - comp.VerifyDiagnostics(); - comp.VerifyIL("Test.TestMethod()", @" -{ - // Code size 141 (0x8d) - .maxstack 1 - IL_0000: ldstr ""A"" - IL_0005: call ""void System.Console.WriteLine(string)"" - IL_000a: ldstr ""AA"" - IL_000f: call ""void System.Console.WriteLine(string)"" - IL_0014: ldstr ""B"" - IL_0019: call ""void System.Console.WriteLine(string)"" - IL_001e: ldstr """" - IL_0023: call ""void System.Console.WriteLine(string)"" - IL_0028: ldstr ""#"" - IL_002d: call ""void System.Console.WriteLine(string)"" - IL_0032: ldstr """" - IL_0037: call ""void System.Console.WriteLine(string)"" - IL_003c: ldstr ""#"" - IL_0041: call ""void System.Console.WriteLine(string)"" - IL_0046: ldstr ""A"" - IL_004b: call ""void System.Console.WriteLine(string)"" - IL_0050: ldstr ""AA"" - IL_0055: call ""void System.Console.WriteLine(string)"" - IL_005a: ldstr ""B"" - IL_005f: call ""void System.Console.WriteLine(string)"" - IL_0064: ldstr """" - IL_0069: call ""void System.Console.WriteLine(string)"" - IL_006e: ldstr ""#"" - IL_0073: call ""void System.Console.WriteLine(string)"" - IL_0078: ldstr """" - IL_007d: call ""void System.Console.WriteLine(string)"" - IL_0082: ldstr ""#"" - IL_0087: call ""void System.Console.WriteLine(string)"" - IL_008c: ret -} -"); - } - - [Fact] - public void ConcatGenericUnconstrained() - { - var source = @" -using System; -class Test -{ - static void Main() - { - var p1 = new Printer(""F""); - p1.Print(""P""); - p1.Print(null); - var p2 = new Printer(null); - p2.Print(""P""); - p2.Print(null); - var p3 = new Printer(new MutableStruct()); - MutableStruct m = new MutableStruct(); - p3.Print(m); - p3.Print(m); - } -} - -class Printer -{ - private T field; - public Printer(T field) => this.field = field; - public void Print(T p) - { - Console.WriteLine("""" + p + p + field + field); - } -} - -struct MutableStruct -{ - private int i; - public override string ToString() => (++i).ToString(); -} -"; - var comp = CompileAndVerify(source, expectedOutput: @"PPFF -FF -PP - -1111 -1111"); - - comp.VerifyDiagnostics(); - comp.VerifyIL("Printer.Print", @" -{ - // Code size 125 (0x7d) - .maxstack 4 - .locals init (T V_0) - IL_0000: ldarg.1 - IL_0001: stloc.0 - IL_0002: ldloc.0 - IL_0003: box ""T"" - IL_0008: brtrue.s IL_000d - IL_000a: ldnull - IL_000b: br.s IL_001a - IL_000d: ldloca.s V_0 - IL_000f: constrained. ""T"" - IL_0015: callvirt ""string object.ToString()"" - IL_001a: ldarg.1 - IL_001b: stloc.0 - IL_001c: ldloc.0 - IL_001d: box ""T"" - IL_0022: brtrue.s IL_0027 - IL_0024: ldnull - IL_0025: br.s IL_0034 - IL_0027: ldloca.s V_0 - IL_0029: constrained. ""T"" - IL_002f: callvirt ""string object.ToString()"" - IL_0034: ldarg.0 - IL_0035: ldfld ""T Printer.field"" - IL_003a: stloc.0 - IL_003b: ldloc.0 - IL_003c: box ""T"" - IL_0041: brtrue.s IL_0046 - IL_0043: ldnull - IL_0044: br.s IL_0053 - IL_0046: ldloca.s V_0 - IL_0048: constrained. ""T"" - IL_004e: callvirt ""string object.ToString()"" - IL_0053: ldarg.0 - IL_0054: ldfld ""T Printer.field"" - IL_0059: stloc.0 - IL_005a: ldloc.0 - IL_005b: box ""T"" - IL_0060: brtrue.s IL_0065 - IL_0062: ldnull - IL_0063: br.s IL_0072 - IL_0065: ldloca.s V_0 - IL_0067: constrained. ""T"" - IL_006d: callvirt ""string object.ToString()"" - IL_0072: call ""string string.Concat(string, string, string, string)"" - IL_0077: call ""void System.Console.WriteLine(string)"" - IL_007c: ret -} -"); - } - - [Fact] - public void ConcatGenericConstrainedClass() - { - var source = @" -using System; -class Test -{ - static void Main() - { - var p1 = new Printer(""F""); - p1.Print(""P""); - p1.Print(null); - var p2 = new Printer(null); - p2.Print(""P""); - p2.Print(null); - } -} - -class Printer where T : class -{ - private T field; - public Printer(T field) => this.field = field; - public void Print(T p) - { - Console.WriteLine("""" + p + p + field + field); - } -}"; - - var comp = CompileAndVerify(source, expectedOutput: @"PPFF -FF -PP -"); - - comp.VerifyDiagnostics(); - comp.VerifyIL("Printer.Print", @" -{ - // Code size 93 (0x5d) - .maxstack 5 - IL_0000: ldarg.1 - IL_0001: box ""T"" - IL_0006: dup - IL_0007: brtrue.s IL_000d - IL_0009: pop - IL_000a: ldnull - IL_000b: br.s IL_0012 - IL_000d: callvirt ""string object.ToString()"" - IL_0012: ldarg.1 - IL_0013: box ""T"" - IL_0018: dup - IL_0019: brtrue.s IL_001f - IL_001b: pop - IL_001c: ldnull - IL_001d: br.s IL_0024 - IL_001f: callvirt ""string object.ToString()"" - IL_0024: ldarg.0 - IL_0025: ldfld ""T Printer.field"" - IL_002a: box ""T"" - IL_002f: dup - IL_0030: brtrue.s IL_0036 - IL_0032: pop - IL_0033: ldnull - IL_0034: br.s IL_003b - IL_0036: callvirt ""string object.ToString()"" - IL_003b: ldarg.0 - IL_003c: ldfld ""T Printer.field"" - IL_0041: box ""T"" - IL_0046: dup - IL_0047: brtrue.s IL_004d - IL_0049: pop - IL_004a: ldnull - IL_004b: br.s IL_0052 - IL_004d: callvirt ""string object.ToString()"" - IL_0052: call ""string string.Concat(string, string, string, string)"" - IL_0057: call ""void System.Console.WriteLine(string)"" - IL_005c: ret -} -"); - - } - - [Fact] - public void ConcatGenericConstrainedStruct() - { - var source = @" -using System; -class Test -{ - static void Main() - { - MutableStruct m = new MutableStruct(); - var p1 = new Printer(new MutableStruct()); - p1.Print(m); - p1.Print(m); - } -} - -class Printer where T : struct -{ - private T field; - public Printer(T field) => this.field = field; - public void Print(T p) - { - Console.WriteLine("""" + p + p + field + field); - } -} - -struct MutableStruct -{ - private int i; - public override string ToString() => (++i).ToString(); -}"; - - var comp = CompileAndVerify(source, expectedOutput: @"1111 -1111"); - - comp.VerifyDiagnostics(); - comp.VerifyIL("Printer.Print", @" -{ - // Code size 81 (0x51) - .maxstack 4 - .locals init (T V_0) - IL_0000: ldarg.1 - IL_0001: stloc.0 - IL_0002: ldloca.s V_0 - IL_0004: constrained. ""T"" - IL_000a: callvirt ""string object.ToString()"" - IL_000f: ldarg.1 - IL_0010: stloc.0 - IL_0011: ldloca.s V_0 - IL_0013: constrained. ""T"" - IL_0019: callvirt ""string object.ToString()"" - IL_001e: ldarg.0 - IL_001f: ldfld ""T Printer.field"" - IL_0024: stloc.0 - IL_0025: ldloca.s V_0 - IL_0027: constrained. ""T"" - IL_002d: callvirt ""string object.ToString()"" - IL_0032: ldarg.0 - IL_0033: ldfld ""T Printer.field"" - IL_0038: stloc.0 - IL_0039: ldloca.s V_0 - IL_003b: constrained. ""T"" - IL_0041: callvirt ""string object.ToString()"" - IL_0046: call ""string string.Concat(string, string, string, string)"" - IL_004b: call ""void System.Console.WriteLine(string)"" - IL_0050: ret -} -"); - - } - - [Fact] - public void ConcatWithOtherOptimizations() - { - var source = @" -using System; - -public class Test -{ - static void Main() - { - var expr1 = ""hi""; - var expr2 = ""bye""; - - // expr1 is optimized away - // only expr2 should be lifted!! - Func f = () => (""abc"" + ""def"" + null ?? expr1 + ""moo"" + ""baz"") + expr2; - - System.Console.WriteLine(f()); - } -} -"; - var comp = CompileAndVerify(source, expectedOutput: @"abcdefbye"); - - comp.VerifyDiagnostics(); - - // IMPORTANT!! only c__DisplayClass0.expr2 should be initialized, - // there should not be such thing as c__DisplayClass0.expr1 - comp.VerifyIL("Test.Main", @" -{ - // Code size 38 (0x26) - .maxstack 3 - IL_0000: newobj ""Test.<>c__DisplayClass0_0..ctor()"" - IL_0005: dup - IL_0006: ldstr ""bye"" - IL_000b: stfld ""string Test.<>c__DisplayClass0_0.expr2"" - IL_0010: ldftn ""string Test.<>c__DisplayClass0_0.
b__0()"" - IL_0016: newobj ""System.Func..ctor(object, System.IntPtr)"" - IL_001b: callvirt ""string System.Func.Invoke()"" - IL_0020: call ""void System.Console.WriteLine(string)"" - IL_0025: ret -} -"); - } - - [Fact, WorkItem(1092853, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1092853")] - public void ConcatWithNullCoalescedNullLiteral() - { - const string source = @" -class Repro -{ - static string Bug(string s) - { - string x = """"; - x += s ?? null; - return x; - } - - static void Main() - { - System.Console.Write(""\""{0}\"""", Bug(null)); - } -}"; - - var comp = CompileAndVerify(source, expectedOutput: "\"\""); - - comp.VerifyDiagnostics(); - comp.VerifyIL("Repro.Bug", @" -{ - // Code size 17 (0x11) - .maxstack 3 - IL_0000: ldstr """" - IL_0005: ldarg.0 - IL_0006: dup - IL_0007: brtrue.s IL_000b - IL_0009: pop - IL_000a: ldnull - IL_000b: call ""string string.Concat(string, string)"" - IL_0010: ret -} -"); - } - - [Fact, WorkItem(1092853, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1092853")] - public void ConcatWithNullCoalescedNullLiteral_2() - { - const string source = @" -class Repro -{ - static string Bug(string s) - { - string x = """"; - x += s ?? ((string)null ?? null); - return x; - } - - static void Main() - { - System.Console.Write(""\""{0}\"""", Bug(null)); - } -}"; - - var comp = CompileAndVerify(source, expectedOutput: "\"\""); - - comp.VerifyIL("Repro.Bug", @" -{ - // Code size 17 (0x11) - .maxstack 3 - IL_0000: ldstr """" - IL_0005: ldarg.0 - IL_0006: dup - IL_0007: brtrue.s IL_000b - IL_0009: pop - IL_000a: ldnull - IL_000b: call ""string string.Concat(string, string)"" - IL_0010: ret -} -"); - } - - [Fact] - public void ConcatMutableStruct() - { - var source = @" -using System; -class Test -{ - static MutableStruct f = new MutableStruct(); - - static void Main() - { - MutableStruct l = new MutableStruct(); - - Console.WriteLine("""" + l + l + f + f); - } -} - -struct MutableStruct -{ - private int i; - public override string ToString() => (++i).ToString(); -} -"; - - var comp = CompileAndVerify(source, expectedOutput: @"1111"); - - comp.VerifyDiagnostics(); - comp.VerifyIL("Test.Main", @" -{ - // Code size 87 (0x57) - .maxstack 4 - .locals init (MutableStruct V_0, //l - MutableStruct V_1) - IL_0000: ldloca.s V_0 - IL_0002: initobj ""MutableStruct"" - IL_0008: ldloc.0 - IL_0009: stloc.1 - IL_000a: ldloca.s V_1 - IL_000c: constrained. ""MutableStruct"" - IL_0012: callvirt ""string object.ToString()"" - IL_0017: ldloc.0 - IL_0018: stloc.1 - IL_0019: ldloca.s V_1 - IL_001b: constrained. ""MutableStruct"" - IL_0021: callvirt ""string object.ToString()"" - IL_0026: ldsfld ""MutableStruct Test.f"" - IL_002b: stloc.1 - IL_002c: ldloca.s V_1 - IL_002e: constrained. ""MutableStruct"" - IL_0034: callvirt ""string object.ToString()"" - IL_0039: ldsfld ""MutableStruct Test.f"" - IL_003e: stloc.1 - IL_003f: ldloca.s V_1 - IL_0041: constrained. ""MutableStruct"" - IL_0047: callvirt ""string object.ToString()"" - IL_004c: call ""string string.Concat(string, string, string, string)"" - IL_0051: call ""void System.Console.WriteLine(string)"" - IL_0056: ret -}"); - } - - [Fact] - public void ConcatMutableStructsSideEffects() - { - const string source = @" -using System; -using static System.Console; - -struct Mutable -{ - int x; - public override string ToString() => (x++).ToString(); -} - -class Test -{ - static Mutable m = new Mutable(); - - static void Main() - { - Write(""("" + m + "")""); // (0) - Write(""("" + m + "")""); // (0) - - Write(""("" + m.ToString() + "")""); // (0) - Write(""("" + m.ToString() + "")""); // (1) - Write(""("" + m.ToString() + "")""); // (2) - - Nullable n = new Mutable(); - Write(""("" + n + "")""); // (0) - Write(""("" + n + "")""); // (0) - - Write(""("" + n.ToString() + "")""); // (0) - Write(""("" + n.ToString() + "")""); // (1) - Write(""("" + n.ToString() + "")""); // (2) - } -}"; - - CompileAndVerify(source, expectedOutput: "(0)(0)(0)(1)(2)(0)(0)(0)(1)(2)"); - } - - [Fact] - public void ConcatReadonlyStruct() - { - var source = @" -using System; -class Test -{ - static ReadonlyStruct f = new ReadonlyStruct(); - - static void Main() - { - ReadonlyStruct l = new ReadonlyStruct(); - - Console.WriteLine("""" + l + l + f + f); - } -} - -readonly struct ReadonlyStruct -{ - public override string ToString() => ""R""; -} -"; - - var comp = CompileAndVerify(source, expectedOutput: @"RRRR"); - - comp.VerifyDiagnostics(); - comp.VerifyIL("Test.Main", @" -{ - // Code size 77 (0x4d) - .maxstack 4 - .locals init (ReadonlyStruct V_0) //l - IL_0000: ldloca.s V_0 - IL_0002: initobj ""ReadonlyStruct"" - IL_0008: ldloca.s V_0 - IL_000a: constrained. ""ReadonlyStruct"" - IL_0010: callvirt ""string object.ToString()"" - IL_0015: ldloca.s V_0 - IL_0017: constrained. ""ReadonlyStruct"" - IL_001d: callvirt ""string object.ToString()"" - IL_0022: ldsflda ""ReadonlyStruct Test.f"" - IL_0027: constrained. ""ReadonlyStruct"" - IL_002d: callvirt ""string object.ToString()"" - IL_0032: ldsflda ""ReadonlyStruct Test.f"" - IL_0037: constrained. ""ReadonlyStruct"" - IL_003d: callvirt ""string object.ToString()"" - IL_0042: call ""string string.Concat(string, string, string, string)"" - IL_0047: call ""void System.Console.WriteLine(string)"" - IL_004c: ret -} -"); - } - - [Fact] - public void ConcatStructWithReadonlyToString() - { - var source = @" -using System; -class Test -{ - static StructWithReadonlyToString f = new StructWithReadonlyToString(); - - static void Main() - { - StructWithReadonlyToString l = new StructWithReadonlyToString(); - - Console.WriteLine("""" + l + l + f + f); - } -} - -struct StructWithReadonlyToString -{ - public readonly override string ToString() => ""R""; -} -"; - - var comp = CompileAndVerify(source, expectedOutput: @"RRRR"); - - comp.VerifyDiagnostics(); - comp.VerifyIL("Test.Main", @" -{ - // Code size 77 (0x4d) - .maxstack 4 - .locals init (StructWithReadonlyToString V_0) //l - IL_0000: ldloca.s V_0 - IL_0002: initobj ""StructWithReadonlyToString"" - IL_0008: ldloca.s V_0 - IL_000a: constrained. ""StructWithReadonlyToString"" - IL_0010: callvirt ""string object.ToString()"" - IL_0015: ldloca.s V_0 - IL_0017: constrained. ""StructWithReadonlyToString"" - IL_001d: callvirt ""string object.ToString()"" - IL_0022: ldsflda ""StructWithReadonlyToString Test.f"" - IL_0027: constrained. ""StructWithReadonlyToString"" - IL_002d: callvirt ""string object.ToString()"" - IL_0032: ldsflda ""StructWithReadonlyToString Test.f"" - IL_0037: constrained. ""StructWithReadonlyToString"" - IL_003d: callvirt ""string object.ToString()"" - IL_0042: call ""string string.Concat(string, string, string, string)"" - IL_0047: call ""void System.Console.WriteLine(string)"" - IL_004c: ret -} -"); - } - - [Fact] - public void ConcatStructWithNoToString() - { - var source = @" -using System; -class Test -{ - static S f = new S(); - - static void Main() - { - S l = new S(); - - Console.WriteLine("""" + l + l + f + f); - } -} - -struct S { } -"; - - var comp = CompileAndVerify(source, expectedOutput: @"SSSS"); - - comp.VerifyDiagnostics(); - comp.VerifyIL("Test.Main", @" -{ - // Code size 77 (0x4d) - .maxstack 4 - .locals init (S V_0) //l - IL_0000: ldloca.s V_0 - IL_0002: initobj ""S"" - IL_0008: ldloca.s V_0 - IL_000a: constrained. ""S"" - IL_0010: callvirt ""string object.ToString()"" - IL_0015: ldloca.s V_0 - IL_0017: constrained. ""S"" - IL_001d: callvirt ""string object.ToString()"" - IL_0022: ldsflda ""S Test.f"" - IL_0027: constrained. ""S"" - IL_002d: callvirt ""string object.ToString()"" - IL_0032: ldsflda ""S Test.f"" - IL_0037: constrained. ""S"" - IL_003d: callvirt ""string object.ToString()"" - IL_0042: call ""string string.Concat(string, string, string, string)"" - IL_0047: call ""void System.Console.WriteLine(string)"" - IL_004c: ret -} -"); - } - - [Fact] - public void ConcatWithImplicitOperator() - { - var source = @" -using System; - -public class Test -{ - static void Main() - { - Console.WriteLine(""S"" + new Test()); - } - - public static implicit operator string(Test test) => ""T""; -} -"; - var comp = CompileAndVerify(source, expectedOutput: @"ST"); - - comp.VerifyDiagnostics(); - comp.VerifyIL("Test.Main", @" -{ - // Code size 26 (0x1a) - .maxstack 2 - IL_0000: ldstr ""S"" - IL_0005: newobj ""Test..ctor()"" - IL_000a: call ""string Test.op_Implicit(Test)"" - IL_000f: call ""string string.Concat(string, string)"" - IL_0014: call ""void System.Console.WriteLine(string)"" - IL_0019: ret -} -"); - } - - [Fact] - public void ConcatWithNull() - { - var source = @" -using System; - -public class Test -{ - public static Test T = null; - - static void Main() - { - Console.WriteLine(""S"" + T); - } -} -"; - var comp = CompileAndVerify(source, expectedOutput: @"S"); - - comp.VerifyDiagnostics(); - comp.VerifyIL("Test.Main", @" -{ - // Code size 33 (0x21) - .maxstack 3 - IL_0000: ldstr ""S"" - IL_0005: ldsfld ""Test Test.T"" - IL_000a: dup - IL_000b: brtrue.s IL_0011 - IL_000d: pop - IL_000e: ldnull - IL_000f: br.s IL_0016 - IL_0011: callvirt ""string object.ToString()"" - IL_0016: call ""string string.Concat(string, string)"" - IL_001b: call ""void System.Console.WriteLine(string)"" - IL_0020: ret -} -"); - } - - [Fact] - public void ConcatWithSpecialValueTypes() - { - var source = @" -using System; - -public class Test -{ - static void Main() - { - const char a = 'a', b = 'b'; - char c = 'c', d = 'd'; - - Console.WriteLine(a + ""1""); - Console.WriteLine(""2"" + b); - - Console.WriteLine(c + ""3""); - Console.WriteLine(""4"" + d); - - Console.WriteLine(true + ""5"" + c); - Console.WriteLine(""6"" + d + (IntPtr)7); - Console.WriteLine(""8"" + (UIntPtr)9 + false); - - Console.WriteLine(c + ""10"" + d + ""11""); - Console.WriteLine(""12"" + c + ""13"" + d); - - Console.WriteLine(a + ""14"" + b + ""15"" + a + ""16""); - Console.WriteLine(c + ""17"" + d + ""18"" + c + ""19""); - - Console.WriteLine(""20"" + 21 + c + d + c + d); - Console.WriteLine(""22"" + c + ""23"" + d + c + d); - } -} -"; - var comp = CompileAndVerify(source, expectedOutput: @"a1 -2b -c3 -4d -True5c -6d7 -89False -c10d11 -12c13d -a14b15a16 -c17d18c19 -2021cdcd -22c23dcd"); - - comp.VerifyDiagnostics(); - comp.VerifyIL("Test.Main", @" -{ - // Code size 477 (0x1dd) - .maxstack 4 - .locals init (char V_0, //c - char V_1, //d - bool V_2, - System.IntPtr V_3, - System.UIntPtr V_4, - int V_5) - IL_0000: ldc.i4.s 99 - IL_0002: stloc.0 - IL_0003: ldc.i4.s 100 - IL_0005: stloc.1 - IL_0006: ldstr ""a1"" - IL_000b: call ""void System.Console.WriteLine(string)"" - IL_0010: ldstr ""2b"" - IL_0015: call ""void System.Console.WriteLine(string)"" - IL_001a: ldloca.s V_0 - IL_001c: call ""string char.ToString()"" - IL_0021: ldstr ""3"" - IL_0026: call ""string string.Concat(string, string)"" - IL_002b: call ""void System.Console.WriteLine(string)"" - IL_0030: ldstr ""4"" - IL_0035: ldloca.s V_1 - IL_0037: call ""string char.ToString()"" - IL_003c: call ""string string.Concat(string, string)"" - IL_0041: call ""void System.Console.WriteLine(string)"" - IL_0046: ldc.i4.1 - IL_0047: stloc.2 - IL_0048: ldloca.s V_2 - IL_004a: call ""string bool.ToString()"" - IL_004f: ldstr ""5"" - IL_0054: ldloca.s V_0 - IL_0056: call ""string char.ToString()"" - IL_005b: call ""string string.Concat(string, string, string)"" - IL_0060: call ""void System.Console.WriteLine(string)"" - IL_0065: ldstr ""6"" - IL_006a: ldloca.s V_1 - IL_006c: call ""string char.ToString()"" - IL_0071: ldc.i4.7 - IL_0072: call ""System.IntPtr System.IntPtr.op_Explicit(int)"" - IL_0077: stloc.3 - IL_0078: ldloca.s V_3 - IL_007a: call ""string System.IntPtr.ToString()"" - IL_007f: call ""string string.Concat(string, string, string)"" - IL_0084: call ""void System.Console.WriteLine(string)"" - IL_0089: ldstr ""8"" - IL_008e: ldc.i4.s 9 - IL_0090: conv.i8 - IL_0091: call ""System.UIntPtr System.UIntPtr.op_Explicit(ulong)"" - IL_0096: stloc.s V_4 - IL_0098: ldloca.s V_4 - IL_009a: call ""string System.UIntPtr.ToString()"" - IL_009f: ldc.i4.0 - IL_00a0: stloc.2 - IL_00a1: ldloca.s V_2 - IL_00a3: call ""string bool.ToString()"" - IL_00a8: call ""string string.Concat(string, string, string)"" - IL_00ad: call ""void System.Console.WriteLine(string)"" - IL_00b2: ldloca.s V_0 - IL_00b4: call ""string char.ToString()"" - IL_00b9: ldstr ""10"" - IL_00be: ldloca.s V_1 - IL_00c0: call ""string char.ToString()"" - IL_00c5: ldstr ""11"" - IL_00ca: call ""string string.Concat(string, string, string, string)"" - IL_00cf: call ""void System.Console.WriteLine(string)"" - IL_00d4: ldstr ""12"" - IL_00d9: ldloca.s V_0 - IL_00db: call ""string char.ToString()"" - IL_00e0: ldstr ""13"" - IL_00e5: ldloca.s V_1 - IL_00e7: call ""string char.ToString()"" - IL_00ec: call ""string string.Concat(string, string, string, string)"" - IL_00f1: call ""void System.Console.WriteLine(string)"" - IL_00f6: ldstr ""a14b15a16"" - IL_00fb: call ""void System.Console.WriteLine(string)"" - IL_0100: ldc.i4.6 - IL_0101: newarr ""string"" - IL_0106: dup - IL_0107: ldc.i4.0 - IL_0108: ldloca.s V_0 - IL_010a: call ""string char.ToString()"" - IL_010f: stelem.ref - IL_0110: dup - IL_0111: ldc.i4.1 - IL_0112: ldstr ""17"" - IL_0117: stelem.ref - IL_0118: dup - IL_0119: ldc.i4.2 - IL_011a: ldloca.s V_1 - IL_011c: call ""string char.ToString()"" - IL_0121: stelem.ref - IL_0122: dup - IL_0123: ldc.i4.3 - IL_0124: ldstr ""18"" - IL_0129: stelem.ref - IL_012a: dup - IL_012b: ldc.i4.4 - IL_012c: ldloca.s V_0 - IL_012e: call ""string char.ToString()"" - IL_0133: stelem.ref - IL_0134: dup - IL_0135: ldc.i4.5 - IL_0136: ldstr ""19"" - IL_013b: stelem.ref - IL_013c: call ""string string.Concat(params string[])"" - IL_0141: call ""void System.Console.WriteLine(string)"" - IL_0146: ldc.i4.6 - IL_0147: newarr ""string"" - IL_014c: dup - IL_014d: ldc.i4.0 - IL_014e: ldstr ""20"" - IL_0153: stelem.ref - IL_0154: dup - IL_0155: ldc.i4.1 - IL_0156: ldc.i4.s 21 - IL_0158: stloc.s V_5 - IL_015a: ldloca.s V_5 - IL_015c: call ""string int.ToString()"" - IL_0161: stelem.ref - IL_0162: dup - IL_0163: ldc.i4.2 - IL_0164: ldloca.s V_0 - IL_0166: call ""string char.ToString()"" - IL_016b: stelem.ref - IL_016c: dup - IL_016d: ldc.i4.3 - IL_016e: ldloca.s V_1 - IL_0170: call ""string char.ToString()"" - IL_0175: stelem.ref - IL_0176: dup - IL_0177: ldc.i4.4 - IL_0178: ldloca.s V_0 - IL_017a: call ""string char.ToString()"" - IL_017f: stelem.ref - IL_0180: dup - IL_0181: ldc.i4.5 - IL_0182: ldloca.s V_1 - IL_0184: call ""string char.ToString()"" - IL_0189: stelem.ref - IL_018a: call ""string string.Concat(params string[])"" - IL_018f: call ""void System.Console.WriteLine(string)"" - IL_0194: ldc.i4.6 - IL_0195: newarr ""string"" - IL_019a: dup - IL_019b: ldc.i4.0 - IL_019c: ldstr ""22"" - IL_01a1: stelem.ref - IL_01a2: dup - IL_01a3: ldc.i4.1 - IL_01a4: ldloca.s V_0 - IL_01a6: call ""string char.ToString()"" - IL_01ab: stelem.ref - IL_01ac: dup - IL_01ad: ldc.i4.2 - IL_01ae: ldstr ""23"" - IL_01b3: stelem.ref - IL_01b4: dup - IL_01b5: ldc.i4.3 - IL_01b6: ldloca.s V_1 - IL_01b8: call ""string char.ToString()"" - IL_01bd: stelem.ref - IL_01be: dup - IL_01bf: ldc.i4.4 - IL_01c0: ldloca.s V_0 - IL_01c2: call ""string char.ToString()"" - IL_01c7: stelem.ref - IL_01c8: dup - IL_01c9: ldc.i4.5 - IL_01ca: ldloca.s V_1 - IL_01cc: call ""string char.ToString()"" - IL_01d1: stelem.ref - IL_01d2: call ""string string.Concat(params string[])"" - IL_01d7: call ""void System.Console.WriteLine(string)"" - IL_01dc: ret -} -"); - } - - [Fact] - public void ConcatExpressions() - { - var source = @" -using System; - -class Test -{ - static int X = 3; - static int Y = 4; - - static void Main() - { - Console.WriteLine(X + ""+"" + Y + ""="" + (X + Y)); - } -} -"; - - var comp = CompileAndVerify(source, expectedOutput: "3+4=7"); - comp.VerifyDiagnostics(); - comp.VerifyIL("Test.Main", @" -{ - // Code size 81 (0x51) - .maxstack 5 - .locals init (int V_0) - IL_0000: ldc.i4.5 - IL_0001: newarr ""string"" - IL_0006: dup - IL_0007: ldc.i4.0 - IL_0008: ldsflda ""int Test.X"" - IL_000d: call ""string int.ToString()"" - IL_0012: stelem.ref - IL_0013: dup - IL_0014: ldc.i4.1 - IL_0015: ldstr ""+"" - IL_001a: stelem.ref - IL_001b: dup - IL_001c: ldc.i4.2 - IL_001d: ldsflda ""int Test.Y"" - IL_0022: call ""string int.ToString()"" - IL_0027: stelem.ref - IL_0028: dup - IL_0029: ldc.i4.3 - IL_002a: ldstr ""="" - IL_002f: stelem.ref - IL_0030: dup - IL_0031: ldc.i4.4 - IL_0032: ldsfld ""int Test.X"" - IL_0037: ldsfld ""int Test.Y"" - IL_003c: add - IL_003d: stloc.0 - IL_003e: ldloca.s V_0 - IL_0040: call ""string int.ToString()"" - IL_0045: stelem.ref - IL_0046: call ""string string.Concat(params string[])"" - IL_004b: call ""void System.Console.WriteLine(string)"" - IL_0050: ret -}"); - } - - [Fact] - public void ConcatRefs() - { - var source = @" -using System; - -class Test -{ - static void Main() - { - string s1 = ""S1""; - string s2 = ""S2""; - int i1 = 3; - int i2 = 4; - object o1 = ""O1""; - object o2 = ""O2""; - Print(ref s1, ref i1, ref o1, ref s2, ref i2, ref o2); - } - - static void Print(ref string s, ref int i, ref object o, ref T1 t1, ref T2 t2, ref T3 t3) - where T1 : class - where T2 : struct - { - Console.WriteLine(s + i + o + t1 + t2 + t3); - } -} -"; - - var comp = CompileAndVerify(source, expectedOutput: "S13O1S24O2"); - comp.VerifyDiagnostics(); - comp.VerifyIL("Test.Print", @" -{ - // Code size 133 (0x85) - .maxstack 5 - .locals init (T2 V_0, - T3 V_1) - IL_0000: ldc.i4.6 - IL_0001: newarr ""string"" - IL_0006: dup - IL_0007: ldc.i4.0 - IL_0008: ldarg.0 - IL_0009: ldind.ref - IL_000a: stelem.ref - IL_000b: dup - IL_000c: ldc.i4.1 - IL_000d: ldarg.1 - IL_000e: call ""string int.ToString()"" - IL_0013: stelem.ref - IL_0014: dup - IL_0015: ldc.i4.2 - IL_0016: ldarg.2 - IL_0017: ldind.ref - IL_0018: dup - IL_0019: brtrue.s IL_001f - IL_001b: pop - IL_001c: ldnull - IL_001d: br.s IL_0024 - IL_001f: callvirt ""string object.ToString()"" - IL_0024: stelem.ref - IL_0025: dup - IL_0026: ldc.i4.3 - IL_0027: ldarg.3 - IL_0028: ldobj ""T1"" - IL_002d: box ""T1"" - IL_0032: dup - IL_0033: brtrue.s IL_0039 - IL_0035: pop - IL_0036: ldnull - IL_0037: br.s IL_003e - IL_0039: callvirt ""string object.ToString()"" - IL_003e: stelem.ref - IL_003f: dup - IL_0040: ldc.i4.4 - IL_0041: ldarg.s V_4 - IL_0043: ldobj ""T2"" - IL_0048: stloc.0 - IL_0049: ldloca.s V_0 - IL_004b: constrained. ""T2"" - IL_0051: callvirt ""string object.ToString()"" - IL_0056: stelem.ref - IL_0057: dup - IL_0058: ldc.i4.5 - IL_0059: ldarg.s V_5 - IL_005b: ldobj ""T3"" - IL_0060: stloc.1 - IL_0061: ldloc.1 - IL_0062: box ""T3"" - IL_0067: brtrue.s IL_006c - IL_0069: ldnull - IL_006a: br.s IL_0079 - IL_006c: ldloca.s V_1 - IL_006e: constrained. ""T3"" - IL_0074: callvirt ""string object.ToString()"" - IL_0079: stelem.ref - IL_007a: call ""string string.Concat(params string[])"" - IL_007f: call ""void System.Console.WriteLine(string)"" - IL_0084: ret -}"); - } - } -} diff --git a/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenStringConcat.cs b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenStringConcat.cs new file mode 100644 index 0000000000000..ef0a2084a1e90 --- /dev/null +++ b/src/Compilers/CSharp/Test/Emit2/CodeGen/CodeGenStringConcat.cs @@ -0,0 +1,3867 @@ +// 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 StringConcatTests : CSharpTestBase + { + [Fact] + public void ConcatConsts() + { + var source = @" +using System; + +public class Test +{ + static void Main() + { + Console.WriteLine(""A"" + ""B""); + + Console.WriteLine(""A"" + (string)null); + Console.WriteLine(""A"" + (object)null); + Console.WriteLine(""A"" + (object)null + ""A"" + (object)null); + + Console.WriteLine((string)null + ""B""); + Console.WriteLine((object)null + ""B""); + + Console.WriteLine((string)null + (object)null); + Console.WriteLine(""#""); + Console.WriteLine((object)null + (string)null); + Console.WriteLine(""#""); + } +} +"; + var comp = CompileAndVerify(source, expectedOutput: @"AB +A +A +AA +B +B + +# + +#"); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.Main", @" +{ + // Code size 101 (0x65) + .maxstack 1 + IL_0000: ldstr ""AB"" + IL_0005: call ""void System.Console.WriteLine(string)"" + IL_000a: ldstr ""A"" + IL_000f: call ""void System.Console.WriteLine(string)"" + IL_0014: ldstr ""A"" + IL_0019: call ""void System.Console.WriteLine(string)"" + IL_001e: ldstr ""AA"" + IL_0023: call ""void System.Console.WriteLine(string)"" + IL_0028: ldstr ""B"" + IL_002d: call ""void System.Console.WriteLine(string)"" + IL_0032: ldstr ""B"" + IL_0037: call ""void System.Console.WriteLine(string)"" + IL_003c: ldstr """" + IL_0041: call ""void System.Console.WriteLine(string)"" + IL_0046: ldstr ""#"" + IL_004b: call ""void System.Console.WriteLine(string)"" + IL_0050: ldstr """" + IL_0055: call ""void System.Console.WriteLine(string)"" + IL_005a: ldstr ""#"" + IL_005f: call ""void System.Console.WriteLine(string)"" + IL_0064: ret +} +"); + } + + [Fact, WorkItem(38858, "https://github.com/dotnet/roslyn/issues/38858")] + public void ConcatEnumWithToString() + { + var source = @" +public class C +{ + public static void Main() + { + System.Console.Write(M(Enum.A)); + } + public static string M(Enum e) + { + return e + """"; + } +} +public enum Enum { A = 0, ToString = 1 } +"; + var comp = CompileAndVerify(source, expectedOutput: "A"); + comp.VerifyDiagnostics(); + } + + [Fact, WorkItem(38858, "https://github.com/dotnet/roslyn/issues/38858")] + public void ConcatStructWithToString() + { + var source = @" +public struct Bad +{ + public new int ToString; + + string Crash() + { + return """" + this; + } +} +"; + var comp = CompileAndVerify(source); + comp.VerifyDiagnostics(); + } + + [Fact] + public void ConcatDefaults() + { + var source = @" +using System; + +public class Test +{ + static void Main() + { + Console.WriteLine(""A"" + ""B""); + + Console.WriteLine(""A"" + default(string)); + Console.WriteLine(""A"" + default(object)); + Console.WriteLine(""A"" + default(object) + ""A"" + default(object)); + + Console.WriteLine(default(string) + ""B""); + Console.WriteLine(default(object) + ""B""); + + Console.WriteLine(default(string) + default(object)); + Console.WriteLine(""#""); + Console.WriteLine(default(object) + default(string)); + Console.WriteLine(""#""); + } +} +"; + var comp = CompileAndVerify(source, expectedOutput: @"AB +A +A +AA +B +B + +# + +#"); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.Main", @" +{ + // Code size 101 (0x65) + .maxstack 1 + IL_0000: ldstr ""AB"" + IL_0005: call ""void System.Console.WriteLine(string)"" + IL_000a: ldstr ""A"" + IL_000f: call ""void System.Console.WriteLine(string)"" + IL_0014: ldstr ""A"" + IL_0019: call ""void System.Console.WriteLine(string)"" + IL_001e: ldstr ""AA"" + IL_0023: call ""void System.Console.WriteLine(string)"" + IL_0028: ldstr ""B"" + IL_002d: call ""void System.Console.WriteLine(string)"" + IL_0032: ldstr ""B"" + IL_0037: call ""void System.Console.WriteLine(string)"" + IL_003c: ldstr """" + IL_0041: call ""void System.Console.WriteLine(string)"" + IL_0046: ldstr ""#"" + IL_004b: call ""void System.Console.WriteLine(string)"" + IL_0050: ldstr """" + IL_0055: call ""void System.Console.WriteLine(string)"" + IL_005a: ldstr ""#"" + IL_005f: call ""void System.Console.WriteLine(string)"" + IL_0064: ret +} +"); + } + + [Fact] + public void ConcatFour() + { + var source = @" +using System; + +public class Test +{ + static void Main() + { + var s = ""qq""; + var ss = s + s + s + s; + Console.WriteLine(ss); + } +} +"; + var comp = CompileAndVerify(source, expectedOutput: @"qqqqqqqq" +); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.Main", @" +{ + // Code size 19 (0x13) + .maxstack 4 + IL_0000: ldstr ""qq"" + IL_0005: dup + IL_0006: dup + IL_0007: dup + IL_0008: call ""string string.Concat(string, string, string, string)"" + IL_000d: call ""void System.Console.WriteLine(string)"" + IL_0012: ret +} +"); + } + + [Fact] + public void ConcatMerge() + { + var source = @" +using System; + +public class Test +{ + private static string S = ""F""; + private static object O = ""O""; + + static void Main() + { + Console.WriteLine( (S + ""A"") + (""B"" + S)); + Console.WriteLine( (O + ""A"") + (""B"" + O)); + Console.WriteLine( ((S + ""A"") + (""B"" + S)) + ((O + ""A"") + (""B"" + O))); + Console.WriteLine( ((O + ""A"") + (S + ""A"")) + ((""B"" + O) + (S + ""A""))); + } +} +"; + var comp = CompileAndVerify(source, expectedOutput: @"FABF +OABO +FABFOABO +OAFABOFA"); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.Main", @" +{ + // Code size 259 (0x103) + .maxstack 5 + IL_0000: ldsfld ""string Test.S"" + IL_0005: ldstr ""AB"" + IL_000a: ldsfld ""string Test.S"" + IL_000f: call ""string string.Concat(string, string, string)"" + IL_0014: call ""void System.Console.WriteLine(string)"" + IL_0019: ldsfld ""object Test.O"" + IL_001e: dup + IL_001f: brtrue.s IL_0025 + IL_0021: pop + IL_0022: ldnull + IL_0023: br.s IL_002a + IL_0025: callvirt ""string object.ToString()"" + IL_002a: ldstr ""AB"" + IL_002f: ldsfld ""object Test.O"" + IL_0034: dup + IL_0035: brtrue.s IL_003b + IL_0037: pop + IL_0038: ldnull + IL_0039: br.s IL_0040 + IL_003b: callvirt ""string object.ToString()"" + IL_0040: call ""string string.Concat(string, string, string)"" + IL_0045: call ""void System.Console.WriteLine(string)"" + IL_004a: ldc.i4.6 + IL_004b: newarr ""string"" + IL_0050: dup + IL_0051: ldc.i4.0 + IL_0052: ldsfld ""string Test.S"" + IL_0057: stelem.ref + IL_0058: dup + IL_0059: ldc.i4.1 + IL_005a: ldstr ""AB"" + IL_005f: stelem.ref + IL_0060: dup + IL_0061: ldc.i4.2 + IL_0062: ldsfld ""string Test.S"" + IL_0067: stelem.ref + IL_0068: dup + IL_0069: ldc.i4.3 + IL_006a: ldsfld ""object Test.O"" + IL_006f: dup + IL_0070: brtrue.s IL_0076 + IL_0072: pop + IL_0073: ldnull + IL_0074: br.s IL_007b + IL_0076: callvirt ""string object.ToString()"" + IL_007b: stelem.ref + IL_007c: dup + IL_007d: ldc.i4.4 + IL_007e: ldstr ""AB"" + IL_0083: stelem.ref + IL_0084: dup + IL_0085: ldc.i4.5 + IL_0086: ldsfld ""object Test.O"" + IL_008b: dup + IL_008c: brtrue.s IL_0092 + IL_008e: pop + IL_008f: ldnull + IL_0090: br.s IL_0097 + IL_0092: callvirt ""string object.ToString()"" + IL_0097: stelem.ref + IL_0098: call ""string string.Concat(params string[])"" + IL_009d: call ""void System.Console.WriteLine(string)"" + IL_00a2: ldc.i4.7 + IL_00a3: newarr ""string"" + IL_00a8: dup + IL_00a9: ldc.i4.0 + IL_00aa: ldsfld ""object Test.O"" + IL_00af: dup + IL_00b0: brtrue.s IL_00b6 + IL_00b2: pop + IL_00b3: ldnull + IL_00b4: br.s IL_00bb + IL_00b6: callvirt ""string object.ToString()"" + IL_00bb: stelem.ref + IL_00bc: dup + IL_00bd: ldc.i4.1 + IL_00be: ldstr ""A"" + IL_00c3: stelem.ref + IL_00c4: dup + IL_00c5: ldc.i4.2 + IL_00c6: ldsfld ""string Test.S"" + IL_00cb: stelem.ref + IL_00cc: dup + IL_00cd: ldc.i4.3 + IL_00ce: ldstr ""AB"" + IL_00d3: stelem.ref + IL_00d4: dup + IL_00d5: ldc.i4.4 + IL_00d6: ldsfld ""object Test.O"" + IL_00db: dup + IL_00dc: brtrue.s IL_00e2 + IL_00de: pop + IL_00df: ldnull + IL_00e0: br.s IL_00e7 + IL_00e2: callvirt ""string object.ToString()"" + IL_00e7: stelem.ref + IL_00e8: dup + IL_00e9: ldc.i4.5 + IL_00ea: ldsfld ""string Test.S"" + IL_00ef: stelem.ref + IL_00f0: dup + IL_00f1: ldc.i4.6 + IL_00f2: ldstr ""A"" + IL_00f7: stelem.ref + IL_00f8: call ""string string.Concat(params string[])"" + IL_00fd: call ""void System.Console.WriteLine(string)"" + IL_0102: ret +} +"); + } + + [ConditionalFact(typeof(WindowsDesktopOnly), Reason = ConditionalSkipReason.TestExecutionNeedsDesktopTypes)] + [WorkItem(37830, "https://github.com/dotnet/roslyn/issues/37830")] + public void ConcatMerge_MarshalByRefObject() + { + var source = @" +using System; +using System.Runtime.Remoting; +using System.Runtime.Remoting.Messaging; +using System.Runtime.Remoting.Proxies; + +class MyProxy : RealProxy +{ + readonly MarshalByRefObject target; + + public MyProxy(MarshalByRefObject target) : base(target.GetType()) + { + this.target = target; + } + + public override IMessage Invoke(IMessage request) + { + IMethodCallMessage call = (IMethodCallMessage)request; + IMethodReturnMessage res = RemotingServices.ExecuteMessage(target, call); + return res; + } +} + +class R1 : MarshalByRefObject +{ + public int test_field = 5; +} + +class Test +{ + static void Main() + { + R1 myobj = new R1(); + MyProxy real_proxy = new MyProxy(myobj); + R1 o = (R1)real_proxy.GetTransparentProxy(); + o.test_field = 2; + Console.WriteLine(""test_field: "" + o.test_field); + } +} +"; + var comp = CompileAndVerify(source, expectedOutput: @"test_field: 2"); + comp.VerifyDiagnostics(); + // Note: we use ldfld on the field, but not ldflda, because the type is MarshalByRefObject + comp.VerifyIL("Test.Main", @" +{ + // Code size 64 (0x40) + .maxstack 2 + .locals init (R1 V_0, //o + int V_1) + IL_0000: newobj ""R1..ctor()"" + IL_0005: newobj ""MyProxy..ctor(System.MarshalByRefObject)"" + IL_000a: callvirt ""object System.Runtime.Remoting.Proxies.RealProxy.GetTransparentProxy()"" + IL_000f: castclass ""R1"" + IL_0014: stloc.0 + IL_0015: ldloc.0 + IL_0016: ldc.i4.2 + IL_0017: stfld ""int R1.test_field"" + IL_001c: ldstr ""test_field: "" + IL_0021: ldloc.0 + IL_0022: ldfld ""int R1.test_field"" + IL_0027: stloc.1 + IL_0028: ldloca.s V_1 + IL_002a: constrained. ""int"" + IL_0030: callvirt ""string object.ToString()"" + IL_0035: call ""string string.Concat(string, string)"" + IL_003a: call ""void System.Console.WriteLine(string)"" + IL_003f: ret +} +"); + } + + [Fact] + public void ConcatMergeFromOne() + { + var source = @" +using System; + +public class Test +{ + private static string S = ""F""; + + static void Main() + { + Console.WriteLine( (S + null) + (S + ""A"") + (""B"" + S) + (S + null)); + } +} +"; + var comp = CompileAndVerify(source, expectedOutput: @"FFABFF"); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.Main", @" +{ + // Code size 57 (0x39) + .maxstack 4 + IL_0000: ldc.i4.5 + IL_0001: newarr ""string"" + IL_0006: dup + IL_0007: ldc.i4.0 + IL_0008: ldsfld ""string Test.S"" + IL_000d: stelem.ref + IL_000e: dup + IL_000f: ldc.i4.1 + IL_0010: ldsfld ""string Test.S"" + IL_0015: stelem.ref + IL_0016: dup + IL_0017: ldc.i4.2 + IL_0018: ldstr ""AB"" + IL_001d: stelem.ref + IL_001e: dup + IL_001f: ldc.i4.3 + IL_0020: ldsfld ""string Test.S"" + IL_0025: stelem.ref + IL_0026: dup + IL_0027: ldc.i4.4 + IL_0028: ldsfld ""string Test.S"" + IL_002d: stelem.ref + IL_002e: call ""string string.Concat(params string[])"" + IL_0033: call ""void System.Console.WriteLine(string)"" + IL_0038: ret +} +"); + } + + [Fact] + public void ConcatOneArg() + { + var source = @" +using System; + +public class Test +{ + private static string S = ""F""; + private static object O = ""O""; + + static void Main() + { + Console.WriteLine(O + null); + Console.WriteLine(S + null); + Console.WriteLine(O?.ToString() + null); + } +} +"; + var comp = CompileAndVerify(source, expectedOutput: @"O +F +O"); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.Main", @" +{ + // Code size 82 (0x52) + .maxstack 2 + IL_0000: ldsfld ""object Test.O"" + IL_0005: dup + IL_0006: brtrue.s IL_000c + IL_0008: pop + IL_0009: ldnull + IL_000a: br.s IL_0011 + IL_000c: callvirt ""string object.ToString()"" + IL_0011: dup + IL_0012: brtrue.s IL_001a + IL_0014: pop + IL_0015: ldstr """" + IL_001a: call ""void System.Console.WriteLine(string)"" + IL_001f: ldsfld ""string Test.S"" + IL_0024: dup + IL_0025: brtrue.s IL_002d + IL_0027: pop + IL_0028: ldstr """" + IL_002d: call ""void System.Console.WriteLine(string)"" + IL_0032: ldsfld ""object Test.O"" + IL_0037: dup + IL_0038: brtrue.s IL_003e + IL_003a: pop + IL_003b: ldnull + IL_003c: br.s IL_0043 + IL_003e: callvirt ""string object.ToString()"" + IL_0043: dup + IL_0044: brtrue.s IL_004c + IL_0046: pop + IL_0047: ldstr """" + IL_004c: call ""void System.Console.WriteLine(string)"" + IL_0051: ret +} +"); + } + + [Fact] + public void ConcatOneArgWithNullToString() + { + var source = @" +using System; + +public class Test +{ + private static object C = new C(); + + static void Main() + { + Console.WriteLine((C + null) == """" ? ""Y"" : ""N""); + Console.WriteLine((C + null + null) == """" ? ""Y"" : ""N""); + } +} + +public class C +{ + public override string ToString() => null; +} +"; + var comp = CompileAndVerify(source, expectedOutput: @"Y +Y"); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.Main", @" +{ + // Code size 111 (0x6f) + .maxstack 2 + IL_0000: ldsfld ""object Test.C"" + IL_0005: dup + IL_0006: brtrue.s IL_000c + IL_0008: pop + IL_0009: ldnull + IL_000a: br.s IL_0011 + IL_000c: callvirt ""string object.ToString()"" + IL_0011: dup + IL_0012: brtrue.s IL_001a + IL_0014: pop + IL_0015: ldstr """" + IL_001a: ldstr """" + IL_001f: call ""bool string.op_Equality(string, string)"" + IL_0024: brtrue.s IL_002d + IL_0026: ldstr ""N"" + IL_002b: br.s IL_0032 + IL_002d: ldstr ""Y"" + IL_0032: call ""void System.Console.WriteLine(string)"" + IL_0037: ldsfld ""object Test.C"" + IL_003c: dup + IL_003d: brtrue.s IL_0043 + IL_003f: pop + IL_0040: ldnull + IL_0041: br.s IL_0048 + IL_0043: callvirt ""string object.ToString()"" + IL_0048: dup + IL_0049: brtrue.s IL_0051 + IL_004b: pop + IL_004c: ldstr """" + IL_0051: ldstr """" + IL_0056: call ""bool string.op_Equality(string, string)"" + IL_005b: brtrue.s IL_0064 + IL_005d: ldstr ""N"" + IL_0062: br.s IL_0069 + IL_0064: ldstr ""Y"" + IL_0069: call ""void System.Console.WriteLine(string)"" + IL_006e: ret +} +"); + } + + [Fact] + public void ConcatOneArgWithExplicitConcatCall() + { + var source = @" +using System; + +public class Test +{ + private static object O = ""O""; + + static void Main() + { + Console.WriteLine(string.Concat(O) + null); + Console.WriteLine(string.Concat(O) + null + null); + } +} +"; + var comp = CompileAndVerify(source, expectedOutput: @"O +O"); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.Main", @" +{ + // Code size 49 (0x31) + .maxstack 2 + IL_0000: ldsfld ""object Test.O"" + IL_0005: call ""string string.Concat(object)"" + IL_000a: dup + IL_000b: brtrue.s IL_0013 + IL_000d: pop + IL_000e: ldstr """" + IL_0013: call ""void System.Console.WriteLine(string)"" + IL_0018: ldsfld ""object Test.O"" + IL_001d: call ""string string.Concat(object)"" + IL_0022: dup + IL_0023: brtrue.s IL_002b + IL_0025: pop + IL_0026: ldstr """" + IL_002b: call ""void System.Console.WriteLine(string)"" + IL_0030: ret +} +"); + } + + [Fact] + public void ConcatEmptyString() + { + var source = @" +using System; + +public class Test +{ + private static string S = ""F""; + private static object O = ""O""; + + static void Main() + { + Console.WriteLine(O + """"); + Console.WriteLine(S + """"); + } +} +"; + var comp = CompileAndVerify(source, expectedOutput: @"O +F"); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.Main", @" +{ + // Code size 51 (0x33) + .maxstack 2 + IL_0000: ldsfld ""object Test.O"" + IL_0005: dup + IL_0006: brtrue.s IL_000c + IL_0008: pop + IL_0009: ldnull + IL_000a: br.s IL_0011 + IL_000c: callvirt ""string object.ToString()"" + IL_0011: dup + IL_0012: brtrue.s IL_001a + IL_0014: pop + IL_0015: ldstr """" + IL_001a: call ""void System.Console.WriteLine(string)"" + IL_001f: ldsfld ""string Test.S"" + IL_0024: dup + IL_0025: brtrue.s IL_002d + IL_0027: pop + IL_0028: ldstr """" + IL_002d: call ""void System.Console.WriteLine(string)"" + IL_0032: ret +} +"); + } + + [Fact] + [WorkItem(679120, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/679120")] + public void ConcatEmptyArray() + { + var source = @" +using System; + +public class Test +{ + static void Main() + { + Console.WriteLine(""Start""); + Console.WriteLine(string.Concat(new string[] {})); + Console.WriteLine(string.Concat(new string[] {}) + string.Concat(new string[] {})); + Console.WriteLine(""A"" + string.Concat(new string[] {})); + Console.WriteLine(string.Concat(new string[] {}) + ""B""); + Console.WriteLine(""End""); + } +} +"; + var comp = CompileAndVerify(source, expectedOutput: @"Start + + +A +B +End"); + + comp.VerifyDiagnostics(); + // NOTE: Dev11 doesn't optimize away string.Concat(new string[0]) either. + // We could add an optimization, but it's unlikely to occur in real code. + comp.VerifyIL("Test.Main", @" +{ + // Code size 67 (0x43) + .maxstack 1 + IL_0000: ldstr ""Start"" + IL_0005: call ""void System.Console.WriteLine(string)"" + IL_000a: ldc.i4.0 + IL_000b: newarr ""string"" + IL_0010: call ""string string.Concat(params string[])"" + IL_0015: call ""void System.Console.WriteLine(string)"" + IL_001a: ldstr """" + IL_001f: call ""void System.Console.WriteLine(string)"" + IL_0024: ldstr ""A"" + IL_0029: call ""void System.Console.WriteLine(string)"" + IL_002e: ldstr ""B"" + IL_0033: call ""void System.Console.WriteLine(string)"" + IL_0038: ldstr ""End"" + IL_003d: call ""void System.Console.WriteLine(string)"" + IL_0042: ret +} +"); + } + + [WorkItem(529064, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/529064")] + [Fact] + public void TestStringConcatOnLiteralAndCompound() + { + var source = @" +public class Test +{ + static string field01 = ""A""; + static string field02 = ""B""; + static void Main() + { + field01 += field02 + ""C"" + ""D""; + } +} +"; + var comp = CompileAndVerify(source); + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.Main", @" +{ + // Code size 26 (0x1a) + .maxstack 3 + IL_0000: ldsfld ""string Test.field01"" + IL_0005: ldsfld ""string Test.field02"" + IL_000a: ldstr ""CD"" + IL_000f: call ""string string.Concat(string, string, string)"" + IL_0014: stsfld ""string Test.field01"" + IL_0019: ret +} +"); + } + + [Fact] + public void ConcatGeneric() + { + var source = @" +using System; + +public class Test +{ + static void Main() + { + TestMethod(); + } + + private static void TestMethod() + { + Console.WriteLine(""A"" + default(T)); + Console.WriteLine(""A"" + default(T) + ""A"" + default(T)); + + Console.WriteLine(default(T) + ""B""); + + Console.WriteLine(default(string) + default(T)); + Console.WriteLine(""#""); + Console.WriteLine(default(T) + default(string)); + Console.WriteLine(""#""); + } +} + +"; + var comp = CompileAndVerify(source, expectedOutput: @"A0 +A0A0 +0B +0 +# +0 +#"); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.TestMethod()", @" +{ + // Code size 291 (0x123) + .maxstack 4 + .locals init (T V_0) + IL_0000: ldstr ""A"" + IL_0005: ldloca.s V_0 + IL_0007: initobj ""T"" + IL_000d: ldloc.0 + IL_000e: box ""T"" + IL_0013: brtrue.s IL_0018 + IL_0015: ldnull + IL_0016: br.s IL_0025 + IL_0018: ldloca.s V_0 + IL_001a: constrained. ""T"" + IL_0020: callvirt ""string object.ToString()"" + IL_0025: call ""string string.Concat(string, string)"" + IL_002a: call ""void System.Console.WriteLine(string)"" + IL_002f: ldstr ""A"" + IL_0034: ldloca.s V_0 + IL_0036: initobj ""T"" + IL_003c: ldloc.0 + IL_003d: box ""T"" + IL_0042: brtrue.s IL_0047 + IL_0044: ldnull + IL_0045: br.s IL_0054 + IL_0047: ldloca.s V_0 + IL_0049: constrained. ""T"" + IL_004f: callvirt ""string object.ToString()"" + IL_0054: ldstr ""A"" + IL_0059: ldloca.s V_0 + IL_005b: initobj ""T"" + IL_0061: ldloc.0 + IL_0062: box ""T"" + IL_0067: brtrue.s IL_006c + IL_0069: ldnull + IL_006a: br.s IL_0079 + IL_006c: ldloca.s V_0 + IL_006e: constrained. ""T"" + IL_0074: callvirt ""string object.ToString()"" + IL_0079: call ""string string.Concat(string, string, string, string)"" + IL_007e: call ""void System.Console.WriteLine(string)"" + IL_0083: ldloca.s V_0 + IL_0085: initobj ""T"" + IL_008b: ldloc.0 + IL_008c: box ""T"" + IL_0091: brtrue.s IL_0096 + IL_0093: ldnull + IL_0094: br.s IL_00a3 + IL_0096: ldloca.s V_0 + IL_0098: constrained. ""T"" + IL_009e: callvirt ""string object.ToString()"" + IL_00a3: ldstr ""B"" + IL_00a8: call ""string string.Concat(string, string)"" + IL_00ad: call ""void System.Console.WriteLine(string)"" + IL_00b2: ldloca.s V_0 + IL_00b4: initobj ""T"" + IL_00ba: ldloc.0 + IL_00bb: box ""T"" + IL_00c0: brtrue.s IL_00c5 + IL_00c2: ldnull + IL_00c3: br.s IL_00d2 + IL_00c5: ldloca.s V_0 + IL_00c7: constrained. ""T"" + IL_00cd: callvirt ""string object.ToString()"" + IL_00d2: dup + IL_00d3: brtrue.s IL_00db + IL_00d5: pop + IL_00d6: ldstr """" + IL_00db: call ""void System.Console.WriteLine(string)"" + IL_00e0: ldstr ""#"" + IL_00e5: call ""void System.Console.WriteLine(string)"" + IL_00ea: ldloca.s V_0 + IL_00ec: initobj ""T"" + IL_00f2: ldloc.0 + IL_00f3: box ""T"" + IL_00f8: brtrue.s IL_00fd + IL_00fa: ldnull + IL_00fb: br.s IL_010a + IL_00fd: ldloca.s V_0 + IL_00ff: constrained. ""T"" + IL_0105: callvirt ""string object.ToString()"" + IL_010a: dup + IL_010b: brtrue.s IL_0113 + IL_010d: pop + IL_010e: ldstr """" + IL_0113: call ""void System.Console.WriteLine(string)"" + IL_0118: ldstr ""#"" + IL_011d: call ""void System.Console.WriteLine(string)"" + IL_0122: ret +} +"); + } + + [Fact] + public void ConcatGenericConstrained() + { + var source = @" +using System; + +public class Test +{ + static void Main() + { + TestMethod(); + } + + private static void TestMethod() where T:class where U: class + { + Console.WriteLine(""A"" + default(T)); + Console.WriteLine(""A"" + default(T) + ""A"" + default(T)); + + Console.WriteLine(default(T) + ""B""); + + Console.WriteLine(default(string) + default(T)); + Console.WriteLine(""#""); + Console.WriteLine(default(T) + default(string)); + Console.WriteLine(""#""); + + Console.WriteLine(""A"" + (U)null); + Console.WriteLine(""A"" + (U)null + ""A"" + (U)null); + + Console.WriteLine((U)null + ""B""); + + Console.WriteLine(default(string) + (U)null); + Console.WriteLine(""#""); + Console.WriteLine((U)null + default(string)); + Console.WriteLine(""#""); + } +} + +"; + var comp = CompileAndVerify(source, expectedOutput: @"A +AA +B + +# + +# +A +AA +B + +# + +#"); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.TestMethod()", @" +{ + // Code size 141 (0x8d) + .maxstack 1 + IL_0000: ldstr ""A"" + IL_0005: call ""void System.Console.WriteLine(string)"" + IL_000a: ldstr ""AA"" + IL_000f: call ""void System.Console.WriteLine(string)"" + IL_0014: ldstr ""B"" + IL_0019: call ""void System.Console.WriteLine(string)"" + IL_001e: ldstr """" + IL_0023: call ""void System.Console.WriteLine(string)"" + IL_0028: ldstr ""#"" + IL_002d: call ""void System.Console.WriteLine(string)"" + IL_0032: ldstr """" + IL_0037: call ""void System.Console.WriteLine(string)"" + IL_003c: ldstr ""#"" + IL_0041: call ""void System.Console.WriteLine(string)"" + IL_0046: ldstr ""A"" + IL_004b: call ""void System.Console.WriteLine(string)"" + IL_0050: ldstr ""AA"" + IL_0055: call ""void System.Console.WriteLine(string)"" + IL_005a: ldstr ""B"" + IL_005f: call ""void System.Console.WriteLine(string)"" + IL_0064: ldstr """" + IL_0069: call ""void System.Console.WriteLine(string)"" + IL_006e: ldstr ""#"" + IL_0073: call ""void System.Console.WriteLine(string)"" + IL_0078: ldstr """" + IL_007d: call ""void System.Console.WriteLine(string)"" + IL_0082: ldstr ""#"" + IL_0087: call ""void System.Console.WriteLine(string)"" + IL_008c: ret +} +"); + } + + [Fact] + public void ConcatGenericUnconstrained() + { + var source = @" +using System; +class Test +{ + static void Main() + { + var p1 = new Printer(""F""); + p1.Print(""P""); + p1.Print(null); + var p2 = new Printer(null); + p2.Print(""P""); + p2.Print(null); + var p3 = new Printer(new MutableStruct()); + MutableStruct m = new MutableStruct(); + p3.Print(m); + p3.Print(m); + } +} + +class Printer +{ + private T field; + public Printer(T field) => this.field = field; + public void Print(T p) + { + Console.WriteLine("""" + p + p + field + field); + } +} + +struct MutableStruct +{ + private int i; + public override string ToString() => (++i).ToString(); +} +"; + var comp = CompileAndVerify(source, expectedOutput: @"PPFF +FF +PP + +1111 +1111"); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Printer.Print", @" +{ + // Code size 125 (0x7d) + .maxstack 4 + .locals init (T V_0) + IL_0000: ldarg.1 + IL_0001: stloc.0 + IL_0002: ldloc.0 + IL_0003: box ""T"" + IL_0008: brtrue.s IL_000d + IL_000a: ldnull + IL_000b: br.s IL_001a + IL_000d: ldloca.s V_0 + IL_000f: constrained. ""T"" + IL_0015: callvirt ""string object.ToString()"" + IL_001a: ldarg.1 + IL_001b: stloc.0 + IL_001c: ldloc.0 + IL_001d: box ""T"" + IL_0022: brtrue.s IL_0027 + IL_0024: ldnull + IL_0025: br.s IL_0034 + IL_0027: ldloca.s V_0 + IL_0029: constrained. ""T"" + IL_002f: callvirt ""string object.ToString()"" + IL_0034: ldarg.0 + IL_0035: ldfld ""T Printer.field"" + IL_003a: stloc.0 + IL_003b: ldloc.0 + IL_003c: box ""T"" + IL_0041: brtrue.s IL_0046 + IL_0043: ldnull + IL_0044: br.s IL_0053 + IL_0046: ldloca.s V_0 + IL_0048: constrained. ""T"" + IL_004e: callvirt ""string object.ToString()"" + IL_0053: ldarg.0 + IL_0054: ldfld ""T Printer.field"" + IL_0059: stloc.0 + IL_005a: ldloc.0 + IL_005b: box ""T"" + IL_0060: brtrue.s IL_0065 + IL_0062: ldnull + IL_0063: br.s IL_0072 + IL_0065: ldloca.s V_0 + IL_0067: constrained. ""T"" + IL_006d: callvirt ""string object.ToString()"" + IL_0072: call ""string string.Concat(string, string, string, string)"" + IL_0077: call ""void System.Console.WriteLine(string)"" + IL_007c: ret +} +"); + } + + [Fact] + public void ConcatGenericConstrainedClass() + { + var source = @" +using System; +class Test +{ + static void Main() + { + var p1 = new Printer(""F""); + p1.Print(""P""); + p1.Print(null); + var p2 = new Printer(null); + p2.Print(""P""); + p2.Print(null); + } +} + +class Printer where T : class +{ + private T field; + public Printer(T field) => this.field = field; + public void Print(T p) + { + Console.WriteLine("""" + p + p + field + field); + } +}"; + + var comp = CompileAndVerify(source, expectedOutput: @"PPFF +FF +PP +"); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Printer.Print", @" +{ + // Code size 93 (0x5d) + .maxstack 5 + IL_0000: ldarg.1 + IL_0001: box ""T"" + IL_0006: dup + IL_0007: brtrue.s IL_000d + IL_0009: pop + IL_000a: ldnull + IL_000b: br.s IL_0012 + IL_000d: callvirt ""string object.ToString()"" + IL_0012: ldarg.1 + IL_0013: box ""T"" + IL_0018: dup + IL_0019: brtrue.s IL_001f + IL_001b: pop + IL_001c: ldnull + IL_001d: br.s IL_0024 + IL_001f: callvirt ""string object.ToString()"" + IL_0024: ldarg.0 + IL_0025: ldfld ""T Printer.field"" + IL_002a: box ""T"" + IL_002f: dup + IL_0030: brtrue.s IL_0036 + IL_0032: pop + IL_0033: ldnull + IL_0034: br.s IL_003b + IL_0036: callvirt ""string object.ToString()"" + IL_003b: ldarg.0 + IL_003c: ldfld ""T Printer.field"" + IL_0041: box ""T"" + IL_0046: dup + IL_0047: brtrue.s IL_004d + IL_0049: pop + IL_004a: ldnull + IL_004b: br.s IL_0052 + IL_004d: callvirt ""string object.ToString()"" + IL_0052: call ""string string.Concat(string, string, string, string)"" + IL_0057: call ""void System.Console.WriteLine(string)"" + IL_005c: ret +} +"); + + } + + [Fact] + public void ConcatGenericConstrainedStruct() + { + var source = @" +using System; +class Test +{ + static void Main() + { + MutableStruct m = new MutableStruct(); + var p1 = new Printer(new MutableStruct()); + p1.Print(m); + p1.Print(m); + } +} + +class Printer where T : struct +{ + private T field; + public Printer(T field) => this.field = field; + public void Print(T p) + { + Console.WriteLine("""" + p + p + field + field); + } +} + +struct MutableStruct +{ + private int i; + public override string ToString() => (++i).ToString(); +}"; + + var comp = CompileAndVerify(source, expectedOutput: @"1111 +1111"); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Printer.Print", @" +{ + // Code size 81 (0x51) + .maxstack 4 + .locals init (T V_0) + IL_0000: ldarg.1 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: constrained. ""T"" + IL_000a: callvirt ""string object.ToString()"" + IL_000f: ldarg.1 + IL_0010: stloc.0 + IL_0011: ldloca.s V_0 + IL_0013: constrained. ""T"" + IL_0019: callvirt ""string object.ToString()"" + IL_001e: ldarg.0 + IL_001f: ldfld ""T Printer.field"" + IL_0024: stloc.0 + IL_0025: ldloca.s V_0 + IL_0027: constrained. ""T"" + IL_002d: callvirt ""string object.ToString()"" + IL_0032: ldarg.0 + IL_0033: ldfld ""T Printer.field"" + IL_0038: stloc.0 + IL_0039: ldloca.s V_0 + IL_003b: constrained. ""T"" + IL_0041: callvirt ""string object.ToString()"" + IL_0046: call ""string string.Concat(string, string, string, string)"" + IL_004b: call ""void System.Console.WriteLine(string)"" + IL_0050: ret +} +"); + + } + + [Fact] + public void ConcatWithOtherOptimizations() + { + var source = @" +using System; + +public class Test +{ + static void Main() + { + var expr1 = ""hi""; + var expr2 = ""bye""; + + // expr1 is optimized away + // only expr2 should be lifted!! + Func f = () => (""abc"" + ""def"" + null ?? expr1 + ""moo"" + ""baz"") + expr2; + + System.Console.WriteLine(f()); + } +} +"; + var comp = CompileAndVerify(source, expectedOutput: @"abcdefbye"); + + comp.VerifyDiagnostics(); + + // IMPORTANT!! only c__DisplayClass0.expr2 should be initialized, + // there should not be such thing as c__DisplayClass0.expr1 + comp.VerifyIL("Test.Main", @" +{ + // Code size 38 (0x26) + .maxstack 3 + IL_0000: newobj ""Test.<>c__DisplayClass0_0..ctor()"" + IL_0005: dup + IL_0006: ldstr ""bye"" + IL_000b: stfld ""string Test.<>c__DisplayClass0_0.expr2"" + IL_0010: ldftn ""string Test.<>c__DisplayClass0_0.
b__0()"" + IL_0016: newobj ""System.Func..ctor(object, System.IntPtr)"" + IL_001b: callvirt ""string System.Func.Invoke()"" + IL_0020: call ""void System.Console.WriteLine(string)"" + IL_0025: ret +} +"); + } + + [Fact, WorkItem(1092853, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1092853")] + public void ConcatWithNullCoalescedNullLiteral() + { + const string source = @" +class Repro +{ + static string Bug(string s) + { + string x = """"; + x += s ?? null; + return x; + } + + static void Main() + { + System.Console.Write(""\""{0}\"""", Bug(null)); + } +}"; + + var comp = CompileAndVerify(source, expectedOutput: "\"\""); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Repro.Bug", @" +{ + // Code size 17 (0x11) + .maxstack 3 + IL_0000: ldstr """" + IL_0005: ldarg.0 + IL_0006: dup + IL_0007: brtrue.s IL_000b + IL_0009: pop + IL_000a: ldnull + IL_000b: call ""string string.Concat(string, string)"" + IL_0010: ret +} +"); + } + + [Fact, WorkItem(1092853, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1092853")] + public void ConcatWithNullCoalescedNullLiteral_2() + { + const string source = @" +class Repro +{ + static string Bug(string s) + { + string x = """"; + x += s ?? ((string)null ?? null); + return x; + } + + static void Main() + { + System.Console.Write(""\""{0}\"""", Bug(null)); + } +}"; + + var comp = CompileAndVerify(source, expectedOutput: "\"\""); + + comp.VerifyIL("Repro.Bug", @" +{ + // Code size 17 (0x11) + .maxstack 3 + IL_0000: ldstr """" + IL_0005: ldarg.0 + IL_0006: dup + IL_0007: brtrue.s IL_000b + IL_0009: pop + IL_000a: ldnull + IL_000b: call ""string string.Concat(string, string)"" + IL_0010: ret +} +"); + } + + [Fact] + public void ConcatMutableStruct() + { + var source = @" +using System; +class Test +{ + static MutableStruct f = new MutableStruct(); + + static void Main() + { + MutableStruct l = new MutableStruct(); + + Console.WriteLine("""" + l + l + f + f); + } +} + +struct MutableStruct +{ + private int i; + public override string ToString() => (++i).ToString(); +} +"; + + var comp = CompileAndVerify(source, expectedOutput: @"1111"); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.Main", @" +{ + // Code size 87 (0x57) + .maxstack 4 + .locals init (MutableStruct V_0, //l + MutableStruct V_1) + IL_0000: ldloca.s V_0 + IL_0002: initobj ""MutableStruct"" + IL_0008: ldloc.0 + IL_0009: stloc.1 + IL_000a: ldloca.s V_1 + IL_000c: constrained. ""MutableStruct"" + IL_0012: callvirt ""string object.ToString()"" + IL_0017: ldloc.0 + IL_0018: stloc.1 + IL_0019: ldloca.s V_1 + IL_001b: constrained. ""MutableStruct"" + IL_0021: callvirt ""string object.ToString()"" + IL_0026: ldsfld ""MutableStruct Test.f"" + IL_002b: stloc.1 + IL_002c: ldloca.s V_1 + IL_002e: constrained. ""MutableStruct"" + IL_0034: callvirt ""string object.ToString()"" + IL_0039: ldsfld ""MutableStruct Test.f"" + IL_003e: stloc.1 + IL_003f: ldloca.s V_1 + IL_0041: constrained. ""MutableStruct"" + IL_0047: callvirt ""string object.ToString()"" + IL_004c: call ""string string.Concat(string, string, string, string)"" + IL_0051: call ""void System.Console.WriteLine(string)"" + IL_0056: ret +}"); + } + + [Fact] + public void ConcatMutableStructsSideEffects() + { + const string source = @" +using System; +using static System.Console; + +struct Mutable +{ + int x; + public override string ToString() => (x++).ToString(); +} + +class Test +{ + static Mutable m = new Mutable(); + + static void Main() + { + Write(""("" + m + "")""); // (0) + Write(""("" + m + "")""); // (0) + + Write(""("" + m.ToString() + "")""); // (0) + Write(""("" + m.ToString() + "")""); // (1) + Write(""("" + m.ToString() + "")""); // (2) + + Nullable n = new Mutable(); + Write(""("" + n + "")""); // (0) + Write(""("" + n + "")""); // (0) + + Write(""("" + n.ToString() + "")""); // (0) + Write(""("" + n.ToString() + "")""); // (1) + Write(""("" + n.ToString() + "")""); // (2) + } +}"; + + CompileAndVerify(source, expectedOutput: "(0)(0)(0)(1)(2)(0)(0)(0)(1)(2)"); + } + + [Fact] + public void ConcatReadonlyStruct() + { + var source = @" +using System; +class Test +{ + static ReadonlyStruct f = new ReadonlyStruct(); + + static void Main() + { + ReadonlyStruct l = new ReadonlyStruct(); + + Console.WriteLine("""" + l + l + f + f); + } +} + +readonly struct ReadonlyStruct +{ + public override string ToString() => ""R""; +} +"; + + var comp = CompileAndVerify(source, expectedOutput: @"RRRR"); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.Main", @" +{ + // Code size 77 (0x4d) + .maxstack 4 + .locals init (ReadonlyStruct V_0) //l + IL_0000: ldloca.s V_0 + IL_0002: initobj ""ReadonlyStruct"" + IL_0008: ldloca.s V_0 + IL_000a: constrained. ""ReadonlyStruct"" + IL_0010: callvirt ""string object.ToString()"" + IL_0015: ldloca.s V_0 + IL_0017: constrained. ""ReadonlyStruct"" + IL_001d: callvirt ""string object.ToString()"" + IL_0022: ldsflda ""ReadonlyStruct Test.f"" + IL_0027: constrained. ""ReadonlyStruct"" + IL_002d: callvirt ""string object.ToString()"" + IL_0032: ldsflda ""ReadonlyStruct Test.f"" + IL_0037: constrained. ""ReadonlyStruct"" + IL_003d: callvirt ""string object.ToString()"" + IL_0042: call ""string string.Concat(string, string, string, string)"" + IL_0047: call ""void System.Console.WriteLine(string)"" + IL_004c: ret +} +"); + } + + [Fact] + public void ConcatStructWithReadonlyToString() + { + var source = @" +using System; +class Test +{ + static StructWithReadonlyToString f = new StructWithReadonlyToString(); + + static void Main() + { + StructWithReadonlyToString l = new StructWithReadonlyToString(); + + Console.WriteLine("""" + l + l + f + f); + } +} + +struct StructWithReadonlyToString +{ + public readonly override string ToString() => ""R""; +} +"; + + var comp = CompileAndVerify(source, expectedOutput: @"RRRR"); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.Main", @" +{ + // Code size 77 (0x4d) + .maxstack 4 + .locals init (StructWithReadonlyToString V_0) //l + IL_0000: ldloca.s V_0 + IL_0002: initobj ""StructWithReadonlyToString"" + IL_0008: ldloca.s V_0 + IL_000a: constrained. ""StructWithReadonlyToString"" + IL_0010: callvirt ""string object.ToString()"" + IL_0015: ldloca.s V_0 + IL_0017: constrained. ""StructWithReadonlyToString"" + IL_001d: callvirt ""string object.ToString()"" + IL_0022: ldsflda ""StructWithReadonlyToString Test.f"" + IL_0027: constrained. ""StructWithReadonlyToString"" + IL_002d: callvirt ""string object.ToString()"" + IL_0032: ldsflda ""StructWithReadonlyToString Test.f"" + IL_0037: constrained. ""StructWithReadonlyToString"" + IL_003d: callvirt ""string object.ToString()"" + IL_0042: call ""string string.Concat(string, string, string, string)"" + IL_0047: call ""void System.Console.WriteLine(string)"" + IL_004c: ret +} +"); + } + + [Fact] + public void ConcatStructWithNoToString() + { + var source = @" +using System; +class Test +{ + static S f = new S(); + + static void Main() + { + S l = new S(); + + Console.WriteLine("""" + l + l + f + f); + } +} + +struct S { } +"; + + var comp = CompileAndVerify(source, expectedOutput: @"SSSS"); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.Main", @" +{ + // Code size 77 (0x4d) + .maxstack 4 + .locals init (S V_0) //l + IL_0000: ldloca.s V_0 + IL_0002: initobj ""S"" + IL_0008: ldloca.s V_0 + IL_000a: constrained. ""S"" + IL_0010: callvirt ""string object.ToString()"" + IL_0015: ldloca.s V_0 + IL_0017: constrained. ""S"" + IL_001d: callvirt ""string object.ToString()"" + IL_0022: ldsflda ""S Test.f"" + IL_0027: constrained. ""S"" + IL_002d: callvirt ""string object.ToString()"" + IL_0032: ldsflda ""S Test.f"" + IL_0037: constrained. ""S"" + IL_003d: callvirt ""string object.ToString()"" + IL_0042: call ""string string.Concat(string, string, string, string)"" + IL_0047: call ""void System.Console.WriteLine(string)"" + IL_004c: ret +} +"); + } + + [Fact] + public void ConcatWithImplicitOperator() + { + var source = @" +using System; + +public class Test +{ + static void Main() + { + Console.WriteLine(""S"" + new Test()); + } + + public static implicit operator string(Test test) => ""T""; +} +"; + var comp = CompileAndVerify(source, expectedOutput: @"ST"); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.Main", @" +{ + // Code size 26 (0x1a) + .maxstack 2 + IL_0000: ldstr ""S"" + IL_0005: newobj ""Test..ctor()"" + IL_000a: call ""string Test.op_Implicit(Test)"" + IL_000f: call ""string string.Concat(string, string)"" + IL_0014: call ""void System.Console.WriteLine(string)"" + IL_0019: ret +} +"); + } + + [Fact] + public void ConcatWithNull() + { + var source = @" +using System; + +public class Test +{ + public static Test T = null; + + static void Main() + { + Console.WriteLine(""S"" + T); + } +} +"; + var comp = CompileAndVerify(source, expectedOutput: @"S"); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.Main", @" +{ + // Code size 33 (0x21) + .maxstack 3 + IL_0000: ldstr ""S"" + IL_0005: ldsfld ""Test Test.T"" + IL_000a: dup + IL_000b: brtrue.s IL_0011 + IL_000d: pop + IL_000e: ldnull + IL_000f: br.s IL_0016 + IL_0011: callvirt ""string object.ToString()"" + IL_0016: call ""string string.Concat(string, string)"" + IL_001b: call ""void System.Console.WriteLine(string)"" + IL_0020: ret +} +"); + } + + [Fact] + public void ConcatWithSpecialValueTypes() + { + var source = @" +using System; + +public class Test +{ + static void Main() + { + const char a = 'a', b = 'b'; + char c = 'c', d = 'd'; + + Console.WriteLine(a + ""1""); + Console.WriteLine(""2"" + b); + + Console.WriteLine(c + ""3""); + Console.WriteLine(""4"" + d); + + Console.WriteLine(true + ""5"" + c); + Console.WriteLine(""6"" + d + (IntPtr)7); + Console.WriteLine(""8"" + (UIntPtr)9 + false); + + Console.WriteLine(c + ""10"" + d + ""11""); + Console.WriteLine(""12"" + c + ""13"" + d); + + Console.WriteLine(a + ""14"" + b + ""15"" + a + ""16""); + Console.WriteLine(c + ""17"" + d + ""18"" + c + ""19""); + + Console.WriteLine(""20"" + 21 + c + d + c + d); + Console.WriteLine(""22"" + c + ""23"" + d + c + d); + } +} +"; + var comp = CompileAndVerify(source, expectedOutput: @"a1 +2b +c3 +4d +True5c +6d7 +89False +c10d11 +12c13d +a14b15a16 +c17d18c19 +2021cdcd +22c23dcd"); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.Main", @" +{ + // Code size 477 (0x1dd) + .maxstack 4 + .locals init (char V_0, //c + char V_1, //d + bool V_2, + System.IntPtr V_3, + System.UIntPtr V_4, + int V_5) + IL_0000: ldc.i4.s 99 + IL_0002: stloc.0 + IL_0003: ldc.i4.s 100 + IL_0005: stloc.1 + IL_0006: ldstr ""a1"" + IL_000b: call ""void System.Console.WriteLine(string)"" + IL_0010: ldstr ""2b"" + IL_0015: call ""void System.Console.WriteLine(string)"" + IL_001a: ldloca.s V_0 + IL_001c: call ""string char.ToString()"" + IL_0021: ldstr ""3"" + IL_0026: call ""string string.Concat(string, string)"" + IL_002b: call ""void System.Console.WriteLine(string)"" + IL_0030: ldstr ""4"" + IL_0035: ldloca.s V_1 + IL_0037: call ""string char.ToString()"" + IL_003c: call ""string string.Concat(string, string)"" + IL_0041: call ""void System.Console.WriteLine(string)"" + IL_0046: ldc.i4.1 + IL_0047: stloc.2 + IL_0048: ldloca.s V_2 + IL_004a: call ""string bool.ToString()"" + IL_004f: ldstr ""5"" + IL_0054: ldloca.s V_0 + IL_0056: call ""string char.ToString()"" + IL_005b: call ""string string.Concat(string, string, string)"" + IL_0060: call ""void System.Console.WriteLine(string)"" + IL_0065: ldstr ""6"" + IL_006a: ldloca.s V_1 + IL_006c: call ""string char.ToString()"" + IL_0071: ldc.i4.7 + IL_0072: call ""System.IntPtr System.IntPtr.op_Explicit(int)"" + IL_0077: stloc.3 + IL_0078: ldloca.s V_3 + IL_007a: call ""string System.IntPtr.ToString()"" + IL_007f: call ""string string.Concat(string, string, string)"" + IL_0084: call ""void System.Console.WriteLine(string)"" + IL_0089: ldstr ""8"" + IL_008e: ldc.i4.s 9 + IL_0090: conv.i8 + IL_0091: call ""System.UIntPtr System.UIntPtr.op_Explicit(ulong)"" + IL_0096: stloc.s V_4 + IL_0098: ldloca.s V_4 + IL_009a: call ""string System.UIntPtr.ToString()"" + IL_009f: ldc.i4.0 + IL_00a0: stloc.2 + IL_00a1: ldloca.s V_2 + IL_00a3: call ""string bool.ToString()"" + IL_00a8: call ""string string.Concat(string, string, string)"" + IL_00ad: call ""void System.Console.WriteLine(string)"" + IL_00b2: ldloca.s V_0 + IL_00b4: call ""string char.ToString()"" + IL_00b9: ldstr ""10"" + IL_00be: ldloca.s V_1 + IL_00c0: call ""string char.ToString()"" + IL_00c5: ldstr ""11"" + IL_00ca: call ""string string.Concat(string, string, string, string)"" + IL_00cf: call ""void System.Console.WriteLine(string)"" + IL_00d4: ldstr ""12"" + IL_00d9: ldloca.s V_0 + IL_00db: call ""string char.ToString()"" + IL_00e0: ldstr ""13"" + IL_00e5: ldloca.s V_1 + IL_00e7: call ""string char.ToString()"" + IL_00ec: call ""string string.Concat(string, string, string, string)"" + IL_00f1: call ""void System.Console.WriteLine(string)"" + IL_00f6: ldstr ""a14b15a16"" + IL_00fb: call ""void System.Console.WriteLine(string)"" + IL_0100: ldc.i4.6 + IL_0101: newarr ""string"" + IL_0106: dup + IL_0107: ldc.i4.0 + IL_0108: ldloca.s V_0 + IL_010a: call ""string char.ToString()"" + IL_010f: stelem.ref + IL_0110: dup + IL_0111: ldc.i4.1 + IL_0112: ldstr ""17"" + IL_0117: stelem.ref + IL_0118: dup + IL_0119: ldc.i4.2 + IL_011a: ldloca.s V_1 + IL_011c: call ""string char.ToString()"" + IL_0121: stelem.ref + IL_0122: dup + IL_0123: ldc.i4.3 + IL_0124: ldstr ""18"" + IL_0129: stelem.ref + IL_012a: dup + IL_012b: ldc.i4.4 + IL_012c: ldloca.s V_0 + IL_012e: call ""string char.ToString()"" + IL_0133: stelem.ref + IL_0134: dup + IL_0135: ldc.i4.5 + IL_0136: ldstr ""19"" + IL_013b: stelem.ref + IL_013c: call ""string string.Concat(params string[])"" + IL_0141: call ""void System.Console.WriteLine(string)"" + IL_0146: ldc.i4.6 + IL_0147: newarr ""string"" + IL_014c: dup + IL_014d: ldc.i4.0 + IL_014e: ldstr ""20"" + IL_0153: stelem.ref + IL_0154: dup + IL_0155: ldc.i4.1 + IL_0156: ldc.i4.s 21 + IL_0158: stloc.s V_5 + IL_015a: ldloca.s V_5 + IL_015c: call ""string int.ToString()"" + IL_0161: stelem.ref + IL_0162: dup + IL_0163: ldc.i4.2 + IL_0164: ldloca.s V_0 + IL_0166: call ""string char.ToString()"" + IL_016b: stelem.ref + IL_016c: dup + IL_016d: ldc.i4.3 + IL_016e: ldloca.s V_1 + IL_0170: call ""string char.ToString()"" + IL_0175: stelem.ref + IL_0176: dup + IL_0177: ldc.i4.4 + IL_0178: ldloca.s V_0 + IL_017a: call ""string char.ToString()"" + IL_017f: stelem.ref + IL_0180: dup + IL_0181: ldc.i4.5 + IL_0182: ldloca.s V_1 + IL_0184: call ""string char.ToString()"" + IL_0189: stelem.ref + IL_018a: call ""string string.Concat(params string[])"" + IL_018f: call ""void System.Console.WriteLine(string)"" + IL_0194: ldc.i4.6 + IL_0195: newarr ""string"" + IL_019a: dup + IL_019b: ldc.i4.0 + IL_019c: ldstr ""22"" + IL_01a1: stelem.ref + IL_01a2: dup + IL_01a3: ldc.i4.1 + IL_01a4: ldloca.s V_0 + IL_01a6: call ""string char.ToString()"" + IL_01ab: stelem.ref + IL_01ac: dup + IL_01ad: ldc.i4.2 + IL_01ae: ldstr ""23"" + IL_01b3: stelem.ref + IL_01b4: dup + IL_01b5: ldc.i4.3 + IL_01b6: ldloca.s V_1 + IL_01b8: call ""string char.ToString()"" + IL_01bd: stelem.ref + IL_01be: dup + IL_01bf: ldc.i4.4 + IL_01c0: ldloca.s V_0 + IL_01c2: call ""string char.ToString()"" + IL_01c7: stelem.ref + IL_01c8: dup + IL_01c9: ldc.i4.5 + IL_01ca: ldloca.s V_1 + IL_01cc: call ""string char.ToString()"" + IL_01d1: stelem.ref + IL_01d2: call ""string string.Concat(params string[])"" + IL_01d7: call ""void System.Console.WriteLine(string)"" + IL_01dc: ret +} +"); + } + + [Fact] + public void ConcatExpressions() + { + var source = @" +using System; + +class Test +{ + static int X = 3; + static int Y = 4; + + static void Main() + { + Console.WriteLine(X + ""+"" + Y + ""="" + (X + Y)); + } +} +"; + + var comp = CompileAndVerify(source, expectedOutput: "3+4=7"); + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.Main", @" +{ + // Code size 81 (0x51) + .maxstack 5 + .locals init (int V_0) + IL_0000: ldc.i4.5 + IL_0001: newarr ""string"" + IL_0006: dup + IL_0007: ldc.i4.0 + IL_0008: ldsflda ""int Test.X"" + IL_000d: call ""string int.ToString()"" + IL_0012: stelem.ref + IL_0013: dup + IL_0014: ldc.i4.1 + IL_0015: ldstr ""+"" + IL_001a: stelem.ref + IL_001b: dup + IL_001c: ldc.i4.2 + IL_001d: ldsflda ""int Test.Y"" + IL_0022: call ""string int.ToString()"" + IL_0027: stelem.ref + IL_0028: dup + IL_0029: ldc.i4.3 + IL_002a: ldstr ""="" + IL_002f: stelem.ref + IL_0030: dup + IL_0031: ldc.i4.4 + IL_0032: ldsfld ""int Test.X"" + IL_0037: ldsfld ""int Test.Y"" + IL_003c: add + IL_003d: stloc.0 + IL_003e: ldloca.s V_0 + IL_0040: call ""string int.ToString()"" + IL_0045: stelem.ref + IL_0046: call ""string string.Concat(params string[])"" + IL_004b: call ""void System.Console.WriteLine(string)"" + IL_0050: ret +}"); + } + + [Fact] + public void ConcatRefs() + { + var source = @" +using System; + +class Test +{ + static void Main() + { + string s1 = ""S1""; + string s2 = ""S2""; + int i1 = 3; + int i2 = 4; + object o1 = ""O1""; + object o2 = ""O2""; + Print(ref s1, ref i1, ref o1, ref s2, ref i2, ref o2); + } + + static void Print(ref string s, ref int i, ref object o, ref T1 t1, ref T2 t2, ref T3 t3) + where T1 : class + where T2 : struct + { + Console.WriteLine(s + i + o + t1 + t2 + t3); + } +} +"; + + var comp = CompileAndVerify(source, expectedOutput: "S13O1S24O2"); + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.Print", @" +{ + // Code size 133 (0x85) + .maxstack 5 + .locals init (T2 V_0, + T3 V_1) + IL_0000: ldc.i4.6 + IL_0001: newarr ""string"" + IL_0006: dup + IL_0007: ldc.i4.0 + IL_0008: ldarg.0 + IL_0009: ldind.ref + IL_000a: stelem.ref + IL_000b: dup + IL_000c: ldc.i4.1 + IL_000d: ldarg.1 + IL_000e: call ""string int.ToString()"" + IL_0013: stelem.ref + IL_0014: dup + IL_0015: ldc.i4.2 + IL_0016: ldarg.2 + IL_0017: ldind.ref + IL_0018: dup + IL_0019: brtrue.s IL_001f + IL_001b: pop + IL_001c: ldnull + IL_001d: br.s IL_0024 + IL_001f: callvirt ""string object.ToString()"" + IL_0024: stelem.ref + IL_0025: dup + IL_0026: ldc.i4.3 + IL_0027: ldarg.3 + IL_0028: ldobj ""T1"" + IL_002d: box ""T1"" + IL_0032: dup + IL_0033: brtrue.s IL_0039 + IL_0035: pop + IL_0036: ldnull + IL_0037: br.s IL_003e + IL_0039: callvirt ""string object.ToString()"" + IL_003e: stelem.ref + IL_003f: dup + IL_0040: ldc.i4.4 + IL_0041: ldarg.s V_4 + IL_0043: ldobj ""T2"" + IL_0048: stloc.0 + IL_0049: ldloca.s V_0 + IL_004b: constrained. ""T2"" + IL_0051: callvirt ""string object.ToString()"" + IL_0056: stelem.ref + IL_0057: dup + IL_0058: ldc.i4.5 + IL_0059: ldarg.s V_5 + IL_005b: ldobj ""T3"" + IL_0060: stloc.1 + IL_0061: ldloc.1 + IL_0062: box ""T3"" + IL_0067: brtrue.s IL_006c + IL_0069: ldnull + IL_006a: br.s IL_0079 + IL_006c: ldloca.s V_1 + IL_006e: constrained. ""T3"" + IL_0074: callvirt ""string object.ToString()"" + IL_0079: stelem.ref + IL_007a: call ""string string.Concat(params string[])"" + IL_007f: call ""void System.Console.WriteLine(string)"" + IL_0084: ret +}"); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatTwo_ReadOnlySpan1() + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s = "s"; + var c = 'c'; + Console.Write(M1(s, c)); + Console.Write(M2(s, c)); + } + + static string M1(string s, char c) => s + c; + static string M2(string s, char c) => c + s; + } + """; + + var comp = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "sccs" : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.M1", """ + { + // Code size 21 (0x15) + .maxstack 2 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.1 + IL_0007: stloc.0 + IL_0008: ldloca.s V_0 + IL_000a: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000f: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0014: ret + } + """); + comp.VerifyIL("Test.M2", """ + { + // Code size 21 (0x15) + .maxstack 2 + .locals init (char V_0) + IL_0000: ldarg.1 + IL_0001: stloc.0 + IL_0002: ldloca.s V_0 + IL_0004: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0009: ldarg.0 + IL_000a: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000f: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0014: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatTwo_ReadOnlySpan2() + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s = "s"; + var c = 'C'; + Console.Write(M1(s, c)); + Console.Write(M2(s, c)); + } + + static string M1(string s, char c) => s + char.ToLowerInvariant(c); + static string M2(string s, char c) => char.ToLowerInvariant(c) + s; + } + """; + + var comp = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "sccs" : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.M1", """ + { + // Code size 26 (0x1a) + .maxstack 2 + .locals init (char V_0) + IL_0000: ldarg.0 + IL_0001: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0006: ldarg.1 + IL_0007: call "char char.ToLowerInvariant(char)" + IL_000c: stloc.0 + IL_000d: ldloca.s V_0 + IL_000f: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0014: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0019: ret + } + """); + comp.VerifyIL("Test.M2", """ + { + // Code size 26 (0x1a) + .maxstack 2 + .locals init (char V_0) + IL_0000: ldarg.1 + IL_0001: call "char char.ToLowerInvariant(char)" + IL_0006: stloc.0 + IL_0007: ldloca.s V_0 + IL_0009: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000e: ldarg.0 + IL_000f: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0014: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_0019: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatTwo_ReadOnlySpan_SideEffect() + { + var source = """ + using System; + + public class Test + { + private static int stringCounter; + private static int charCounterPlusOne = 1; + + static void Main() + { + Console.WriteLine(M1()); + Console.WriteLine(M2()); + } + + static string M1() => GetStringWithSideEffect() + GetCharWithSideEffect(); + static string M2() => GetCharWithSideEffect() + GetStringWithSideEffect(); + + private static string GetStringWithSideEffect() + { + Console.Write(stringCounter++); + return "s"; + } + + private static char GetCharWithSideEffect() + { + Console.Write(charCounterPlusOne++); + return 'c'; + } + } + """; + + var expectedOutput = """ + 01sc + 21cs + """; + + var comp = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? expectedOutput : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + comp.VerifyDiagnostics(); + comp.VerifyIL("Test.M1", """ + { + // Code size 29 (0x1d) + .maxstack 2 + .locals init (char V_0) + IL_0000: call "string Test.GetStringWithSideEffect()" + IL_0005: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000a: call "char Test.GetCharWithSideEffect()" + IL_000f: stloc.0 + IL_0010: ldloca.s V_0 + IL_0012: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0017: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001c: ret + } + """); + comp.VerifyIL("Test.M2", """ + { + // Code size 29 (0x1d) + .maxstack 2 + .locals init (char V_0) + IL_0000: call "char Test.GetCharWithSideEffect()" + IL_0005: stloc.0 + IL_0006: ldloca.s V_0 + IL_0008: newobj "System.ReadOnlySpan..ctor(in char)" + IL_000d: call "string Test.GetStringWithSideEffect()" + IL_0012: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_0017: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan)" + IL_001c: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + public void ConcatTwo_ReadOnlySpan_MissingMemberForOptimization(int member) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s = "s"; + var c = 'c'; + Console.Write(M1(s, c)); + Console.Write(M2(s, c)); + } + + static string M1(string s, char c) => s + c; + static string M2(string s, char c) => c + s; + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + comp.MakeMemberMissing((WellKnownMember)member); + + var verifier = CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "sccs" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + verifier.VerifyDiagnostics(); + verifier.VerifyIL("Test.M1", """ + { + // Code size 14 (0xe) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldarga.s V_1 + IL_0003: call "string char.ToString()" + IL_0008: call "string string.Concat(string, string)" + IL_000d: ret + } + """); + verifier.VerifyIL("Test.M2", """ + { + // Code size 14 (0xe) + .maxstack 2 + IL_0000: ldarga.s V_1 + IL_0002: call "string char.ToString()" + IL_0007: ldarg.0 + IL_0008: call "string string.Concat(string, string)" + IL_000d: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatThree_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)); + 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 = 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 + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatThree_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)); + 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 = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "cssscsssccsc" : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + comp.VerifyDiagnostics(); + comp.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 + } + """); + comp.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 + } + """); + comp.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 + } + """); + comp.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 + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatThree_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()); + 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 = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? expectedOutput : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + comp.VerifyDiagnostics(); + comp.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 + } + """); + comp.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 + } + """); + comp.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 + } + """); + comp.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 + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatThree_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("a" + c + GetC()); + } + } + """; + + var comp = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "aab" : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + comp.VerifyDiagnostics(); + comp.VerifyIL("C.M", """ + { + // Code size 50 (0x32) + .maxstack 3 + .locals init (char V_0, + char V_1) + IL_0000: ldstr "a" + IL_0005: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000a: ldarg.0 + IL_000b: ldfld "char C.c" + IL_0010: stloc.0 + IL_0011: ldloca.s V_0 + IL_0013: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0018: ldarg.0 + IL_0019: call "ref char C.GetC()" + IL_001e: ldind.u2 + IL_001f: stloc.1 + IL_0020: ldloca.s V_1 + IL_0022: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0027: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_002c: call "void System.Console.Write(string)" + IL_0031: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatThree_ReadOnlySpan_MutateLocal() + { + 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 = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "aab" : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + comp.VerifyDiagnostics(); + comp.VerifyIL("C.M", """ + { + // Code size 44 (0x2c) + .maxstack 4 + .locals init (char V_0, //c + char V_1, + char V_2) + IL_0000: ldc.i4.s 97 + IL_0002: stloc.0 + IL_0003: ldstr "a" + IL_0008: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000d: ldloc.0 + IL_000e: stloc.1 + IL_000f: ldloca.s V_1 + IL_0011: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0016: ldarg.0 + IL_0017: ldloca.s V_0 + IL_0019: call "char C.SneakyLocalChange(ref char)" + IL_001e: stloc.2 + IL_001f: ldloca.s V_2 + IL_0021: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0026: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_002b: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + [InlineData((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + public void ConcatThree_ReadOnlySpan_MissingMemberForOptimization(int member) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s = "s"; + var c = 'c'; + Console.Write(M1(s, c)); + Console.Write(M2(s, c)); + Console.Write(M3(s, c)); + Console.Write(M4(s, c)); + } + + static string M1(string s, char c) => c + s + s; + static string M2(string s, char c) => s + c + s; + static string M3(string s, char c) => s + s + c; + static string M4(string s, char c) => c + s + c; + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net70); + comp.MakeMemberMissing((WellKnownMember)member); + + // We are missing a key member to perform span-based optimization. This is a real-life scenario e.g. when compiling against .NET Framework, + // so verify both expected output and generated IL to ensure we don't regress codegen and still pick up correct overload of string.Concat + 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((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + public void ConcatThree_ReadOnlySpan_MissingSpanConcat(int spanConcatMember) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s = "s"; + var c = 'c'; + Console.Write(M1(s, c)); + Console.Write(M2(s, c)); + Console.Write(M3(s, c)); + Console.Write(M4(s, c)); + } + + static string M1(string s, char c) => c + s + s; + static string M2(string s, char c) => s + c + s; + static string M3(string s, char c) => s + s + c; + static string M4(string s, char c) => c + s + c; + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + comp.MakeMemberMissing((WellKnownMember)spanConcatMember); + + // Just verify that we can still run this and get expected output. + // This is not something that can be seen in real-life scenarios, so don't care about precise IL we generate + CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "cssscsssccsc" : null, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped).VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatFour_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)); + 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 = 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 + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatFour_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)); + 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 = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "csssscsssscssssccscsscsccssc" : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + comp.VerifyDiagnostics(); + comp.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 + } + """); + comp.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 + } + """); + comp.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 + } + """); + comp.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 + } + """); + comp.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 + } + """); + comp.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 + } + """); + comp.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 + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatFour_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()); + 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 = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? expectedOutput : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + comp.VerifyDiagnostics(); + comp.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 + } + """); + comp.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 + } + """); + comp.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 + } + """); + comp.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 + } + """); + comp.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 + } + """); + comp.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 + } + """); + comp.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 + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatFour_ReadOnlySpan_ReferenceToSameLocation() + { + 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 = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "aaab" : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + comp.VerifyDiagnostics(); + comp.VerifyIL("C.M", """ + { + // Code size 65 (0x41) + .maxstack 4 + .locals init (char V_0, + char V_1, + char V_2) + IL_0000: ldstr "a" + IL_0005: call "System.ReadOnlySpan string.op_Implicit(string)" + IL_000a: ldarg.0 + IL_000b: ldfld "char C.c" + IL_0010: stloc.0 + IL_0011: ldloca.s V_0 + IL_0013: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0018: ldarg.0 + IL_0019: call "ref char C.GetC()" + IL_001e: ldind.u2 + IL_001f: stloc.1 + IL_0020: ldloca.s V_1 + IL_0022: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0027: ldarg.0 + IL_0028: call "ref char C.GetC2()" + IL_002d: ldind.u2 + IL_002e: stloc.2 + IL_002f: ldloca.s V_2 + IL_0031: newobj "System.ReadOnlySpan..ctor(in char)" + IL_0036: call "string string.Concat(System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan, System.ReadOnlySpan)" + IL_003b: call "void System.Console.Write(string)" + IL_0040: ret + } + """); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatFour_ReadOnlySpan_MutateLocal() + { + 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 = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "abab" : null, targetFramework: TargetFramework.Net80, verify: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? default : Verification.Skipped); + + comp.VerifyDiagnostics(); + comp.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((int)WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar)] + [InlineData((int)WellKnownMember.System_ReadOnlySpan_T__ctor_Reference)] + public void ConcatFour_ReadOnlySpan_MissingMemberForOptimization(int member) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s = "s"; + var c = 'c'; + Console.Write(M1(s, c)); + Console.Write(M2(s, c)); + Console.Write(M3(s, c)); + Console.Write(M4(s, c)); + Console.Write(M5(s, c)); + Console.Write(M6(s, c)); + Console.Write(M7(s, c)); + } + + static string M1(string s, char c) => c + s + s + s; + static string M2(string s, char c) => s + c + s + s; + static string M3(string s, char c) => s + s + c + s; + static string M4(string s, char c) => s + s + s + c; + static string M5(string s, char c) => c + s + c + s; + static string M6(string s, char c) => s + c + s + c; + static string M7(string s, char c) => c + s + s + c; + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + comp.MakeMemberMissing((WellKnownMember)member); + + // We are missing a key member to perform span-based optimization. This is a real-life scenario e.g. when compiling against .NET Framework, + // so verify both expected output and generated IL to ensure we don't regress codegen and still pick up correct overload of string.Concat + 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((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan)] + [InlineData((int)WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan)] + public void ConcatFour_ReadOnlySpan_MissingSpanConcat(int spanConcatMember) + { + var source = """ + using System; + + public class Test + { + static void Main() + { + var s = "s"; + var c = 'c'; + Console.Write(M1(s, c)); + Console.Write(M2(s, c)); + Console.Write(M3(s, c)); + Console.Write(M4(s, c)); + Console.Write(M5(s, c)); + Console.Write(M6(s, c)); + Console.Write(M7(s, c)); + } + + static string M1(string s, char c) => c + s + s + s; + static string M2(string s, char c) => s + c + s + s; + static string M3(string s, char c) => s + s + c + s; + static string M4(string s, char c) => s + s + s + c; + static string M5(string s, char c) => c + s + c + s; + static string M6(string s, char c) => s + c + s + c; + static string M7(string s, char c) => c + s + s + c; + } + """; + + var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80); + comp.MakeMemberMissing((WellKnownMember)spanConcatMember); + + // Just verify that we can still run this and get expected output. + // This is not something that can be seen in real-life scenarios, so don't care about precise IL we generate + CompileAndVerify(compilation: comp, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "csssscsssscssssccscsscsccssc" : null, verify: ExecutionConditionUtil.IsCoreClr ? default : Verification.Skipped).VerifyDiagnostics(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/66827")] + public void ConcatFive_Char() + { + 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 = CompileAndVerify(source, expectedOutput: RuntimeUtilities.IsCoreClr8OrHigherRuntime ? "scsssssssc" : null, targetFramework: TargetFramework.Net80, verify: ExecutionConditionUtil.IsCoreClr ? default : Verification.Skipped); + + comp.VerifyDiagnostics(); + + // This is actually more optimal then using string.Concat(string[]) overload + comp.VerifyIL("Test.CharInFirstFourArgs", """ + { + // Code size 39 (0x27) + .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: ldarg.0 + IL_0021: call "string string.Concat(string, string)" + IL_0026: ret + } + """); + + // Ideally this should use span overload and then concat the remaining string as well, + // but due to the order of evaluation of expressions during lowering we end up falling back to concat array overload. + // This is not a typical real-life scenario (although still possible one), so analysis complications in the compiler are not worth the result + comp.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 + } + """); + } + } +} diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs index e477803077b41..56001ba2a9581 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs @@ -958,9 +958,14 @@ public void AllWellKnownTypeMembers() case WellKnownMember.System_ReadOnlySpan_T__ctor_Pointer: case WellKnownMember.System_ReadOnlySpan_T__ctor_Array: case WellKnownMember.System_ReadOnlySpan_T__ctor_Array_Start_Length: + case WellKnownMember.System_ReadOnlySpan_T__ctor_Reference: case WellKnownMember.System_ReadOnlySpan_T__get_Item: case WellKnownMember.System_ReadOnlySpan_T__get_Length: case WellKnownMember.System_ReadOnlySpan_T__Slice_Int_Int: + case WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar: + case WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan: + case WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan: + case WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan: case WellKnownMember.System_Index__ctor: case WellKnownMember.System_Index__GetOffset: case WellKnownMember.System_Range__ctor: @@ -2403,7 +2408,7 @@ static void Main() compilation.VerifyEmitDiagnostics( // (9,27): error CS0656: Missing compiler required member 'System.Object.ToString' // Console.WriteLine(c + "3"); - Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"c + ""3""").WithArguments("System.Object", "ToString").WithLocation(9, 27) + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "c").WithArguments("System.Object", "ToString").WithLocation(9, 27) ); } diff --git a/src/Compilers/Core/Portable/WellKnownMember.cs b/src/Compilers/Core/Portable/WellKnownMember.cs index d3c38f6908576..cbc72af382ad3 100644 --- a/src/Compilers/Core/Portable/WellKnownMember.cs +++ b/src/Compilers/Core/Portable/WellKnownMember.cs @@ -431,6 +431,11 @@ internal enum WellKnownMember System_Runtime_CompilerServices_TupleElementNamesAttribute__ctorTransformNames, System_String__Format_IFormatProvider, + System_String__op_Implicit_ToReadOnlySpanOfChar, + + System_String__Concat_ReadOnlySpanReadOnlySpan, + System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan, + System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan, Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningSingleFile, Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningMultipleFiles, @@ -495,6 +500,7 @@ internal enum WellKnownMember System_ReadOnlySpan_T__ctor_Pointer, System_ReadOnlySpan_T__ctor_Array, System_ReadOnlySpan_T__ctor_Array_Start_Length, + System_ReadOnlySpan_T__ctor_Reference, System_ReadOnlySpan_T__get_Item, System_ReadOnlySpan_T__get_Length, System_ReadOnlySpan_T__Slice_Int_Int, diff --git a/src/Compilers/Core/Portable/WellKnownMembers.cs b/src/Compilers/Core/Portable/WellKnownMembers.cs index 3365e9caf63e1..6b352d1b83884 100644 --- a/src/Compilers/Core/Portable/WellKnownMembers.cs +++ b/src/Compilers/Core/Portable/WellKnownMembers.cs @@ -497,7 +497,7 @@ static WellKnownMembers() (byte)WellKnownType.System_Collections_Generic_EqualityComparer_T, // DeclaringTypeId 0, // Arity 0, // Method Signature - (byte)SignatureTypeCode.TypeHandle, (byte)WellKnownType.System_Collections_Generic_EqualityComparer_T,// Return Type + (byte)SignatureTypeCode.TypeHandle, (byte)WellKnownType.System_Collections_Generic_EqualityComparer_T, // Return Type // System_AttributeUsageAttribute__ctor (byte)MemberFlags.Constructor, // Flags @@ -2979,6 +2979,74 @@ static WellKnownMembers() (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, (byte)SignatureTypeCode.SZArray, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Object, + // System_String__op_Implicit_ToReadOnlySpanOfChar + (byte)(MemberFlags.Method | MemberFlags.Static), // Flags + (byte)SpecialType.System_String, // DeclaringTypeId + 0, // Arity + 1, // Method Signature + (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, // Return Type + (byte)WellKnownType.ExtSentinel, (WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), + 1, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, + + // System_String__Concat_ReadOnlySpanReadOnlySpan + (byte)(MemberFlags.Method | MemberFlags.Static), // Flags + (byte)SpecialType.System_String, // DeclaringTypeId + 0, // Arity + 2, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, // Return Type + (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, + (byte)WellKnownType.ExtSentinel, (WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), + 1, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, + (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, + (byte)WellKnownType.ExtSentinel, (WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), + 1, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, + + // System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan + (byte)(MemberFlags.Method | MemberFlags.Static), // Flags + (byte)SpecialType.System_String, // DeclaringTypeId + 0, // Arity + 3, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, // Return Type + (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, + (byte)WellKnownType.ExtSentinel, (WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), + 1, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, + (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, + (byte)WellKnownType.ExtSentinel, (WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), + 1, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, + (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, + (byte)WellKnownType.ExtSentinel, (WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), + 1, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, + + // System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan + (byte)(MemberFlags.Method | MemberFlags.Static), // Flags + (byte)SpecialType.System_String, // DeclaringTypeId + 0, // Arity + 4, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_String, // Return Type + (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, + (byte)WellKnownType.ExtSentinel, (WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), + 1, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, + (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, + (byte)WellKnownType.ExtSentinel, (WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), + 1, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, + (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, + (byte)WellKnownType.ExtSentinel, (WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), + 1, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, + (byte)SignatureTypeCode.GenericTypeInstance, (byte)SignatureTypeCode.TypeHandle, + (byte)WellKnownType.ExtSentinel, (WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), + 1, + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Char, + // Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningSingleFile (byte)(MemberFlags.Method | MemberFlags.Static), // Flags (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.Microsoft_CodeAnalysis_Runtime_Instrumentation - WellKnownType.ExtSentinel), // DeclaringTypeId @@ -3439,6 +3507,14 @@ static WellKnownMembers() (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Int32, (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Int32, + // System_ReadOnlySpan_T__ctor_Reference + (byte)(MemberFlags.Constructor), // Flags + (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), // DeclaringTypeId + 0, // Arity + 1, // Method Signature + (byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Void, // Return Type + (byte)SignatureTypeCode.ByReference, (byte)SignatureTypeCode.GenericTypeParameter, 0, + // System_ReadOnlySpan_T__get_Item (byte)(MemberFlags.PropertyGet), // Flags (byte)WellKnownType.ExtSentinel, (byte)(WellKnownType.System_ReadOnlySpan_T - WellKnownType.ExtSentinel), // DeclaringTypeId @@ -4812,6 +4888,11 @@ static WellKnownMembers() ".ctor", // System_Runtime_CompilerServices_TupleElementNamesAttribute__ctorTransformNames "Format", // System_String__Format_IFormatProvider + "op_Implicit", // System_String__op_Implicit_ToReadOnlySpanOfChar + + "Concat", // System_String__Concat_ReadOnlySpanReadOnlySpan + "Concat", // System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan + "Concat", // System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan "CreatePayload", // Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningSingleFile "CreatePayload", // Microsoft_CodeAnalysis_Runtime_Instrumentation__CreatePayloadForMethodsSpanningMultipleFiles @@ -4867,6 +4948,7 @@ static WellKnownMembers() ".ctor", // System_ReadOnlySpan_T__ctor_Pointer ".ctor", // System_ReadOnlySpan_T__ctor_Array ".ctor", // System_ReadOnlySpan_T__ctor_Array_Start_Length + ".ctor", // System_ReadOnlySpan_T__ctor_Reference "get_Item", // System_ReadOnlySpan_T__get_Item "get_Length", // System_ReadOnlySpan_T__get_Length "Slice", // System_ReadOnlySpan_T__Slice_Int_Int diff --git a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb index 8700f144d3848..3f56a5c215bbd 100644 --- a/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb +++ b/src/Compilers/VisualBasic/Test/Symbol/SymbolsTests/WellKnownTypeValidationTests.vb @@ -2,7 +2,6 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. -Imports System.Xml.Linq Imports Microsoft.CodeAnalysis.Test.Utilities Imports Roslyn.Test.Utilities @@ -703,9 +702,14 @@ End Namespace WellKnownMember.System_ReadOnlySpan_T__ctor_Pointer, WellKnownMember.System_ReadOnlySpan_T__ctor_Array, WellKnownMember.System_ReadOnlySpan_T__ctor_Array_Start_Length, + WellKnownMember.System_ReadOnlySpan_T__ctor_Reference, WellKnownMember.System_ReadOnlySpan_T__get_Item, WellKnownMember.System_ReadOnlySpan_T__get_Length, WellKnownMember.System_ReadOnlySpan_T__Slice_Int_Int, + WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar, + WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan, + WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan, + WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan, WellKnownMember.System_Runtime_CompilerServices_AsyncIteratorStateMachineAttribute__ctor, WellKnownMember.System_IAsyncDisposable__DisposeAsync, WellKnownMember.System_Collections_Generic_IAsyncEnumerable_T__GetAsyncEnumerator, @@ -908,9 +912,14 @@ End Namespace WellKnownMember.System_ReadOnlySpan_T__ctor_Pointer, WellKnownMember.System_ReadOnlySpan_T__ctor_Array, WellKnownMember.System_ReadOnlySpan_T__ctor_Array_Start_Length, + WellKnownMember.System_ReadOnlySpan_T__ctor_Reference, WellKnownMember.System_ReadOnlySpan_T__get_Item, WellKnownMember.System_ReadOnlySpan_T__get_Length, WellKnownMember.System_ReadOnlySpan_T__Slice_Int_Int, + WellKnownMember.System_String__op_Implicit_ToReadOnlySpanOfChar, + WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpan, + WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpan, + WellKnownMember.System_String__Concat_ReadOnlySpanReadOnlySpanReadOnlySpanReadOnlySpan, WellKnownMember.System_Runtime_CompilerServices_AsyncIteratorStateMachineAttribute__ctor, WellKnownMember.System_IAsyncDisposable__DisposeAsync, WellKnownMember.System_Collections_Generic_IAsyncEnumerable_T__GetAsyncEnumerator,