File: CodeGen\CodeGenRefConditionalOperatorTests.cs
Web Access
Project: ..\..\..\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 Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen
{
    [CompilerTrait(CompilerFeature.RefConditionalOperator)]
    public class CodeGenRefConditionalOperatorTests : CSharpTestBase
    {
        [Fact]
        public void TestRefConditionalOperatorInRefReturn()
        {
            var source = @"
class C
{
    static void Main()
    {
        System.Console.Write(Test1(true));
        System.Console.Write(Test2(false));
    }
 
    static int val1 = 33;
    static int val2 = 44;
 
    static ref int Test1(bool b)
    {
        return ref b ? ref val1 : ref val2;
    }
 
    static ref int Test2(bool b) => ref b ? ref val1 : ref val2;
 
}";
            var comp = CompileAndVerify(source, expectedOutput: "3344");
            comp.VerifyDiagnostics();
 
            comp.VerifyIL("C.Test1", @"
{
  // Code size       15 (0xf)
  .maxstack  1
  IL_0000:  ldarg.0
  IL_0001:  brtrue.s   IL_0009
  IL_0003:  ldsflda    ""int C.val2""
  IL_0008:  ret
  IL_0009:  ldsflda    ""int C.val1""
  IL_000e:  ret
}
");
 
            comp.VerifyIL("C.Test2", @"
{
  // Code size       15 (0xf)
  .maxstack  1
  IL_0000:  ldarg.0
  IL_0001:  brtrue.s   IL_0009
  IL_0003:  ldsflda    ""int C.val2""
  IL_0008:  ret
  IL_0009:  ldsflda    ""int C.val1""
  IL_000e:  ret
}
");
 
        }
 
        [Fact]
        public void TestRefConditionalOperatorInRefArgument()
        {
            var source = @"
class C
{
    static void Main()
    {
        bool b = false;
        System.Console.Write(
                            M1(ref b? 
                                   ref val1: 
                                   ref val2));
    }
 
    static int val1 = 33;
    static int val2 = 44;
 
    static ref int M1(ref int arg) => ref arg;
 
}";
            var comp = CompileAndVerify(source, expectedOutput: "44", verify: Verification.Fails);
            comp.VerifyDiagnostics();
 
            comp.VerifyIL("C.Main", @"
{
  // Code size       27 (0x1b)
  .maxstack  1
  IL_0000:  ldc.i4.0
  IL_0001:  brtrue.s   IL_000a
  IL_0003:  ldsflda    ""int C.val2""
  IL_0008:  br.s       IL_000f
  IL_000a:  ldsflda    ""int C.val1""
  IL_000f:  call       ""ref int C.M1(ref int)""
  IL_0014:  ldind.i4
  IL_0015:  call       ""void System.Console.Write(int)""
  IL_001a:  ret
}
");
        }
 
        [Fact]
        public void TestRefConditionalOperatorAsValue()
        {
            var source = @"
class C
{
    static void Main()
    {
        bool b = false;
 
        System.Console.Write(b? ref val1: ref val2);
    }
 
    static int val1 = 33;
    static int val2 = 44;
}";
            var comp = CompileAndVerify(source, expectedOutput: "44", verify: Verification.Passes);
            comp.VerifyDiagnostics();
 
            comp.VerifyIL("C.Main", @"
{
  // Code size       21 (0x15)
  .maxstack  1
  IL_0000:  ldc.i4.0
  IL_0001:  brtrue.s   IL_000a
  IL_0003:  ldsfld     ""int C.val2""
  IL_0008:  br.s       IL_000f
  IL_000a:  ldsfld     ""int C.val1""
  IL_000f:  call       ""void System.Console.Write(int)""
  IL_0014:  ret
}
");
        }
 
        [Fact]
        public void TestRefConditionalOperatorAssignmentTarget()
        {
            var source = @"
class C
{
    static void Main()
    {
        bool b = false;
 
        (b? ref val1: ref val2) = 55;
 
        System.Console.Write(val2);
    }
 
    static int val1 = 33;
    static int val2 = 44;
}";
            var comp = CompileAndVerify(source, expectedOutput: "55", verify: Verification.Passes);
            comp.VerifyDiagnostics();
 
            comp.VerifyIL("C.Main", @"
{
  // Code size       29 (0x1d)
  .maxstack  2
  IL_0000:  ldc.i4.0
  IL_0001:  brtrue.s   IL_000a
  IL_0003:  ldsflda    ""int C.val2""
  IL_0008:  br.s       IL_000f
  IL_000a:  ldsflda    ""int C.val1""
  IL_000f:  ldc.i4.s   55
  IL_0011:  stind.i4
  IL_0012:  ldsfld     ""int C.val2""
  IL_0017:  call       ""void System.Console.Write(int)""
  IL_001c:  ret
}
");
        }
 
        [Fact]
        public void TestRefConditionalOperatorAssignmentTargetUsed()
        {
            var source = @"
class C
{
    static void Main()
    {
        bool b = false;
         System.Console.Write((b? ref val1: ref val2) = 55);
 
        System.Console.Write(val2);
    }
 
    static int val1 = 33;
    static int val2 = 44;
}";
            var comp = CompileAndVerify(source, expectedOutput: "5555", verify: Verification.Passes);
            comp.VerifyDiagnostics();
 
            comp.VerifyIL("C.Main", @"
{
  // Code size       37 (0x25)
  .maxstack  3
  .locals init (int V_0)
  IL_0000:  ldc.i4.0
  IL_0001:  brtrue.s   IL_000a
  IL_0003:  ldsflda    ""int C.val2""
  IL_0008:  br.s       IL_000f
  IL_000a:  ldsflda    ""int C.val1""
  IL_000f:  ldc.i4.s   55
  IL_0011:  dup
  IL_0012:  stloc.0
  IL_0013:  stind.i4
  IL_0014:  ldloc.0
  IL_0015:  call       ""void System.Console.Write(int)""
  IL_001a:  ldsfld     ""int C.val2""
  IL_001f:  call       ""void System.Console.Write(int)""
  IL_0024:  ret
}
");
        }
 
        [Fact]
        public void TestRefConditionalOperatorIncrement()
        {
            var source = @"
class C
{
    static void Main()
    {
        bool b = false;
        (b? ref val1: ref M1(ref val2)) ++;
        (b? ref val1: ref M1(ref val2)) += 22;
 
        System.Console.Write(val2);
    }
 
    static int val1 = 33;
    static int val2 = 44;
 
    static ref int M1(ref int arg) => ref arg;
 
}";
            var comp = CompileAndVerify(source, expectedOutput: "67", verify: Verification.Fails);
            comp.VerifyDiagnostics();
 
            comp.VerifyIL("C.Main", @"
{
  // Code size       62 (0x3e)
  .maxstack  4
  IL_0000:  ldc.i4.0
  IL_0001:  dup
  IL_0002:  brtrue.s   IL_0010
  IL_0004:  ldsflda    ""int C.val2""
  IL_0009:  call       ""ref int C.M1(ref int)""
  IL_000e:  br.s       IL_0015
  IL_0010:  ldsflda    ""int C.val1""
  IL_0015:  dup
  IL_0016:  ldind.i4
  IL_0017:  ldc.i4.1
  IL_0018:  add
  IL_0019:  stind.i4
  IL_001a:  brtrue.s   IL_0028
  IL_001c:  ldsflda    ""int C.val2""
  IL_0021:  call       ""ref int C.M1(ref int)""
  IL_0026:  br.s       IL_002d
  IL_0028:  ldsflda    ""int C.val1""
  IL_002d:  dup
  IL_002e:  ldind.i4
  IL_002f:  ldc.i4.s   22
  IL_0031:  add
  IL_0032:  stind.i4
  IL_0033:  ldsfld     ""int C.val2""
  IL_0038:  call       ""void System.Console.Write(int)""
  IL_003d:  ret
}
");
        }
 
        [Fact]
        public void TestRefConditionalOperatorIncrementUsed()
        {
            var source = @"
class C
{
    static void Main()
    {
        bool b = false;
        System.Console.Write((b? ref val1: ref val2) ++);
        System.Console.Write((b? ref val1: ref val2) += 22);
 
        System.Console.Write(val2);
    }
 
    static int val1 = 33;
    static int val2 = 44;
}";
            var comp = CompileAndVerify(source, expectedOutput: "446767", verify: Verification.Passes);
            comp.VerifyDiagnostics();
 
            comp.VerifyIL("C.Main", @"
{
  // Code size       68 (0x44)
  .maxstack  4
  .locals init (int V_0)
  IL_0000:  ldc.i4.0
  IL_0001:  dup
  IL_0002:  brtrue.s   IL_000b
  IL_0004:  ldsflda    ""int C.val2""
  IL_0009:  br.s       IL_0010
  IL_000b:  ldsflda    ""int C.val1""
  IL_0010:  dup
  IL_0011:  ldind.i4
  IL_0012:  stloc.0
  IL_0013:  ldloc.0
  IL_0014:  ldc.i4.1
  IL_0015:  add
  IL_0016:  stind.i4
  IL_0017:  ldloc.0
  IL_0018:  call       ""void System.Console.Write(int)""
  IL_001d:  brtrue.s   IL_0026
  IL_001f:  ldsflda    ""int C.val2""
  IL_0024:  br.s       IL_002b
  IL_0026:  ldsflda    ""int C.val1""
  IL_002b:  dup
  IL_002c:  ldind.i4
  IL_002d:  ldc.i4.s   22
  IL_002f:  add
  IL_0030:  dup
  IL_0031:  stloc.0
  IL_0032:  stind.i4
  IL_0033:  ldloc.0
  IL_0034:  call       ""void System.Console.Write(int)""
  IL_0039:  ldsfld     ""int C.val2""
  IL_003e:  call       ""void System.Console.Write(int)""
  IL_0043:  ret
}
");
        }
 
        [Fact]
        public void TestRefConditionalOperatorInRefAssignment()
        {
            var source = @"
class C
{
    static void Main()
    {
        bool b = true;
        int x = 1;
        int y = 2;
 
        ref var local = ref b ? ref x : ref y;
 
        System.Console.WriteLine(local);
    }
}";
            var comp = CompileAndVerify(source, expectedOutput: "1");
            comp.VerifyDiagnostics();
            comp.VerifyIL("C.Main", @"
{
  // Code size       20 (0x14)
  .maxstack  2
  .locals init (int V_0, //x
                int V_1) //y
  IL_0000:  ldc.i4.1
  IL_0001:  ldc.i4.1
  IL_0002:  stloc.0
  IL_0003:  ldc.i4.2
  IL_0004:  stloc.1
  IL_0005:  brtrue.s   IL_000b
  IL_0007:  ldloca.s   V_1
  IL_0009:  br.s       IL_000d
  IL_000b:  ldloca.s   V_0
  IL_000d:  ldind.i4
  IL_000e:  call       ""void System.Console.WriteLine(int)""
  IL_0013:  ret
}
");
        }
 
        [Fact]
        public void TestRefConditionalOperatorElvis()
        {
            var source = @"
 
class Program
{
    interface IDisposable1
    {
        void Dispose();
    }
 
    class C1 : IDisposable1
    {
        private bool disposed;
 
        public void Dispose()
        {
            System.Console.WriteLine(disposed);
            disposed = true;
        }
    }
 
    struct S1 : IDisposable1
    {
        private bool disposed;
 
        public void Dispose()
        {
            System.Console.WriteLine(disposed);
            disposed = true;
        }
    }
 
    static void Main(string[] args)
    {
        C1 c = new C1();
        Test(ref c, ref c);
 
        S1 s = new S1();
        Test(ref s, ref s);
    }
 
    static void Test<T>(ref T x, ref T y) where T : IDisposable1
    {
        bool b = true;
        (b? ref x: ref y)?.Dispose();
        (!b? ref x: ref y)?.Dispose();
    }
}";
            var comp = CompileAndVerify(source, expectedOutput: @"False
True
False
True");
            comp.VerifyDiagnostics();
            comp.VerifyIL("Program.Test<T>(ref T, ref T)", @"
{
  // Code size      106 (0x6a)
  .maxstack  3
  .locals init (T V_0)
  IL_0000:  ldc.i4.1
  IL_0001:  dup
  IL_0002:  brtrue.s   IL_0007
  IL_0004:  ldarg.1
  IL_0005:  br.s       IL_0008
  IL_0007:  ldarg.0
  IL_0008:  ldloca.s   V_0
  IL_000a:  initobj    ""T""
  IL_0010:  ldloc.0
  IL_0011:  box        ""T""
  IL_0016:  brtrue.s   IL_002b
  IL_0018:  ldobj      ""T""
  IL_001d:  stloc.0
  IL_001e:  ldloca.s   V_0
  IL_0020:  ldloc.0
  IL_0021:  box        ""T""
  IL_0026:  brtrue.s   IL_002b
  IL_0028:  pop
  IL_0029:  br.s       IL_0036
  IL_002b:  constrained. ""T""
  IL_0031:  callvirt   ""void Program.IDisposable1.Dispose()""
  IL_0036:  brfalse.s  IL_003b
  IL_0038:  ldarg.1
  IL_0039:  br.s       IL_003c
  IL_003b:  ldarg.0
  IL_003c:  ldloca.s   V_0
  IL_003e:  initobj    ""T""
  IL_0044:  ldloc.0
  IL_0045:  box        ""T""
  IL_004a:  brtrue.s   IL_005e
  IL_004c:  ldobj      ""T""
  IL_0051:  stloc.0
  IL_0052:  ldloca.s   V_0
  IL_0054:  ldloc.0
  IL_0055:  box        ""T""
  IL_005a:  brtrue.s   IL_005e
  IL_005c:  pop
  IL_005d:  ret
  IL_005e:  constrained. ""T""
  IL_0064:  callvirt   ""void Program.IDisposable1.Dispose()""
  IL_0069:  ret
}
");
        }
 
        [Fact]
        public void TestRefConditionalAsyncIncrement()
        {
            var source = @"
using System.Threading.Tasks;
 
class C
{
    static void Main()
    {
        Test().Wait();
        System.Console.Write(val1);
    }
 
    static async Task<int> Test()
    {
        bool b = true;
 
        (b? ref val1: ref val2) += await One();
 
        return 1;
    }
 
    static int val1 = 33;
    static int val2 = 44;
 
    static ref int M1() => ref val1;
 
    static async Task<int> One()
    {
        await Task.Yield();
 
        return 1;
    }
 
}";
            var comp = CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe);
 
            comp.VerifyEmitDiagnostics(
                // (16,10): error CS8325: 'await' cannot be used in an expression containing a ref conditional operator
                //         (b? ref val1: ref val2) += await One();
                Diagnostic(ErrorCode.ERR_RefConditionalAndAwait, "b? ref val1: ref val2").WithLocation(16, 10)
                );
        }
 
        [Fact]
        public void TestRefConditionalAsyncAssign()
        {
            var source = @"
using System.Threading.Tasks;
 
class C
{
    static void Main()
    {
        Test().Wait();
        System.Console.Write(val1);
    }
 
    static async Task<int> Test()
    {
        bool b = true;
 
        (b? ref val1: ref val2) = await One();
 
        return 1;
    }
 
    static int val1 = 33;
    static int val2 = 44;
 
    static ref int M1() => ref val1;
 
    static async Task<int> One()
    {
        await Task.Yield();
 
        return 1;
    }
 
}";
            var comp = CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe);
 
            comp.VerifyEmitDiagnostics(
                // (16,10): error CS8325: 'await' cannot be used in an expression containing a ref conditional operator
                //         (b? ref val1: ref val2) = await One();
                Diagnostic(ErrorCode.ERR_RefConditionalAndAwait, "b? ref val1: ref val2").WithLocation(16, 10)
               );
        }
 
        [Fact]
        public void TestRefConditionalOneRef()
        {
            var source = @"
class C
{
    static void Main()
    {
        bool b = false;
 
        System.Console.Write(b? val1: ref val2);
        System.Console.Write(b? ref val1: val2);
    }
 
    static int val1 = 33;
    static int val2 = 44;
}
";
            var comp = CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe);
 
            comp.VerifyEmitDiagnostics(
                // (8,43): error CS8326: Both conditional operator values must be ref values or neither may be a ref value
                //         System.Console.Write(b? val1: ref val2);
                Diagnostic(ErrorCode.ERR_RefConditionalNeedsTwoRefs, "val2").WithLocation(8, 43),
                // (9,37): error CS8326: Both conditional operator values must be ref values or neither may be a ref value
                //         System.Console.Write(b? ref val1: val2);
                Diagnostic(ErrorCode.ERR_RefConditionalNeedsTwoRefs, "val1").WithLocation(9, 37)
               );
        }
 
        [Fact]
        public void TestRefConditionalRValue()
        {
            var source = @"
class C
{
    static void Main()
    {
        bool b = false;
 
        (b? ref val1: ref 42) = 1;
 
        ref var local = ref b? ref val1: ref 42;
 
    }
 
    static int val1 = 33;
}
";
            var comp = CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe);
 
            comp.VerifyEmitDiagnostics(
                // (8,27): error CS8156: An expression cannot be used in this context because it may not be returned by reference
                //         (b? ref val1: ref 42) = 1;
                Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "42").WithLocation(8, 27),
                // (10,46): error CS8156: An expression cannot be used in this context because it may not be returned by reference
                //         ref var local = ref b? ref val1: ref 42;
                Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "42").WithLocation(10, 46)
               );
        }
 
        [Fact]
        [WorkItem(24306, "https://github.com/dotnet/roslyn/issues/24306")]
        public void TestRefConditional_71()
        {
            var source = @"
class C
{
    static void Main()
    {
 
    }
 
    void Test()
    {
        int local1 = 1;
        int local2 = 2;
        bool b = true;
 
        ref int r = ref b? ref local1: ref local2;
    }
}
";
            var comp = CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular7_1);
 
            comp.VerifyEmitDiagnostics(
                // (15,25): error CS8302: Feature 'ref conditional expression' is not available in C# 7.1. Please use language version 7.2 or greater.
                //         ref int r = ref b? ref local1: ref local2;
                Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_1, "b? ref local1: ref local2").WithArguments("ref conditional expression", "7.2").WithLocation(15, 25)
               );
        }
 
        [Fact]
        public void TestRefConditionalUnsafeToReturn1()
        {
            var source = @"
class C
{
    static void Main()
    {
 
    }
 
    ref int Test()
    {
        int local1 = 1;
        int local2 = 2;
        bool b = true;
 
        return ref b? ref local1: ref local2;
    }
}
";
            var comp = CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe);
 
            comp.VerifyEmitDiagnostics(
                // (15,27): error CS8168: Cannot return local 'local1' by reference because it is not a ref local
                //         return ref b? ref local1: ref local2;
                Diagnostic(ErrorCode.ERR_RefReturnLocal, "local1").WithArguments("local1").WithLocation(15, 27)
               );
        }
 
        [Fact]
        public void TestRefConditionalUnsafeToReturn2()
        {
            var source = @"
class C
{
    static void Main()
    {
 
    }
 
    ref int Test()
    {
        int local2 = 1;
        bool b = true;
 
        return ref b? ref val1: ref local2;
    }
 
    static int val1 = 33;
}
";
            var comp = CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe);
 
            comp.VerifyEmitDiagnostics(
                // (14,37): error CS8168: Cannot return local 'local2' by reference because it is not a ref local
                //         return ref b? ref val1: ref local2;
                Diagnostic(ErrorCode.ERR_RefReturnLocal, "local2").WithArguments("local2").WithLocation(14, 37)
               );
        }
 
        [Fact]
        public void TestRefConditionalUnsafeToReturn3()
        {
            var source = @"
class C
{
    static void Main()
    {
 
    }
 
    ref int Test()
    {
        S1 local2 = default(S1);
        bool b = true;
 
        return ref (b? ref val1: ref local2).x;
    }
 
    static S1 val1;
 
    struct S1
    {
        public int x;
    }
}
";
            var comp = CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe);
 
            comp.VerifyEmitDiagnostics(
                // (14,38): error CS8168: Cannot return local 'local2' by reference because it is not a ref local
                //         return ref (b? ref val1: ref local2).x;
                Diagnostic(ErrorCode.ERR_RefReturnLocal, "local2").WithArguments("local2").WithLocation(14, 38)
               );
        }
 
        [Fact]
        public void TestRefConditionalUnsafeToReturn4()
        {
            var source = @"
class C
{
    static void Main()
    {
 
    }
 
    ref int Test()
    {
        S1 local2 = default(S1);
        bool b = true;
 
        ref var temp = ref (b? ref val1: ref local2).x;
        return ref temp;
    }
 
    static S1 val1;
 
    struct S1
    {
        public int x;
    }
}
";
            var comp = CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe);
 
            comp.VerifyEmitDiagnostics(
                // (15,20): error CS8157: Cannot return 'temp' by reference because it was initialized to a value that cannot be returned by reference
                //         return ref temp;
                Diagnostic(ErrorCode.ERR_RefReturnNonreturnableLocal, "temp").WithArguments("temp").WithLocation(15, 20)
               );
        }
 
        [Fact]
        public void TestRefConditionalSafeToReturn1()
        {
            var source = @"
class C
{
    static void Main()
    {
        Test() ++;
        System.Console.WriteLine(val1.x);
    }
 
    static ref int Test()
    {
        bool b = true;
 
        return ref (b? ref val1: ref val2).x;
    }
 
    static S1 val1;
    static S1 val2;
 
    struct S1
    {
        public int x;
    }
}
";
            var comp = CompileAndVerify(source, expectedOutput: "1", verify: Verification.Passes);
            comp.VerifyDiagnostics();
 
            comp.VerifyIL("C.Test", @"
{
  // Code size       21 (0x15)
  .maxstack  1
  IL_0000:  ldc.i4.1
  IL_0001:  brtrue.s   IL_000a
  IL_0003:  ldsflda    ""C.S1 C.val2""
  IL_0008:  br.s       IL_000f
  IL_000a:  ldsflda    ""C.S1 C.val1""
  IL_000f:  ldflda     ""int C.S1.x""
  IL_0014:  ret
}
");
        }
 
        [Fact]
        public void TestRefConditionalSafeToReturn2()
        {
            var source = @"
class C
{
    static void Main()
    {
        Test() ++;
        System.Console.WriteLine(val1.x);
    }
 
    static ref int Test()
    {
        return ref (true? ref val1: ref val2).x;
    }
 
    static S1 val1;
    static S1 val2;
 
    struct S1
    {
        public int x;
    }
}
";
            var comp = CompileAndVerify(source, expectedOutput: "1", verify: Verification.Passes);
            comp.VerifyDiagnostics();
 
            comp.VerifyIL("C.Test", @"
{
  // Code size       11 (0xb)
  .maxstack  1
  IL_0000:  ldsflda    ""C.S1 C.val1""
  IL_0005:  ldflda     ""int C.S1.x""
  IL_000a:  ret
}
");
        }
 
        [Fact]
        public void TestRefConditionalSafeToReturn3()
        {
            var source = @"
class C
{
    static void Main()
    {
        (false? ref val1: ref val2) = (true? 1: val2);
        (true? ref val1: ref val2) = (false? ref val1: ref val2);
        System.Console.WriteLine(val1);
    }
 
    static int val1;
    static int val2;
}
";
            var comp = CompileAndVerify(source, expectedOutput: "1", verify: Verification.Passes);
            comp.VerifyDiagnostics();
 
            comp.VerifyIL("C.Main()", @"
{
  // Code size       27 (0x1b)
  .maxstack  1
  IL_0000:  ldc.i4.1
  IL_0001:  stsfld     ""int C.val2""
  IL_0006:  ldsfld     ""int C.val2""
  IL_000b:  stsfld     ""int C.val1""
  IL_0010:  ldsfld     ""int C.val1""
  IL_0015:  call       ""void System.Console.WriteLine(int)""
  IL_001a:  ret
}
");
        }
 
        [Fact]
        public void TestRefConditionalDifferentTypes1()
        {
            var source = @"
class C
{
    static void Main()
    {
        bool b = false;
 
        System.Console.Write(b? ref val1: ref val2);
    }
 
    static int val1 = 33;
    static short val2 = 44;
}
";
            var comp = CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe);
 
            comp.VerifyEmitDiagnostics(
                // (8,47): error CS8327: The expression must be of type 'int' to match the alternative ref value
                //         System.Console.Write(b? ref val1: ref val2);
                Diagnostic(ErrorCode.ERR_RefConditionalDifferentTypes, "val2").WithArguments("int").WithLocation(8, 47)
               );
        }
 
        [Fact]
        public void TestRefConditionalDifferentTypes2()
        {
            var source = @"
class C
{
    static void Main()
    {
        bool b = false;
 
        System.Console.Write(b? ref val1: ref ()=>1);
    }
 
    static System.Func<int> val1 = null;
}
";
            var comp = CreateCompilationWithMscorlib45(source, options: TestOptions.ReleaseExe);
 
            comp.VerifyEmitDiagnostics(
                // (8,47): error CS8156: An expression cannot be used in this context because it may not be returned by reference
                //         System.Console.Write(b? ref val1: ref ()=>1);
                Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "()=>1").WithLocation(8, 47)
               );
        }
 
        [Fact]
        public void TestRefConditionalDifferentTypes3()
        {
            var source = @"
class C
{
    static void Main()
    {
        bool b = true;
 
        ref var x = ref b? ref val1: ref val2;
        System.Console.Write(x.Alice);
    }
 
    static (int Alice, int) val1 = (1,2);
    static (int Alice, int Bob) val2 = (3,4);
}
";
 
            var comp = CompileAndVerify(source, expectedOutput: "1");
            comp.VerifyDiagnostics();
 
            comp.VerifyIL("C.Main", @"
{
  // Code size       26 (0x1a)
  .maxstack  1
  IL_0000:  ldc.i4.1
  IL_0001:  brtrue.s   IL_000a
  IL_0003:  ldsflda    ""System.ValueTuple<int, int> C.val2""
  IL_0008:  br.s       IL_000f
  IL_000a:  ldsflda    ""System.ValueTuple<int, int> C.val1""
  IL_000f:  ldfld      ""int System.ValueTuple<int, int>.Item1""
  IL_0014:  call       ""void System.Console.Write(int)""
  IL_0019:  ret
}
");
        }
 
        [Fact]
        public void TestRefConditionalHomelessBranches()
        {
            var source = @"
    class Program
    {
        static void Main(string[] args)
        {
            var o = new C1();
            var o1 = new C1();
 
            (args != null ? ref o.field : ref o1.field).ToString();
            System.Console.Write(o.field.value);
 
            // no copying expected
            (args != null ? ref o.field : ref o1.field).RoExtension();
            System.Console.Write(o.field.value);
        }
    }
 
    class C1
    {
        public readonly S1 field;
    }
 
    public struct S1
    {
        public int value;
 
        public override string ToString()
        {
            value = 42;
            return base.ToString();
        }
    }
 
    public static class S1Ext
    {
        public static void RoExtension(in this S1 self)
        {
            // do nothing
        }
    }
";
 
            // PEVerify: Cannot change initonly field outside its .ctor.
            var comp = CompileAndVerifyWithMscorlib40(source, references: new[] { TestMetadata.Net40.System, ValueTupleRef, TestMetadata.Net40.SystemCore }, expectedOutput: "00", verify: Verification.FailsPEVerify);
            comp.VerifyDiagnostics();
 
            comp.VerifyIL("Program.Main", @"
{
  // Code size       99 (0x63)
  .maxstack  1
  .locals init (C1 V_0, //o
                C1 V_1, //o1
                S1 V_2)
  IL_0000:  newobj     ""C1..ctor()""
  IL_0005:  stloc.0
  IL_0006:  newobj     ""C1..ctor()""
  IL_000b:  stloc.1
  IL_000c:  ldarg.0
  IL_000d:  brtrue.s   IL_0017
  IL_000f:  ldloc.1
  IL_0010:  ldfld      ""S1 C1.field""
  IL_0015:  br.s       IL_001d
  IL_0017:  ldloc.0
  IL_0018:  ldfld      ""S1 C1.field""
  IL_001d:  stloc.2
  IL_001e:  ldloca.s   V_2
  IL_0020:  constrained. ""S1""
  IL_0026:  callvirt   ""string object.ToString()""
  IL_002b:  pop
  IL_002c:  ldloc.0
  IL_002d:  ldflda     ""S1 C1.field""
  IL_0032:  ldfld      ""int S1.value""
  IL_0037:  call       ""void System.Console.Write(int)""
  IL_003c:  ldarg.0
  IL_003d:  brtrue.s   IL_0047
  IL_003f:  ldloc.1
  IL_0040:  ldflda     ""S1 C1.field""
  IL_0045:  br.s       IL_004d
  IL_0047:  ldloc.0
  IL_0048:  ldflda     ""S1 C1.field""
  IL_004d:  call       ""void S1Ext.RoExtension(in S1)""
  IL_0052:  ldloc.0
  IL_0053:  ldflda     ""S1 C1.field""
  IL_0058:  ldfld      ""int S1.value""
  IL_005d:  call       ""void System.Console.Write(int)""
  IL_0062:  ret
}
");
        }
 
        [Fact]
        public void TestRefConditionalOneHomelessBranch()
        {
            var source = @"
    class Program
    {
        static void Main(string[] args)
        {
            var o = new C1();
 
            Test(true, o);
            Test(false, o);
 
            System.Console.Write(o.field.value);
            System.Console.Write(o.field1.value);
        }
 
        private static void Test(bool flag, C1 o)
        {
            (flag ? ref o.field : ref o.field1).ToString();
        }
    }
 
    class C1
    {
        public readonly S1 field;
        public S1 field1;
    }
 
    struct S1
    {
        public int value;
 
        public override string ToString()
        {
            value = 42;
            return base.ToString();
        }
    }
";
            // PEVerify: Cannot change initonly field outside its .ctor.
            var comp = CompileAndVerify(source, expectedOutput: "00", verify: Verification.FailsPEVerify);
            comp.VerifyDiagnostics();
 
            comp.VerifyIL("Program.Test", @"
{
  // Code size       33 (0x21)
  .maxstack  1
  .locals init (S1 V_0)
  IL_0000:  ldarg.0
  IL_0001:  brtrue.s   IL_000b
  IL_0003:  ldarg.1
  IL_0004:  ldfld      ""S1 C1.field1""
  IL_0009:  br.s       IL_0011
  IL_000b:  ldarg.1
  IL_000c:  ldfld      ""S1 C1.field""
  IL_0011:  stloc.0
  IL_0012:  ldloca.s   V_0
  IL_0014:  constrained. ""S1""
  IL_001a:  callvirt   ""string object.ToString()""
  IL_001f:  pop
  IL_0020:  ret
}
");
 
        }
 
        [Fact]
        public void TestRefConditionalDifferentTypes4()
        {
            var source = @"
class C
{
    static void Main()
    {
        bool b = true;
 
        ref var x = ref b? ref val1: ref val2;
        System.Console.Write(x.Bob);
    }
 
    static (int Alice, int) val1 = (1,2);
    static (int Alice, int Bob) val2 = (3,4);
}
";
 
            var comp = CreateCompilationWithMscorlib45(source, references: new[] { SystemRuntimeFacadeRef, ValueTupleRef }, options: TestOptions.ReleaseExe);
 
            comp.VerifyEmitDiagnostics(
                // (9,32): error CS1061: '(int Alice, int)' does not contain a definition for 'Bob' and no extension method 'Bob' accepting a first argument of type '(int Alice, int)' could be found (are you missing a using directive or an assembly reference?)
                //         System.Console.Write(x.Bob);
                Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "Bob").WithArguments("(int Alice, int)", "Bob").WithLocation(9, 32)
               );
        }
 
        [Fact, WorkItem(53113, "https://github.com/dotnet/roslyn/issues/53113")]
        public void TestRefOnPointerIndirection_ThroughTernary_01()
        {
            var code = @"
using System;
 
unsafe
{
    bool b = true;
    ref int x = ref b ? ref *(int*)0 : ref *(int*)1;
    Console.WriteLine(""run"");
}
";
 
            verify(TestOptions.UnsafeReleaseExe, Verification.Passes, @"
{
  // Code size       22 (0x16)
  .maxstack  1
  IL_0000:  ldc.i4.1
  IL_0001:  brtrue.s   IL_0008
  IL_0003:  ldc.i4.1
  IL_0004:  conv.i
  IL_0005:  pop
  IL_0006:  br.s       IL_000b
  IL_0008:  ldc.i4.0
  IL_0009:  conv.i
  IL_000a:  pop
  IL_000b:  ldstr      ""run""
  IL_0010:  call       ""void System.Console.WriteLine(string)""
  IL_0015:  ret
}
");
 
            verify(TestOptions.UnsafeDebugExe, Verification.Fails, @"
{
  // Code size       26 (0x1a)
  .maxstack  1
  .locals init (bool V_0, //b
                int& V_1) //x
  IL_0000:  nop
  IL_0001:  ldc.i4.1
  IL_0002:  stloc.0
  IL_0003:  ldloc.0
  IL_0004:  brtrue.s   IL_000a
  IL_0006:  ldc.i4.1
  IL_0007:  conv.i
  IL_0008:  br.s       IL_000c
  IL_000a:  ldc.i4.0
  IL_000b:  conv.i
  IL_000c:  stloc.1
  IL_000d:  ldstr      ""run""
  IL_0012:  call       ""void System.Console.WriteLine(string)""
  IL_0017:  nop
  IL_0018:  nop
  IL_0019:  ret
}
");
 
            void verify(CSharpCompilationOptions options, Verification verify, string expectedIL)
            {
                var comp = CreateCompilation(code, options: options);
                var verifier = CompileAndVerify(comp, expectedOutput: "run", verify: verify);
                verifier.VerifyDiagnostics();
                verifier.VerifyIL("<top-level-statements-entry-point>", expectedIL);
            }
        }
 
        [Fact, WorkItem(53113, "https://github.com/dotnet/roslyn/issues/53113")]
        public void TestRefOnPointerIndirection_ThroughTernary_02()
        {
            var code = @"
using System;
 
unsafe
{
    int i1 = 0;
    int* p1 = &i1;
    bool b = true;
    ref int x = ref b ? ref *M(*p1) : ref i1;
    Console.WriteLine(""run"");
 
    int* M(int i)
    {
        Console.Write(i);
        return (int*)0;
    }
}
";
 
            verify(TestOptions.UnsafeReleaseExe, @"
{
  // Code size       28 (0x1c)
  .maxstack  1
  .locals init (int V_0, //i1
                int* V_1) //p1
  IL_0000:  ldc.i4.0
  IL_0001:  stloc.0
  IL_0002:  ldloca.s   V_0
  IL_0004:  conv.u
  IL_0005:  stloc.1
  IL_0006:  ldc.i4.1
  IL_0007:  brfalse.s  IL_0011
  IL_0009:  ldloc.1
  IL_000a:  ldind.i4
  IL_000b:  call       ""int* Program.<<Main>$>g__M|0_0(int)""
  IL_0010:  pop
  IL_0011:  ldstr      ""run""
  IL_0016:  call       ""void System.Console.WriteLine(string)""
  IL_001b:  ret
}
");
 
            verify(TestOptions.UnsafeDebugExe, @"
{
  // Code size       38 (0x26)
  .maxstack  1
  .locals init (int V_0, //i1
                int* V_1, //p1
                bool V_2, //b
                int& V_3) //x
  IL_0000:  nop
  IL_0001:  ldc.i4.0
  IL_0002:  stloc.0
  IL_0003:  ldloca.s   V_0
  IL_0005:  conv.u
  IL_0006:  stloc.1
  IL_0007:  ldc.i4.1
  IL_0008:  stloc.2
  IL_0009:  ldloc.2
  IL_000a:  brtrue.s   IL_0010
  IL_000c:  ldloca.s   V_0
  IL_000e:  br.s       IL_0017
  IL_0010:  ldloc.1
  IL_0011:  ldind.i4
  IL_0012:  call       ""int* Program.<<Main>$>g__M|0_0(int)""
  IL_0017:  stloc.3
  IL_0018:  ldstr      ""run""
  IL_001d:  call       ""void System.Console.WriteLine(string)""
  IL_0022:  nop
  IL_0023:  nop
  IL_0024:  nop
  IL_0025:  ret
}
");
 
            void verify(CSharpCompilationOptions options, string expectedIL)
            {
                var comp = CreateCompilation(code, options: options);
                var verifier = CompileAndVerify(comp, expectedOutput: "0run", verify: Verification.Fails);
                verifier.VerifyDiagnostics();
                verifier.VerifyIL("<top-level-statements-entry-point>", expectedIL);
            }
        }
    }
}