diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_FixedStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_FixedStatement.cs index 1b759cabfc0ef..5037d23b3c01e 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_FixedStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_FixedStatement.cs @@ -336,7 +336,7 @@ private BoundStatement InitializeFixedStatementGetPinnable( if (needNullCheck) { - currentConditionalAccessID = _currentConditionalAccessID++; + currentConditionalAccessID = ++_currentConditionalAccessID; callReceiver = new BoundConditionalReceiver( initializerSyntax, currentConditionalAccessID, diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs index c27a7ba26f306..d3e6e1e5b9088 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringConcat.cs @@ -4,11 +4,8 @@ using System.Diagnostics; using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; -using Microsoft.CodeAnalysis.RuntimeMembers; namespace Microsoft.CodeAnalysis.CSharp { @@ -46,9 +43,12 @@ private BoundExpression RewriteStringConcatenation(SyntaxNode syntax, BinaryOper return RewriteStringConcatInExpressionLambda(syntax, operatorKind, loweredLeft, loweredRight, type); } - // avoid run time boxing and ToString operations if we can reasonably convert to a string at compile time - loweredLeft = ConvertConcatExprToStringIfPossible(syntax, loweredLeft); - loweredRight = ConvertConcatExprToStringIfPossible(syntax, loweredRight); + // Convert both sides to a string (calling ToString if necessary) + loweredLeft = ConvertConcatExprToString(syntax, loweredLeft); + loweredRight = ConvertConcatExprToString(syntax, loweredRight); + + Debug.Assert(loweredLeft.Type.IsStringType() || loweredLeft.ConstantValue?.IsNull == true || loweredLeft.Type.IsErrorType()); + Debug.Assert(loweredRight.Type.IsStringType() || loweredRight.ConstantValue?.IsNull == true || loweredRight.Type.IsErrorType()); // try fold two args without flattening. var folded = TryFoldTwoConcatOperands(syntax, loweredLeft, loweredRight); @@ -86,6 +86,8 @@ private BoundExpression RewriteStringConcatenation(SyntaxNode syntax, BinaryOper break; case 1: + // All code paths which reach here (through TryFoldTwoConcatOperands) have already called + // RewriteStringConcatenationOneExpr if necessary result = leftFlattened[0]; break; @@ -96,10 +98,22 @@ private BoundExpression RewriteStringConcatenation(SyntaxNode syntax, BinaryOper break; case 3: - var first = leftFlattened[0]; - var second = leftFlattened[1]; - var third = leftFlattened[2]; - result = RewriteStringConcatenationThreeExprs(syntax, first, second, third); + { + var first = leftFlattened[0]; + var second = leftFlattened[1]; + var third = leftFlattened[2]; + result = RewriteStringConcatenationThreeExprs(syntax, first, second, third); + } + break; + + case 4: + { + var first = leftFlattened[0]; + var second = leftFlattened[1]; + var third = leftFlattened[2]; + var fourth = leftFlattened[3]; + result = RewriteStringConcatenationFourExprs(syntax, first, second, third, fourth); + } break; default: @@ -119,7 +133,7 @@ private BoundExpression RewriteStringConcatenation(SyntaxNode syntax, BinaryOper /// private void FlattenConcatArg(BoundExpression lowered, ArrayBuilder flattened) { - if (TryExtractStringConcatArgs(lowered, out var arguments, out _)) + if (TryExtractStringConcatArgs(lowered, out var arguments)) { flattened.AddRange(arguments); } @@ -134,17 +148,39 @@ private void FlattenConcatArg(BoundExpression lowered, ArrayBuilder - /// - /// True if this method returns true, and the expression can return null (string.Concat(object) can return null if - /// the object's ToString method returns null) - /// /// True if this is a call to a known string concat operator, false otherwise - private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableArray arguments, out bool loweredCanReturnNull) + private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableArray arguments) { switch (lowered.Kind) { case BoundKind.Call: - return TryExtractStringConcatArgsFromBoundCall((BoundCall)lowered, out arguments, out loweredCanReturnNull); + var boundCall = (BoundCall)lowered; + var method = boundCall.Method; + if (method.IsStatic && method.ContainingType.SpecialType == SpecialType.System_String) + { + if ((object)method == (object)_compilation.GetSpecialTypeMember(SpecialMember.System_String__ConcatStringString) || + (object)method == (object)_compilation.GetSpecialTypeMember(SpecialMember.System_String__ConcatStringStringString) || + (object)method == (object)_compilation.GetSpecialTypeMember(SpecialMember.System_String__ConcatStringStringStringString)) + { + arguments = boundCall.Arguments; + return true; + } + + if ((object)method == (object)_compilation.GetSpecialTypeMember(SpecialMember.System_String__ConcatStringArray)) + { + var args = boundCall.Arguments[0] as BoundArrayCreation; + if (args != null) + { + var initializer = args.InitializerOpt; + if (initializer != null) + { + arguments = initializer.Initializers; + return true; + } + } + } + } + break; case BoundKind.NullCoalescingOperator: var boundCoalesce = (BoundNullCoalescingOperator)lowered; @@ -159,16 +195,6 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr var rightConstant = boundCoalesce.RightOperand.ConstantValue; if (rightConstant != null && rightConstant.IsString && rightConstant.StringValue.Length == 0) { - // If lowered ends in '?? ""', it can never return null. - loweredCanReturnNull = false; - - // The left operand might be a call to string.Concat(object) - if (boundCoalesce.LeftOperand.Kind == BoundKind.Call && - TryExtractStringConcatArgsFromBoundCall((BoundCall)boundCoalesce.LeftOperand, out arguments, out _)) - { - return true; - } - arguments = ImmutableArray.Create(boundCoalesce.LeftOperand); return true; } @@ -177,60 +203,6 @@ private bool TryExtractStringConcatArgs(BoundExpression lowered, out ImmutableAr } arguments = default; - loweredCanReturnNull = default; - return false; - } - - /// - /// Attempts to extract the args from a call to string.Concat - /// - /// - /// True if this method returns true, and boundCall is a call to a string.Concat overload which can return null - /// (i.e. string.Concat(object)) - /// - /// True if this was a call to string.Concat, false otherwise - private bool TryExtractStringConcatArgsFromBoundCall(BoundCall boundCall, out ImmutableArray arguments, out bool callCanReturnNull) - { - var method = boundCall.Method; - if (method.IsStatic && method.ContainingType.SpecialType == SpecialType.System_String) - { - if ((object)method == (object)_compilation.GetSpecialTypeMember(SpecialMember.System_String__ConcatObject)) - { - callCanReturnNull = true; // string.Concat(object) can return null - arguments = boundCall.Arguments; - return true; - } - - callCanReturnNull = false; // other string.Concat overloads cannot return null - - if ((object)method == (object)_compilation.GetSpecialTypeMember(SpecialMember.System_String__ConcatStringString) || - (object)method == (object)_compilation.GetSpecialTypeMember(SpecialMember.System_String__ConcatStringStringString) || - (object)method == (object)_compilation.GetSpecialTypeMember(SpecialMember.System_String__ConcatStringStringStringString) || - (object)method == (object)_compilation.GetSpecialTypeMember(SpecialMember.System_String__ConcatObjectObject) || - (object)method == (object)_compilation.GetSpecialTypeMember(SpecialMember.System_String__ConcatObjectObjectObject)) - { - arguments = boundCall.Arguments; - return true; - } - - if ((object)method == (object)_compilation.GetSpecialTypeMember(SpecialMember.System_String__ConcatStringArray) || - (object)method == (object)_compilation.GetSpecialTypeMember(SpecialMember.System_String__ConcatObjectArray)) - { - var args = boundCall.Arguments[0] as BoundArrayCreation; - if (args != null) - { - var initializer = args.InitializerOpt; - if (initializer != null) - { - arguments = initializer.Initializers; - return true; - } - } - } - } - - arguments = default; - callCanReturnNull = default; return false; } @@ -309,34 +281,24 @@ private static ConstantValue TryFoldTwoConcatConsts(ConstantValue leftConst, Con /// private BoundExpression RewriteStringConcatenationOneExpr(SyntaxNode syntax, BoundExpression loweredOperand) { - if (loweredOperand.Type.SpecialType == SpecialType.System_String) + // 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 it's a call to 'string.Concat(object) ?? ""' or another overload of 'string.Concat', we know the result cannot - // be null. Otherwise if it's 'string.Concat(object)' or something which isn't 'string.Concat', return loweredOperand ?? "" - if (TryExtractStringConcatArgs(loweredOperand, out _, out bool loweredCanReturnNull) && !loweredCanReturnNull) - { - return loweredOperand; - } - else - { - return _factory.Coalesce(loweredOperand, _factory.Literal("")); - } + return loweredOperand; + } + else + { + return _factory.Coalesce(loweredOperand, _factory.Literal("")); } - - var method = UnsafeGetSpecialTypeMethod(syntax, SpecialMember.System_String__ConcatObject); - Debug.Assert((object)method != null); - - // string.Concat(object) might return null (if the object's ToString method returns null): convert to "" - return _factory.Coalesce((BoundExpression)BoundCall.Synthesized(syntax, null, method, loweredOperand), _factory.Literal("")); } private BoundExpression RewriteStringConcatenationTwoExprs(SyntaxNode syntax, BoundExpression loweredLeft, BoundExpression loweredRight) { - SpecialMember member = (loweredLeft.Type.SpecialType == SpecialType.System_String && loweredRight.Type.SpecialType == SpecialType.System_String) ? - SpecialMember.System_String__ConcatStringString : - SpecialMember.System_String__ConcatObjectObject; + Debug.Assert(loweredLeft.HasAnyErrors || loweredLeft.Type.IsStringType()); + Debug.Assert(loweredRight.HasAnyErrors || loweredRight.Type.IsStringType()); - var method = UnsafeGetSpecialTypeMethod(syntax, member); + var method = UnsafeGetSpecialTypeMethod(syntax, SpecialMember.System_String__ConcatStringString); Debug.Assert((object)method != null); return (BoundExpression)BoundCall.Synthesized(syntax, null, method, loweredLeft, loweredRight); @@ -344,59 +306,40 @@ private BoundExpression RewriteStringConcatenationTwoExprs(SyntaxNode syntax, Bo private BoundExpression RewriteStringConcatenationThreeExprs(SyntaxNode syntax, BoundExpression loweredFirst, BoundExpression loweredSecond, BoundExpression loweredThird) { - SpecialMember member = (loweredFirst.Type.SpecialType == SpecialType.System_String && - loweredSecond.Type.SpecialType == SpecialType.System_String && - loweredThird.Type.SpecialType == SpecialType.System_String) ? - SpecialMember.System_String__ConcatStringStringString : - SpecialMember.System_String__ConcatObjectObjectObject; + Debug.Assert(loweredFirst.HasAnyErrors || loweredFirst.Type.IsStringType()); + Debug.Assert(loweredSecond.HasAnyErrors || loweredSecond.Type.IsStringType()); + Debug.Assert(loweredThird.HasAnyErrors || loweredThird.Type.IsStringType()); - var method = UnsafeGetSpecialTypeMethod(syntax, member); + var method = UnsafeGetSpecialTypeMethod(syntax, SpecialMember.System_String__ConcatStringStringString); Debug.Assert((object)method != null); return BoundCall.Synthesized(syntax, null, method, ImmutableArray.Create(loweredFirst, loweredSecond, loweredThird)); } - private BoundExpression RewriteStringConcatenationManyExprs(SyntaxNode syntax, ImmutableArray loweredArgs) + private BoundExpression RewriteStringConcatenationFourExprs(SyntaxNode syntax, BoundExpression loweredFirst, BoundExpression loweredSecond, BoundExpression loweredThird, BoundExpression loweredFourth) { - Debug.Assert(loweredArgs.Length > 3); - Debug.Assert(loweredArgs.All(a => a.HasErrors || a.Type.SpecialType == SpecialType.System_Object || a.Type.SpecialType == SpecialType.System_String)); - - bool isObject = false; - TypeSymbol elementType = null; + Debug.Assert(loweredFirst.HasAnyErrors || loweredFirst.Type.IsStringType()); + Debug.Assert(loweredSecond.HasAnyErrors || loweredSecond.Type.IsStringType()); + Debug.Assert(loweredThird.HasAnyErrors || loweredThird.Type.IsStringType()); + Debug.Assert(loweredFourth.HasAnyErrors || loweredFourth.Type.IsStringType()); - foreach (var arg in loweredArgs) - { - elementType = arg.Type; - if (elementType.SpecialType != SpecialType.System_String) - { - isObject = true; - break; - } - } + var method = UnsafeGetSpecialTypeMethod(syntax, SpecialMember.System_String__ConcatStringStringStringString); + Debug.Assert((object)method != null); - // Count == 4 is handled differently because there is a Concat method with 4 arguments - // for strings, but there is no such method for objects. - if (!isObject && loweredArgs.Length == 4) - { - SpecialMember member = SpecialMember.System_String__ConcatStringStringStringString; - var method = UnsafeGetSpecialTypeMethod(syntax, member); - Debug.Assert((object)method != null); + return BoundCall.Synthesized(syntax, null, method, ImmutableArray.Create(loweredFirst, loweredSecond, loweredThird, loweredFourth)); + } - return (BoundExpression)BoundCall.Synthesized(syntax, null, method, loweredArgs); - } - else - { - SpecialMember member = isObject ? - SpecialMember.System_String__ConcatObjectArray : - SpecialMember.System_String__ConcatStringArray; + private BoundExpression RewriteStringConcatenationManyExprs(SyntaxNode syntax, ImmutableArray loweredArgs) + { + Debug.Assert(loweredArgs.Length > 4); + Debug.Assert(loweredArgs.All(a => a.HasErrors || a.Type.IsStringType())); - var method = UnsafeGetSpecialTypeMethod(syntax, member); - Debug.Assert((object)method != null); + var method = UnsafeGetSpecialTypeMethod(syntax, SpecialMember.System_String__ConcatStringArray); + Debug.Assert((object)method != null); - var array = _factory.ArrayOrEmpty(elementType, loweredArgs); + var array = _factory.ArrayOrEmpty(_factory.SpecialType(SpecialType.System_String), loweredArgs); - return (BoundExpression)BoundCall.Synthesized(syntax, null, method, array); - } + return (BoundExpression)BoundCall.Synthesized(syntax, null, method, array); } /// @@ -416,89 +359,136 @@ private BoundExpression RewriteStringConcatInExpressionLambda(SyntaxNode syntax, } /// - /// Checks whether the expression represents a boxing conversion of a special value type. - /// If it does, it tries to return a string-based representation instead in order - /// to avoid allocations. If it can't, the original expression is returned. + /// Returns an expression which converts the given expression into a string (or null). + /// If necessary, this invokes .ToString() on the expression, to avoid boxing value types. /// - private BoundExpression ConvertConcatExprToStringIfPossible(SyntaxNode syntax, BoundExpression expr) + private BoundExpression ConvertConcatExprToString(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. if (expr.Kind == BoundKind.Conversion) { BoundConversion conv = (BoundConversion)expr; if (conv.ConversionKind == ConversionKind.Boxing) { - BoundExpression operand = conv.Operand; - if (operand != null) + expr = conv.Operand; + } + } + + // Is the expression a literal char? If so, we can + // simply make it a literal string instead and avoid any + // allocations for converting the char to a string at run time. + // Similarly if it's a literal null, don't do anything special. + if (expr.Kind == BoundKind.Literal) + { + ConstantValue cv = ((BoundLiteral)expr).ConstantValue; + if (cv != null) + { + if (cv.SpecialType == SpecialType.System_Char) { - // Is the expression a literal char? If so, we can - // simply make it a literal string instead and avoid any - // allocations for converting the char to a string at run time. - if (operand.Kind == BoundKind.Literal) - { - ConstantValue cv = ((BoundLiteral)operand).ConstantValue; - if (cv != null && cv.SpecialType == SpecialType.System_Char) - { - return _factory.StringLiteral(cv.CharValue.ToString()); - } - } + return _factory.StringLiteral(cv.CharValue.ToString()); + } + else if (cv.IsNull) + { + return expr; + } + } + } - // Can the expression be optimized with a ToString call? - // If so, we can synthesize a ToString call to avoid boxing. - if (ConcatExprCanBeOptimizedWithToString(operand.Type)) - { - var toString = UnsafeGetSpecialTypeMethod(syntax, SpecialMember.System_Object__ToString); + // If it's a string already, just return it + if (expr.Type.IsStringType()) + { + return expr; + } - var type = (NamedTypeSymbol)operand.Type; - var toStringMembers = type.GetMembers(toString.Name); - foreach (var member in toStringMembers) - { - var toStringMethod = member as MethodSymbol; - if (toStringMethod.GetLeastOverriddenMethod(type) == (object)toString) - { - return BoundCall.Synthesized(syntax, operand, toStringMethod); - } - } - } + // Evaluate toString at the last possible moment, to avoid spurious diagnostics if it's missing. + // All code paths below here use it. + var objectToStringMethod = UnsafeGetSpecialTypeMethod(syntax, SpecialMember.System_Object__ToString); + + // 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) + { + var toStringMethod = (MethodSymbol)member; + if (toStringMethod.GetLeastOverriddenMethod(type) == (object)objectToStringMethod) + { + structToStringMethod = toStringMethod; + break; } } } - // Optimization not possible; just return the original expression. - return expr; - } + // If it's a special value type, 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) + { + return BoundCall.Synthesized(expr.Syntax, expr, structToStringMethod); + } - /// - /// Gets whether the type of an argument used in string concatenation can - /// be optimized by first calling ToString on it before passing the argument - /// to the String.Concat function. - /// - /// The type symbol of the argument. - /// - /// true if ToString may be used; false if using ToString could lead to observable differences in behavior. - /// - private static bool ConcatExprCanBeOptimizedWithToString(TypeSymbol symbol) - { - // There are several constraints applied here in support of backwards compatibility: - // - This optimization potentially changes the order in which ToString is called - // on the arguments. That's a a compatibility issue if one argument's ToString - // depends on state mutated by another, such as current culture. - // - For value types, this optimization causes ToString to be called on the original - // value rather than on a boxed copy. That means a mutating ToString implementation - // could change the original rather than the copy. - // For these reasons, this optimization is currently restricted to primitives - // known to have a non-mutating ToString implementation that is independent - // of externally mutable state. Common value types such as Int32 and Double - // do not meet this bar. - - switch (symbol.SpecialType) + // - It's a reference type (excluding unconstrained generics): no copy + // - It's a constant: no copy + // - The type definitely doesn't have its own ToString method (i.e. we're definitely calling + // object.ToString on a struct type, not type parameter): no copy (yes this is a versioning issue, + // but that doesn't matter) + // - We're calling the type's own ToString method, and it's effectively readonly (the method or the whole + // type is readonly): no copy + // - Otherwise: copy + // This is to minic the old behaviour, where value types would be boxed before ToString was called on them, + // but with optimizations for readonly methods. + bool callWithoutCopy = expr.Type.IsReferenceType || + expr.ConstantValue != null || + (structToStringMethod == null && !expr.Type.IsTypeParameter()) || + structToStringMethod?.IsEffectivelyReadOnly == true; + + // No need for a conditional access if it's a value type - we know it's not null. + if (expr.Type.IsValueType) { - case SpecialType.System_Boolean: - case SpecialType.System_Char: - case SpecialType.System_IntPtr: - case SpecialType.System_UIntPtr: - return true; - default: - return false; + if (!callWithoutCopy) + { + expr = new BoundPassByCopy(expr.Syntax, expr, expr.Type); + } + return BoundCall.Synthesized(expr.Syntax, expr, objectToStringMethod); + } + + if (callWithoutCopy) + { + return makeConditionalAccess(expr); + } + else + { + // If we do conditional access on a copy, we need a proper BoundLocal rather than a + // BoundPassByCopy (as it's accessed multiple times). If we don't do this, and the + // receiver is an unconstrained generic parameter, BoundLoweredConditionalAccess has + // to generate a lot of code to ensure it only accesses the copy once (which is pointless). + var temp = _factory.StoreToTemp(expr, out var store); + return _factory.Sequence( + ImmutableArray.Create(temp.LocalSymbol), + ImmutableArray.Create(store), + makeConditionalAccess(temp)); + } + + BoundExpression makeConditionalAccess(BoundExpression receiver) + { + int currentConditionalAccessID = ++_currentConditionalAccessID; + + return new BoundLoweredConditionalAccess( + syntax, + receiver, + hasValueMethodOpt: null, + whenNotNull: BoundCall.Synthesized( + syntax, + new BoundConditionalReceiver(syntax, currentConditionalAccessID, expr.Type), + objectToStringMethod), + whenNullOpt: null, + id: currentConditionalAccessID, + type: _compilation.GetSpecialType(SpecialType.System_String)); } } } diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs index 29e7e8a0fa4a2..90c8f400a092a 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs @@ -105,25 +105,28 @@ public void Deconstruct(out int a, out string b) comp.VerifyDiagnostics(); comp.VerifyIL("C.Main", @" { - // Code size 40 (0x28) + // Code size 43 (0x2b) .maxstack 3 - .locals init (string V_0, //y - int V_1, - string V_2) + .locals init (long V_0, //x + string V_1, //y + int V_2, + string V_3) IL_0000: newobj ""C..ctor()"" - IL_0005: ldloca.s V_1 - IL_0007: ldloca.s V_2 + IL_0005: ldloca.s V_2 + IL_0007: ldloca.s V_3 IL_0009: callvirt ""void C.Deconstruct(out int, out string)"" - IL_000e: ldloc.1 + IL_000e: ldloc.2 IL_000f: conv.i8 - IL_0010: ldloc.2 - IL_0011: stloc.0 - IL_0012: box ""long"" - IL_0017: ldstr "" "" - IL_001c: ldloc.0 - IL_001d: call ""string string.Concat(object, object, object)"" - IL_0022: call ""void System.Console.WriteLine(string)"" - IL_0027: ret + IL_0010: stloc.0 + IL_0011: ldloc.3 + IL_0012: stloc.1 + IL_0013: ldloca.s V_0 + IL_0015: call ""string long.ToString()"" + IL_001a: ldstr "" "" + IL_001f: ldloc.1 + IL_0020: call ""string string.Concat(string, string, string)"" + IL_0025: call ""void System.Console.WriteLine(string)"" + IL_002a: ret }"); } @@ -190,24 +193,27 @@ public void Deconstruct(out int a, out string b) comp.VerifyDiagnostics(); comp.VerifyIL("C.Main", @" { - // Code size 39 (0x27) + // Code size 42 (0x2a) .maxstack 3 - .locals init (string V_0, //y - int V_1, - string V_2) + .locals init (int V_0, //x + string V_1, //y + int V_2, + string V_3) IL_0000: newobj ""C..ctor()"" - IL_0005: ldloca.s V_1 - IL_0007: ldloca.s V_2 + IL_0005: ldloca.s V_2 + IL_0007: ldloca.s V_3 IL_0009: callvirt ""void C.Deconstruct(out int, out string)"" - IL_000e: ldloc.1 - IL_000f: ldloc.2 - IL_0010: stloc.0 - IL_0011: box ""int"" - IL_0016: ldstr "" "" - IL_001b: ldloc.0 - IL_001c: call ""string string.Concat(object, object, object)"" - IL_0021: call ""void System.Console.WriteLine(string)"" - IL_0026: ret + IL_000e: ldloc.2 + IL_000f: stloc.0 + IL_0010: ldloc.3 + IL_0011: stloc.1 + IL_0012: ldloca.s V_0 + IL_0014: call ""string int.ToString()"" + IL_0019: ldstr "" "" + IL_001e: ldloc.1 + IL_001f: call ""string string.Concat(string, string, string)"" + IL_0024: call ""void System.Console.WriteLine(string)"" + IL_0029: ret }"); } @@ -1159,7 +1165,7 @@ static void Main() int y; (x, x, x, x, x, x, x, x, x, y) = (1, 1, 1, 1, 1, 1, 1, 1, 4, 2); - System.Console.WriteLine(x + "" "" + y); + System.Console.WriteLine(string.Concat(x, "" "", y)); } } "; @@ -3019,18 +3025,21 @@ static void Main() comp.VerifyDiagnostics(); comp.VerifyIL("C.Main", @" { - // Code size 29 (0x1d) + // Code size 32 (0x20) .maxstack 3 - .locals init (string V_0) //x2 + .locals init (int V_0, //x1 + string V_1) //x2 IL_0000: ldc.i4.1 - IL_0001: ldstr ""hello"" - IL_0006: stloc.0 - IL_0007: box ""int"" - IL_000c: ldstr "" "" - IL_0011: ldloc.0 - IL_0012: call ""string string.Concat(object, object, object)"" - IL_0017: call ""void System.Console.WriteLine(string)"" - IL_001c: ret + IL_0001: stloc.0 + IL_0002: ldstr ""hello"" + IL_0007: stloc.1 + IL_0008: ldloca.s V_0 + IL_000a: call ""string int.ToString()"" + IL_000f: ldstr "" "" + IL_0014: ldloc.1 + IL_0015: call ""string string.Concat(string, string, string)"" + IL_001a: call ""void System.Console.WriteLine(string)"" + IL_001f: ret }"); } @@ -4078,7 +4087,7 @@ static void Main() comp.VerifyDiagnostics(); comp.VerifyIL("C.Main", @"{ - // Code size 91 (0x5b) + // Code size 75 (0x4b) .maxstack 4 .locals init ((int, int)[] V_0, int V_1, @@ -4088,7 +4097,7 @@ .locals init ((int, int)[] V_0, IL_0005: stloc.0 IL_0006: ldc.i4.0 IL_0007: stloc.1 - IL_0008: br.s IL_0054 + IL_0008: br.s IL_0044 IL_000a: ldloc.0 IL_000b: ldloc.1 IL_000c: ldelem ""System.ValueTuple"" @@ -4097,38 +4106,24 @@ .locals init ((int, int)[] V_0, IL_0017: stloc.2 IL_0018: ldfld ""int System.ValueTuple.Item2"" IL_001d: stloc.3 - IL_001e: ldc.i4.4 - IL_001f: newarr ""object"" - IL_0024: dup - IL_0025: ldc.i4.0 - IL_0026: ldloc.2 - IL_0027: box ""int"" - IL_002c: stelem.ref - IL_002d: dup - IL_002e: ldc.i4.1 - IL_002f: ldstr "" "" - IL_0034: stelem.ref - IL_0035: dup - IL_0036: ldc.i4.2 - IL_0037: ldloc.3 - IL_0038: box ""int"" - IL_003d: stelem.ref - IL_003e: dup - IL_003f: ldc.i4.3 - IL_0040: ldstr "" - "" - IL_0045: stelem.ref - IL_0046: call ""string string.Concat(params object[])"" - IL_004b: call ""void System.Console.Write(string)"" - IL_0050: ldloc.1 - IL_0051: ldc.i4.1 - IL_0052: add - IL_0053: stloc.1 - IL_0054: ldloc.1 - IL_0055: ldloc.0 - IL_0056: ldlen - IL_0057: conv.i4 - IL_0058: blt.s IL_000a - IL_005a: ret + IL_001e: ldloca.s V_2 + IL_0020: call ""string int.ToString()"" + IL_0025: ldstr "" "" + IL_002a: ldloca.s V_3 + IL_002c: call ""string int.ToString()"" + IL_0031: ldstr "" - "" + IL_0036: call ""string string.Concat(string, string, string, string)"" + IL_003b: call ""void System.Console.Write(string)"" + IL_0040: ldloc.1 + IL_0041: ldc.i4.1 + IL_0042: add + IL_0043: stloc.1 + IL_0044: ldloc.1 + IL_0045: ldloc.0 + IL_0046: ldlen + IL_0047: conv.i4 + IL_0048: blt.s IL_000a + IL_004a: ret }"); } diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenStringConcat.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenStringConcat.cs index bc821657e1795..8690263411638 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenStringConcat.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenStringConcat.cs @@ -205,79 +205,115 @@ static void Main() comp.VerifyDiagnostics(); comp.VerifyIL("Test.Main", @" { - // Code size 187 (0xbb) - .maxstack 4 + // 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: ldstr ""AB"" - IL_0023: ldsfld ""object Test.O"" - IL_0028: call ""string string.Concat(object, object, object)"" - IL_002d: call ""void System.Console.WriteLine(string)"" - IL_0032: ldc.i4.6 - IL_0033: newarr ""object"" - IL_0038: dup - IL_0039: ldc.i4.0 - IL_003a: ldsfld ""string Test.S"" - IL_003f: stelem.ref - IL_0040: dup - IL_0041: ldc.i4.1 - IL_0042: ldstr ""AB"" - IL_0047: stelem.ref - IL_0048: dup - IL_0049: ldc.i4.2 - IL_004a: ldsfld ""string Test.S"" - IL_004f: stelem.ref + 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.3 - IL_0052: ldsfld ""object Test.O"" + IL_0051: ldc.i4.0 + IL_0052: ldsfld ""string Test.S"" IL_0057: stelem.ref IL_0058: dup - IL_0059: ldc.i4.4 + IL_0059: ldc.i4.1 IL_005a: ldstr ""AB"" IL_005f: stelem.ref IL_0060: dup - IL_0061: ldc.i4.5 - IL_0062: ldsfld ""object Test.O"" + IL_0061: ldc.i4.2 + IL_0062: ldsfld ""string Test.S"" IL_0067: stelem.ref - IL_0068: call ""string string.Concat(params object[])"" - IL_006d: call ""void System.Console.WriteLine(string)"" - IL_0072: ldc.i4.7 - IL_0073: newarr ""object"" - IL_0078: dup - IL_0079: ldc.i4.0 - IL_007a: ldsfld ""object Test.O"" - IL_007f: stelem.ref - IL_0080: dup - IL_0081: ldc.i4.1 - IL_0082: ldstr ""A"" - IL_0087: stelem.ref - IL_0088: dup - IL_0089: ldc.i4.2 - IL_008a: ldsfld ""string Test.S"" - IL_008f: stelem.ref - IL_0090: dup - IL_0091: ldc.i4.3 - IL_0092: ldstr ""AB"" + 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: dup - IL_0099: ldc.i4.4 - IL_009a: ldsfld ""object Test.O"" - IL_009f: stelem.ref - IL_00a0: dup - IL_00a1: ldc.i4.5 - IL_00a2: ldsfld ""string Test.S"" - IL_00a7: 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.6 - IL_00aa: ldstr ""A"" - IL_00af: stelem.ref - IL_00b0: call ""string string.Concat(params object[])"" - IL_00b5: call ""void System.Console.WriteLine(string)"" - IL_00ba: ret + 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 } "); } @@ -349,31 +385,50 @@ static void Main() { Console.WriteLine(O + null); Console.WriteLine(S + null); + Console.WriteLine(O?.ToString() + null); } } "; var comp = CompileAndVerify(source, expectedOutput: @"O -F"); +F +O"); comp.VerifyDiagnostics(); comp.VerifyIL("Test.Main", @" { - // Code size 44 (0x2c) + // Code size 82 (0x52) .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 ""string Test.S"" - IL_001d: dup - IL_001e: brtrue.s IL_0026 - IL_0020: pop - IL_0021: ldstr """" - IL_0026: call ""void System.Console.WriteLine(string)"" - IL_002b: ret + 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 } "); } @@ -406,35 +461,45 @@ public class C comp.VerifyDiagnostics(); comp.VerifyIL("Test.Main", @" { - // Code size 97 (0x61) + // Code size 111 (0x6f) .maxstack 2 IL_0000: ldsfld ""object Test.C"" - IL_0005: call ""string string.Concat(object)"" - IL_000a: dup - IL_000b: brtrue.s IL_0013 - IL_000d: pop - IL_000e: ldstr """" - IL_0013: ldstr """" - IL_0018: call ""bool string.op_Equality(string, string)"" - IL_001d: brtrue.s IL_0026 - IL_001f: ldstr ""N"" - IL_0024: br.s IL_002b - IL_0026: ldstr ""Y"" - IL_002b: call ""void System.Console.WriteLine(string)"" - IL_0030: ldsfld ""object Test.C"" - IL_0035: call ""string string.Concat(object)"" - IL_003a: dup - IL_003b: brtrue.s IL_0043 - IL_003d: pop - IL_003e: ldstr """" - IL_0043: ldstr """" - IL_0048: call ""bool string.op_Equality(string, string)"" - IL_004d: brtrue.s IL_0056 - IL_004f: ldstr ""N"" - IL_0054: br.s IL_005b - IL_0056: ldstr ""Y"" - IL_005b: call ""void System.Console.WriteLine(string)"" - IL_0060: ret + 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 } "); } @@ -507,22 +572,27 @@ static void Main() comp.VerifyDiagnostics(); comp.VerifyIL("Test.Main", @" { - // Code size 44 (0x2c) + // Code size 51 (0x33) .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 ""string Test.S"" - IL_001d: dup - IL_001e: brtrue.s IL_0026 - IL_0020: pop - IL_0021: ldstr """" - IL_0026: call ""void System.Console.WriteLine(string)"" - IL_002b: ret + 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 } "); } @@ -650,7 +720,7 @@ private static void TestMethod() comp.VerifyDiagnostics(); comp.VerifyIL("Test.TestMethod()", @" { - // Code size 211 (0xd3) + // Code size 291 (0x123) .maxstack 4 .locals init (T V_0) IL_0000: ldstr ""A"" @@ -658,66 +728,86 @@ .locals init (T V_0) IL_0007: initobj ""T"" IL_000d: ldloc.0 IL_000e: box ""T"" - IL_0013: call ""string string.Concat(object, object)"" - IL_0018: call ""void System.Console.WriteLine(string)"" - IL_001d: ldc.i4.4 - IL_001e: newarr ""object"" - IL_0023: dup - IL_0024: ldc.i4.0 - IL_0025: ldstr ""A"" - IL_002a: stelem.ref - IL_002b: dup - IL_002c: ldc.i4.1 - IL_002d: ldloca.s V_0 - IL_002f: initobj ""T"" - IL_0035: ldloc.0 - IL_0036: box ""T"" - IL_003b: stelem.ref - IL_003c: dup - IL_003d: ldc.i4.2 - IL_003e: ldstr ""A"" - IL_0043: stelem.ref - IL_0044: dup - IL_0045: ldc.i4.3 - IL_0046: ldloca.s V_0 - IL_0048: initobj ""T"" - IL_004e: ldloc.0 - IL_004f: box ""T"" - IL_0054: stelem.ref - IL_0055: call ""string string.Concat(params object[])"" - IL_005a: call ""void System.Console.WriteLine(string)"" - IL_005f: ldloca.s V_0 - IL_0061: initobj ""T"" - IL_0067: ldloc.0 - IL_0068: box ""T"" - IL_006d: ldstr ""B"" - IL_0072: call ""string string.Concat(object, object)"" - IL_0077: call ""void System.Console.WriteLine(string)"" - IL_007c: ldloca.s V_0 - IL_007e: initobj ""T"" - IL_0084: ldloc.0 - IL_0085: box ""T"" - IL_008a: call ""string string.Concat(object)"" - IL_008f: dup - IL_0090: brtrue.s IL_0098 - IL_0092: pop - IL_0093: ldstr """" - IL_0098: call ""void System.Console.WriteLine(string)"" - IL_009d: ldstr ""#"" - IL_00a2: call ""void System.Console.WriteLine(string)"" - IL_00a7: ldloca.s V_0 - IL_00a9: initobj ""T"" - IL_00af: ldloc.0 - IL_00b0: box ""T"" - IL_00b5: call ""string string.Concat(object)"" - IL_00ba: dup - IL_00bb: brtrue.s IL_00c3 - IL_00bd: pop - IL_00be: ldstr """" - IL_00c3: call ""void System.Console.WriteLine(string)"" - IL_00c8: ldstr ""#"" - IL_00cd: call ""void System.Console.WriteLine(string)"" - IL_00d2: ret + 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 } "); } @@ -813,6 +903,257 @@ .maxstack 1 "); } + [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() { @@ -878,6 +1219,7 @@ static void Main() var comp = CompileAndVerify(source, expectedOutput: "\"\""); + comp.VerifyDiagnostics(); comp.VerifyIL("Repro.Bug", @" { // Code size 17 (0x11) @@ -932,7 +1274,68 @@ .maxstack 3 } [Fact] - public void ConcatMutableStructs() + 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; @@ -941,12 +1344,15 @@ public void ConcatMutableStructs() struct Mutable { int x; - public override string ToString() => (x++).ToString(); +} + +class Test +{ + static Mutable m = new Mutable(); static void Main() { - Mutable m = new Mutable(); Write(""("" + m + "")""); // (0) Write(""("" + m + "")""); // (0) @@ -967,6 +1373,230 @@ static void Main() 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() { @@ -1018,13 +1648,14 @@ static void Main() comp.VerifyDiagnostics(); comp.VerifyIL("Test.Main", @" { - // Code size 473 (0x1d9) + // 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) + System.UIntPtr V_4, + int V_5) IL_0000: ldc.i4.s 99 IL_0002: stloc.0 IL_0003: ldc.i4.s 100 @@ -1125,7 +1756,7 @@ .locals init (char V_0, //c IL_013c: call ""string string.Concat(params string[])"" IL_0141: call ""void System.Console.WriteLine(string)"" IL_0146: ldc.i4.6 - IL_0147: newarr ""object"" + IL_0147: newarr ""string"" IL_014c: dup IL_014d: ldc.i4.0 IL_014e: ldstr ""20"" @@ -1133,65 +1764,227 @@ .locals init (char V_0, //c IL_0154: dup IL_0155: ldc.i4.1 IL_0156: ldc.i4.s 21 - IL_0158: box ""int"" - IL_015d: stelem.ref - IL_015e: dup - IL_015f: ldc.i4.2 - IL_0160: ldloca.s V_0 - IL_0162: call ""string char.ToString()"" - IL_0167: stelem.ref - IL_0168: dup - IL_0169: ldc.i4.3 - IL_016a: ldloca.s V_1 - IL_016c: call ""string char.ToString()"" - IL_0171: stelem.ref - IL_0172: dup - IL_0173: ldc.i4.4 - IL_0174: ldloca.s V_0 - IL_0176: call ""string char.ToString()"" - IL_017b: stelem.ref - IL_017c: dup - IL_017d: ldc.i4.5 - IL_017e: ldloca.s V_1 - IL_0180: call ""string char.ToString()"" - IL_0185: stelem.ref - IL_0186: call ""string string.Concat(params object[])"" - IL_018b: call ""void System.Console.WriteLine(string)"" - IL_0190: ldc.i4.6 - IL_0191: newarr ""string"" - IL_0196: dup - IL_0197: ldc.i4.0 - IL_0198: ldstr ""22"" - IL_019d: stelem.ref - IL_019e: dup - IL_019f: ldc.i4.1 - IL_01a0: ldloca.s V_0 - IL_01a2: call ""string char.ToString()"" - IL_01a7: stelem.ref - IL_01a8: dup - IL_01a9: ldc.i4.2 - IL_01aa: ldstr ""23"" - IL_01af: stelem.ref - IL_01b0: dup - IL_01b1: ldc.i4.3 - IL_01b2: ldloca.s V_1 - IL_01b4: call ""string char.ToString()"" - IL_01b9: stelem.ref - IL_01ba: dup - IL_01bb: ldc.i4.4 - IL_01bc: ldloca.s V_0 - IL_01be: call ""string char.ToString()"" - IL_01c3: stelem.ref - IL_01c4: dup - IL_01c5: ldc.i4.5 - IL_01c6: ldloca.s V_1 - IL_01c8: call ""string char.ToString()"" - IL_01cd: stelem.ref - IL_01ce: call ""string string.Concat(params string[])"" - IL_01d3: call ""void System.Console.WriteLine(string)"" - IL_01d8: ret + 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/Emit/CodeGen/CodeGenTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTests.cs index 46bceddedb3c6..8cd7fe29ec339 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTests.cs @@ -16705,7 +16705,7 @@ static void G(object[] a, string s) var verifier = CompileAndVerify(source, expectedOutput: "AB"); verifier.VerifyIL("C.G", @"{ - // Code size 15 (0xf) + // Code size 27 (0x1b) .maxstack 4 .locals init (object[] V_0) IL_0000: ldarg.0 @@ -16715,10 +16715,16 @@ .locals init (object[] V_0) IL_0004: ldloc.0 IL_0005: ldc.i4.0 IL_0006: ldelem.ref - IL_0007: ldarg.1 - IL_0008: call ""string string.Concat(object, object)"" - IL_000d: stelem.ref - IL_000e: ret + IL_0007: dup + IL_0008: brtrue.s IL_000e + IL_000a: pop + IL_000b: ldnull + IL_000c: br.s IL_0013 + IL_000e: callvirt ""string object.ToString()"" + IL_0013: ldarg.1 + IL_0014: call ""string string.Concat(string, string)"" + IL_0019: stelem.ref + IL_001a: ret }"); } @@ -16789,7 +16795,7 @@ static int Index(object arg) var verifier = CompileAndVerify(source, expectedOutput: "Object[]AString[]B"); verifier.VerifyIL("C.G", @"{ - // Code size 22 (0x16) + // Code size 34 (0x22) .maxstack 4 .locals init (object[] V_0, int V_1) @@ -16803,10 +16809,16 @@ .locals init (object[] V_0, IL_000b: ldloc.0 IL_000c: ldloc.1 IL_000d: ldelem.ref - IL_000e: ldarg.1 - IL_000f: call ""string string.Concat(object, object)"" - IL_0014: stelem.ref - IL_0015: ret + IL_000e: dup + IL_000f: brtrue.s IL_0015 + IL_0011: pop + IL_0012: ldnull + IL_0013: br.s IL_001a + IL_0015: callvirt ""string object.ToString()"" + IL_001a: ldarg.1 + IL_001b: call ""string string.Concat(string, string)"" + IL_0020: stelem.ref + IL_0021: ret }"); } diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTryFinally.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTryFinally.cs index 3eb3bb5b409d7..802b18bc15e6b 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTryFinally.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTryFinally.cs @@ -2381,10 +2381,11 @@ static void Test() CompileAndVerify(src, expectedOutput: "TryCatch228Finally"). VerifyIL("C.Test", @" { - // Code size 129 (0x81) + // Code size 132 (0x84) .maxstack 2 .locals init (int V_0, //x - System.DivideByZeroException V_1) //e + System.DivideByZeroException V_1, //e + int V_2) IL_0000: ldc.i4.0 IL_0001: stloc.0 .try @@ -2397,7 +2398,7 @@ .locals init (int V_0, //x IL_000d: ldloc.0 IL_000e: div IL_000f: stloc.0 - IL_0010: leave.s IL_0080 + IL_0010: leave.s IL_0083 } filter { @@ -2418,7 +2419,7 @@ .locals init (int V_0, //x IL_002b: pop IL_002c: ldstr ""Catch1"" IL_0031: call ""void System.Console.Write(string)"" - IL_0036: leave.s IL_0080 + IL_0036: leave.s IL_0083 } filter { @@ -2443,21 +2444,22 @@ .locals init (int V_0, //x IL_0059: ldloc.1 IL_005a: callvirt ""string System.Exception.Message.get"" IL_005f: callvirt ""int string.Length.get"" - IL_0064: box ""int"" - IL_0069: call ""string string.Concat(object, object)"" - IL_006e: call ""void System.Console.Write(string)"" - IL_0073: leave.s IL_0080 + IL_0064: stloc.2 + IL_0065: ldloca.s V_2 + IL_0067: call ""string int.ToString()"" + IL_006c: call ""string string.Concat(string, string)"" + IL_0071: call ""void System.Console.Write(string)"" + IL_0076: leave.s IL_0083 } } finally { - IL_0075: ldstr ""Finally"" - IL_007a: call ""void System.Console.Write(string)"" - IL_007f: endfinally + IL_0078: ldstr ""Finally"" + IL_007d: call ""void System.Console.Write(string)"" + IL_0082: endfinally } - IL_0080: ret -} -"); + IL_0083: ret +}"); } [Fact] diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/SwitchTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/SwitchTests.cs index 0081eabcaab43..32faba301cdb3 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/SwitchTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/SwitchTests.cs @@ -8935,7 +8935,7 @@ public static string M2(object o) Generic.Color.Red"); compVerifier.VerifyIL("Program.M2", @"{ - // Code size 108 (0x6c) + // Code size 115 (0x73) .maxstack 2 .locals init (Generic.Color V_0, //c object V_1, @@ -8957,38 +8957,39 @@ .locals init (Generic.Color V_0, //c IL_0016: br.s IL_0038 IL_0018: ldloc.1 IL_0019: isinst ""Generic.Color"" - IL_001e: brfalse.s IL_0060 + IL_001e: brfalse.s IL_0067 IL_0020: ldloc.1 IL_0021: unbox.any ""Generic.Color"" IL_0026: stloc.2 IL_0027: ldloc.2 IL_0028: ldc.i4.5 - IL_0029: beq.s IL_004e + IL_0029: beq.s IL_0055 IL_002b: ldloc.1 IL_002c: unbox.any ""Generic.Color"" IL_0031: stloc.3 IL_0032: ldloc.3 IL_0033: ldc.i4.4 - IL_0034: beq.s IL_0057 - IL_0036: br.s IL_0060 + IL_0034: beq.s IL_005e + IL_0036: br.s IL_0067 IL_0038: br.s IL_003a IL_003a: ldstr ""Generic.Color."" - IL_003f: ldloc.0 - IL_0040: box ""Generic.Color"" - IL_0045: call ""string string.Concat(object, object)"" - IL_004a: stloc.s V_5 - IL_004c: br.s IL_0069 - IL_004e: ldstr ""Generic.Color.Red"" - IL_0053: stloc.s V_5 - IL_0055: br.s IL_0069 - IL_0057: ldstr ""Generic.Color.Blue"" - IL_005c: stloc.s V_5 - IL_005e: br.s IL_0069 - IL_0060: ldstr ""None"" - IL_0065: stloc.s V_5 - IL_0067: br.s IL_0069 - IL_0069: ldloc.s V_5 - IL_006b: ret + IL_003f: ldloca.s V_0 + IL_0041: constrained. ""Generic.Color"" + IL_0047: callvirt ""string object.ToString()"" + IL_004c: call ""string string.Concat(string, string)"" + IL_0051: stloc.s V_5 + IL_0053: br.s IL_0070 + IL_0055: ldstr ""Generic.Color.Red"" + IL_005a: stloc.s V_5 + IL_005c: br.s IL_0070 + IL_005e: ldstr ""Generic.Color.Blue"" + IL_0063: stloc.s V_5 + IL_0065: br.s IL_0070 + IL_0067: ldstr ""None"" + IL_006c: stloc.s V_5 + IL_006e: br.s IL_0070 + IL_0070: ldloc.s V_5 + IL_0072: ret }" ); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/OutVarTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/OutVarTests.cs index 3a1da1b2ad83b..42528ded86fd3 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/OutVarTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/OutVarTests.cs @@ -20363,43 +20363,48 @@ 5 4 6") .VerifyIL("B.Main()", @"{ - // Code size 103 (0x67) + // Code size 113 (0x71) .maxstack 4 .locals init (int V_0, //x1 int V_1, //x2 int V_2, //x3 - int V_3) //x4 + int V_3, //x4 + int V_4) IL_0000: newobj ""A..ctor()"" IL_0005: dup IL_0006: ldloca.s V_0 IL_0008: callvirt ""int IA.P[out int].get"" - IL_000d: box ""int"" - IL_0012: ldstr "" "" - IL_0017: ldloc.0 - IL_0018: box ""int"" - IL_001d: call ""string string.Concat(object, object, object)"" - IL_0022: call ""void System.Console.WriteLine(string)"" - IL_0027: dup - IL_0028: ldloca.s V_1 - IL_002a: ldc.i4.4 - IL_002b: callvirt ""void IA.P[out int].set"" - IL_0030: ldloc.1 - IL_0031: call ""void System.Console.WriteLine(int)"" - IL_0036: dup - IL_0037: ldloca.s V_2 - IL_0039: callvirt ""int IA.this[out int].get"" - IL_003e: box ""int"" - IL_0043: ldstr "" "" - IL_0048: ldloc.2 - IL_0049: box ""int"" - IL_004e: call ""string string.Concat(object, object, object)"" - IL_0053: call ""void System.Console.WriteLine(string)"" - IL_0058: ldloca.s V_3 - IL_005a: ldc.i4.4 - IL_005b: callvirt ""void IA.this[out int].set"" - IL_0060: ldloc.3 - IL_0061: call ""void System.Console.WriteLine(int)"" - IL_0066: ret + IL_000d: stloc.s V_4 + IL_000f: ldloca.s V_4 + IL_0011: call ""string int.ToString()"" + IL_0016: ldstr "" "" + IL_001b: ldloca.s V_0 + IL_001d: call ""string int.ToString()"" + IL_0022: call ""string string.Concat(string, string, string)"" + IL_0027: call ""void System.Console.WriteLine(string)"" + IL_002c: dup + IL_002d: ldloca.s V_1 + IL_002f: ldc.i4.4 + IL_0030: callvirt ""void IA.P[out int].set"" + IL_0035: ldloc.1 + IL_0036: call ""void System.Console.WriteLine(int)"" + IL_003b: dup + IL_003c: ldloca.s V_2 + IL_003e: callvirt ""int IA.this[out int].get"" + IL_0043: stloc.s V_4 + IL_0045: ldloca.s V_4 + IL_0047: call ""string int.ToString()"" + IL_004c: ldstr "" "" + IL_0051: ldloca.s V_2 + IL_0053: call ""string int.ToString()"" + IL_0058: call ""string string.Concat(string, string, string)"" + IL_005d: call ""void System.Console.WriteLine(string)"" + IL_0062: ldloca.s V_3 + IL_0064: ldc.i4.4 + IL_0065: callvirt ""void IA.this[out int].set"" + IL_006a: ldloc.3 + IL_006b: call ""void System.Console.WriteLine(int)"" + IL_0070: ret }"); } } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs index 2c84852163e5e..69f079db4e9a5 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/MissingSpecialMember.cs @@ -1373,39 +1373,6 @@ public static void Main() ); } - [Fact] - public void System_String__ConcatObjectObject() - { - var source = -@" - -using System; - -class Class1 -{ - static void Main() - { - } -} - -class MyClass -{ - public static implicit operator MyClass(decimal Value) - { - Console.WriteLine(""Value is: "" + Value); - return new MyClass(); - } -} -"; - var compilation = CreateCompilationWithMscorlib45(source); - compilation.MakeMemberMissing(SpecialMember.System_String__ConcatObjectObject); - compilation.VerifyEmitDiagnostics( - // (16,27): error CS0656: Missing compiler required member 'System.String.Concat' - // Console.WriteLine("Value is: " + Value); - Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"""Value is: "" + Value").WithArguments("System.String", "Concat").WithLocation(16, 27) - ); - } - [Fact] public void System_Nullable_T_GetValueOrDefault_04() { @@ -1523,6 +1490,31 @@ public static implicit operator S(int? s) ); } + [Fact] + public void System_String__ConcatObjectObject() + { + var source = +@" +using System; +using System.Linq.Expressions; + +class Class1 +{ + static void Main() + { + Expression> e = x => ""X = "" + x; + } +} +"; + var compilation = CreateCompilationWithMscorlib45(source, new[] { SystemCoreRef }); + compilation.MakeMemberMissing(SpecialMember.System_String__ConcatObjectObject); + compilation.VerifyEmitDiagnostics( + // (9,51): error CS0656: Missing compiler required member 'System.String.Concat' + // Expression> e = x => "X = " + x; + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, @"""X = "" + x").WithArguments("System.String", "Concat").WithLocation(9, 51) + ); + } + [Fact] public void System_String__ConcatStringStringString() { @@ -1533,7 +1525,7 @@ struct S private string str; public S(char chr) { this.str = chr.ToString(); } public S(string str) { this.str = str; } - public static S operator + (S x, S y) { return new S('(' + x.str + '+' + y.str + ')'); } + public static S operator + (S x, S y) { return new S(x.str + '+' + y.str); } } class C @@ -1546,8 +1538,8 @@ static void Main() compilation.MakeMemberMissing(SpecialMember.System_String__ConcatStringStringString); compilation.VerifyEmitDiagnostics( // (8,58): error CS0656: Missing compiler required member 'System.String.Concat' - // public static S operator + (S x, S y) { return new S('(' + x.str + '+' + y.str + ')'); } - Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "'(' + x.str + '+'").WithArguments("System.String", "Concat").WithLocation(8, 58) + // public static S operator + (S x, S y) { return new S(x.str + '+' + y.str); } + Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "x.str + '+' + y.str").WithArguments("System.String", "Concat").WithLocation(8, 58) ); } @@ -2267,28 +2259,26 @@ struct X [Fact] public void System_String__ConcatObject() { + // It isn't possible to trigger this diagnostic, as we don't use String.Concat(object) + 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); } } -"; - var compilation = CreateCompilationWithMscorlib45(source); + "; + var compilation = CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe); compilation.MakeMemberMissing(SpecialMember.System_String__ConcatObject); - compilation.VerifyEmitDiagnostics( - // (11,27): error CS0656: Missing compiler required member 'System.String.Concat' - // Console.WriteLine(O + null); - Diagnostic(ErrorCode.ERR_MissingPredefinedMember, "O + null").WithArguments("System.String", "Concat").WithLocation(11, 27) - ); + compilation.VerifyEmitDiagnostics(); // We don't expect any + CompileAndVerify(compilation, expectedOutput: @"O +F"); } [Fact] diff --git a/src/Compilers/CSharp/Test/WinRT/CodeGen/WinRTCollectionTests.cs b/src/Compilers/CSharp/Test/WinRT/CodeGen/WinRTCollectionTests.cs index fe0b5bb757de8..db0a12510a848 100644 --- a/src/Compilers/CSharp/Test/WinRT/CodeGen/WinRTCollectionTests.cs +++ b/src/Compilers/CSharp/Test/WinRT/CodeGen/WinRTCollectionTests.cs @@ -253,8 +253,8 @@ public static void Main(string[] args) verifier.VerifyIL("Class1.Main", @"{ -// Code size 213 (0xd5) - .maxstack 3 + // Code size 225 (0xe1) + .maxstack 4 .locals init (Windows.ApplicationModel.DataTransfer.DataPackagePropertySet V_0, //dpps object V_1, //tv2 System.Collections.Generic.IEnumerator V_2, //valsEnumerator @@ -305,21 +305,27 @@ .locals init (Windows.ApplicationModel.DataTransfer.DataPackagePropertySet V_0, IL_009c: callvirt ""System.Collections.Generic.ICollection System.Collections.Generic.IDictionary.Keys.get"" IL_00a1: callvirt ""System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator()"" IL_00a6: stloc.3 - IL_00a7: br.s IL_00c4 + IL_00a7: br.s IL_00d0 IL_00a9: call ""System.IO.TextWriter System.Console.Out.get"" IL_00ae: ldloc.3 IL_00af: callvirt ""string System.Collections.Generic.IEnumerator.Current.get"" IL_00b4: ldloc.2 IL_00b5: callvirt ""object System.Collections.Generic.IEnumerator.Current.get"" - IL_00ba: call ""string string.Concat(object, object)"" - IL_00bf: callvirt ""void System.IO.TextWriter.WriteLine(string)"" - IL_00c4: ldloc.3 - IL_00c5: callvirt ""bool System.Collections.IEnumerator.MoveNext()"" - IL_00ca: brfalse.s IL_00d4 - IL_00cc: ldloc.2 - IL_00cd: callvirt ""bool System.Collections.IEnumerator.MoveNext()"" - IL_00d2: brtrue.s IL_00a9 - IL_00d4: ret + IL_00ba: dup + IL_00bb: brtrue.s IL_00c1 + IL_00bd: pop + IL_00be: ldnull + IL_00bf: br.s IL_00c6 + IL_00c1: callvirt ""string object.ToString()"" + IL_00c6: call ""string string.Concat(string, string)"" + IL_00cb: callvirt ""void System.IO.TextWriter.WriteLine(string)"" + IL_00d0: ldloc.3 + IL_00d1: callvirt ""bool System.Collections.IEnumerator.MoveNext()"" + IL_00d6: brfalse.s IL_00e0 + IL_00d8: ldloc.2 + IL_00d9: callvirt ""bool System.Collections.IEnumerator.MoveNext()"" + IL_00de: brtrue.s IL_00a9 + IL_00e0: ret }"); } diff --git a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/LocalsTests.cs b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/LocalsTests.cs index 254a57478fb04..aaae319ca3fd6 100644 --- a/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/LocalsTests.cs +++ b/src/ExpressionEvaluator/CSharp/Test/ExpressionCompiler/LocalsTests.cs @@ -3535,6 +3535,7 @@ from x in args { // Code size 7 (0x7) .maxstack 1 + .locals init (int V_0) IL_0000: ldarg.1 IL_0001: ldfld ""int <>f__AnonymousType1<<>f__AnonymousType0, int>.i__Field"" IL_0006: ret @@ -3544,6 +3545,7 @@ .maxstack 1 { // Code size 12 (0xc) .maxstack 1 + .locals init (int V_0) IL_0000: ldarg.1 IL_0001: ldfld ""<>f__AnonymousType0 <>f__AnonymousType1<<>f__AnonymousType0, int>.<<>h__TransparentIdentifier0>i__Field"" IL_0006: ldfld ""string <>f__AnonymousType0.i__Field"" @@ -3554,6 +3556,7 @@ .maxstack 1 { // Code size 12 (0xc) .maxstack 1 + .locals init (int V_0) IL_0000: ldarg.1 IL_0001: ldfld ""<>f__AnonymousType0 <>f__AnonymousType1<<>f__AnonymousType0, int>.<<>h__TransparentIdentifier0>i__Field"" IL_0006: ldfld ""string <>f__AnonymousType0.i__Field"" @@ -3620,6 +3623,7 @@ from x in args { // Code size 2 (0x2) .maxstack 1 + .locals init (int V_0) IL_0000: ldarg.1 IL_0001: ret } @@ -3628,6 +3632,7 @@ .maxstack 1 { // Code size 12 (0xc) .maxstack 1 + .locals init (int V_0) IL_0000: ldarg.0 IL_0001: ldfld ""<>f__AnonymousType1<<>f__AnonymousType0, int> C.<>c__DisplayClass0_0.<>h__TransparentIdentifier1"" IL_0006: ldfld ""int <>f__AnonymousType1<<>f__AnonymousType0, int>.i__Field"" @@ -3638,6 +3643,7 @@ .maxstack 1 { // Code size 17 (0x11) .maxstack 1 + .locals init (int V_0) IL_0000: ldarg.0 IL_0001: ldfld ""<>f__AnonymousType1<<>f__AnonymousType0, int> C.<>c__DisplayClass0_0.<>h__TransparentIdentifier1"" IL_0006: ldfld ""<>f__AnonymousType0 <>f__AnonymousType1<<>f__AnonymousType0, int>.<<>h__TransparentIdentifier0>i__Field"" @@ -3649,6 +3655,7 @@ .maxstack 1 { // Code size 17 (0x11) .maxstack 1 + .locals init (int V_0) IL_0000: ldarg.0 IL_0001: ldfld ""<>f__AnonymousType1<<>f__AnonymousType0, int> C.<>c__DisplayClass0_0.<>h__TransparentIdentifier1"" IL_0006: ldfld ""<>f__AnonymousType0 <>f__AnonymousType1<<>f__AnonymousType0, int>.<<>h__TransparentIdentifier0>i__Field""