File: CodeGen\CodeGenStringConcat.cs
Web Access
Project: src\src\Compilers\CSharp\Test\Emit\Microsoft.CodeAnalysis.CSharp.Emit.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.Emit.UnitTests)
// 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<int>();
    }
 
    private static void TestMethod<T>()
    {
        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<T>()", @"
{
  // 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<Exception, Exception>();
    }
 
    private static void TestMethod<T, U>() 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<T, U>()", @"
{
  // 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<string>(""F"");
        p1.Print(""P"");
        p1.Print(null);
        var p2 = new Printer<string>(null);
        p2.Print(""P"");
        p2.Print(null);
        var p3 = new Printer<MutableStruct>(new MutableStruct());
        MutableStruct m = new MutableStruct();
        p3.Print(m);
        p3.Print(m);
    }
}
 
class Printer<T>
{
    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<T>.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<T>.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<T>.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<string>(""F"");
        p1.Print(""P"");
        p1.Print(null);
        var p2 = new Printer<string>(null);
        p2.Print(""P"");
        p2.Print(null);
    }
}
 
class Printer<T> 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<T>.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<T>.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<T>.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<MutableStruct>(new MutableStruct());
        p1.Print(m);
        p1.Print(m);
    }
}
 
class Printer<T> 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<T>.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<T>.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<T>.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<string> 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.<Main>b__0()""
  IL_0016:  newobj     ""System.Func<string>..ctor(object, System.IntPtr)""
  IL_001b:  callvirt   ""string System.Func<string>.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<Mutable> 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<T1, T2, T3>(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<T1, T2, T3>", @"
{
  // 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
}");
        }
    }
}