File: CodeGen\CodeGenTupleEqualityTests.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 System.Linq;
using Microsoft.CodeAnalysis.CSharp.Syntax;
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.TupleEquality)]
    public class CodeGenTupleEqualityTests : CSharpTestBase
    {
        [Fact]
        public void TestCSharp7_2()
        {
            var source = @"
class C
{
    static void Main()
    {
        var t = (1, 2);
        System.Console.Write(t == (1, 2));
    }
}";
            var comp = CreateCompilation(source, parseOptions: TestOptions.Regular7_2);
            comp.VerifyDiagnostics(
                // (7,30): error CS8320: Feature 'tuple equality' is not available in C# 7.2. Please use language version 7.3 or greater.
                //         System.Console.Write(t == (1, 2));
                Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_2, "t == (1, 2)").WithArguments("tuple equality", "7.3").WithLocation(7, 30)
                );
        }
 
        [Theory]
        [InlineData("(1, 2)", "(1L, 2L)", true)]
        [InlineData("(1, 2)", "(1, 0)", false)]
        [InlineData("(1, 2)", "(0, 2)", false)]
        [InlineData("(1, 2)", "((long, long))(1, 2)", true)]
        [InlineData("((1, 2L), (3, 4))", "((1L, 2), (3L, 4))", true)]
        [InlineData("((1, 2L), (3, 4))", "((0L, 2), (3L, 4))", false)]
        [InlineData("((1, 2L), (3, 4))", "((1L, 0), (3L, 4))", false)]
        [InlineData("((1, 2L), (3, 4))", "((1L, 0), (0L, 4))", false)]
        [InlineData("((1, 2L), (3, 4))", "((1L, 0), (3L, 0))", false)]
        public void TestSimple(string change1, string change2, bool expectedMatch)
        {
            var sourceTemplate = @"
class C
{
    static void Main()
    {
        var t1 = CHANGE1;
        var t2 = CHANGE2;
        System.Console.Write($""{(t1 == t2) == EXPECTED} {(t1 != t2) != EXPECTED}"");
    }
}";
            string source = sourceTemplate
                .Replace("CHANGE1", change1)
                .Replace("CHANGE2", change2)
                .Replace("EXPECTED", expectedMatch ? "true" : "false");
            string name = GetUniqueName();
            var comp = CreateCompilation(source, options: TestOptions.DebugExe, assemblyName: name);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "True True");
        }
 
        [Fact]
        public void TestTupleLiteralsWithDifferentCardinalities()
        {
            var source = @"
class C
{
    static bool M()
    {
        return (1, 1) == (2, 2, 2);
    }
}";
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (6,16): error CS8373: Tuple types used as operands of an == or != operator must have matching cardinalities. But this operator has tuple types of cardinality 2 on the left and 3 on the right.
                //         return (1, 1) == (2, 2, 2);
                Diagnostic(ErrorCode.ERR_TupleSizesMismatchForBinOps, "(1, 1) == (2, 2, 2)").WithArguments("2", "3").WithLocation(6, 16)
                );
        }
 
        [Fact]
        public void TestTuplesWithDifferentCardinalities()
        {
            var source = @"
class C
{
    static bool M()
    {
        var t1 = (1, 1);
        var t2 = (2, 2, 2);
        return t1 == t2;
    }
}";
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (8,16): error CS8355: Tuple types used as operands of a binary operator must have matching cardinalities. But this operator has tuple types of cardinality 2 on the left and 3 on the right.
                //         return t1 == t2;
                Diagnostic(ErrorCode.ERR_TupleSizesMismatchForBinOps, "t1 == t2").WithArguments("2", "3").WithLocation(8, 16)
                );
        }
 
        [Fact]
        public void TestNestedTuplesWithDifferentCardinalities()
        {
            var source = @"
class C
{
    static bool M()
    {
        var t1 = (1, (1, 1));
        var t2 = (2, (2, 2, 2));
        return t1 == t2;
    }
}";
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (8,16): error CS8355: Tuple types used as operands of a binary operator must have matching cardinalities. But this operator has tuple types of cardinality 2 on the left and 3 on the right.
                //         return t1 == t2;
                Diagnostic(ErrorCode.ERR_TupleSizesMismatchForBinOps, "t1 == t2").WithArguments("2", "3").WithLocation(8, 16)
                );
        }
 
        [Fact, WorkItem(25295, "https://github.com/dotnet/roslyn/issues/25295")]
        public void TestWithoutValueTuple()
        {
            var source = @"
class C
{
    static bool M()
    {
        return (1, 2) == (3, 4);
    }
}";
            var comp = CreateCompilationWithMscorlib40(source);
 
            // https://github.com/dotnet/roslyn/issues/25295
            // Can we relax the requirement on ValueTuple types being found?
 
            comp.VerifyDiagnostics(
                // (6,16): error CS8179: Predefined type 'System.ValueTuple`2' is not defined or imported
                //         return (1, 2) == (3, 4);
                Diagnostic(ErrorCode.ERR_PredefinedValueTupleTypeNotFound, "(1, 2)").WithArguments("System.ValueTuple`2").WithLocation(6, 16),
                // (6,26): error CS8179: Predefined type 'System.ValueTuple`2' is not defined or imported
                //         return (1, 2) == (3, 4);
                Diagnostic(ErrorCode.ERR_PredefinedValueTupleTypeNotFound, "(3, 4)").WithArguments("System.ValueTuple`2").WithLocation(6, 26)
                );
        }
 
        [Fact]
        public void TestNestedNullableTuplesWithDifferentCardinalities()
        {
            var source = @"
class C
{
    static bool M()
    {
        (int, int)? nt = (1, 1);
        var t1 = (1, nt);
        var t2 = (2, (2, 2, 2));
        return t1 == t2;
    }
}";
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (9,16): error CS8373: Tuple types used as operands of a binary operator must have matching cardinalities. But this operator has tuple types of cardinality 2 on the left and 3 on the right.
                //         return t1 == t2;
                Diagnostic(ErrorCode.ERR_TupleSizesMismatchForBinOps, "t1 == t2").WithArguments("2", "3").WithLocation(9, 16)
                );
        }
 
        [Fact]
        public void TestILForSimpleEqual()
        {
            var source = @"
class C
{
    static bool M()
    {
        var t1 = (1, 1);
        var t2 = (2, 2);
        return t1 == t2;
    }
}";
            var comp = CompileAndVerify(source);
            comp.VerifyDiagnostics();
 
            comp.VerifyIL("C.M", @"{
  // Code size       50 (0x32)
  .maxstack  3
  .locals init (System.ValueTuple<int, int> V_0, //t1
                System.ValueTuple<int, int> V_1,
                System.ValueTuple<int, int> V_2)
  IL_0000:  ldloca.s   V_0
  IL_0002:  ldc.i4.1
  IL_0003:  ldc.i4.1
  IL_0004:  call       ""System.ValueTuple<int, int>..ctor(int, int)""
  IL_0009:  ldc.i4.2
  IL_000a:  ldc.i4.2
  IL_000b:  newobj     ""System.ValueTuple<int, int>..ctor(int, int)""
  IL_0010:  ldloc.0
  IL_0011:  stloc.1
  IL_0012:  stloc.2
  IL_0013:  ldloc.1
  IL_0014:  ldfld      ""int System.ValueTuple<int, int>.Item1""
  IL_0019:  ldloc.2
  IL_001a:  ldfld      ""int System.ValueTuple<int, int>.Item1""
  IL_001f:  bne.un.s   IL_0030
  IL_0021:  ldloc.1
  IL_0022:  ldfld      ""int System.ValueTuple<int, int>.Item2""
  IL_0027:  ldloc.2
  IL_0028:  ldfld      ""int System.ValueTuple<int, int>.Item2""
  IL_002d:  ceq
  IL_002f:  ret
  IL_0030:  ldc.i4.0
  IL_0031:  ret
}");
        }
 
        [Fact]
        public void TestILForSimpleNotEqual()
        {
            var source = @"
class C
{
    static bool M((int, int) t1, (int, int) t2)
    {
        return t1 != t2;
    }
}";
            var comp = CompileAndVerify(source);
            comp.VerifyDiagnostics();
 
            comp.VerifyIL("C.M", @"{
  // Code size       38 (0x26)
  .maxstack  2
  .locals init (System.ValueTuple<int, int> V_0,
                System.ValueTuple<int, int> V_1)
  IL_0000:  ldarg.0
  IL_0001:  stloc.0
  IL_0002:  ldarg.1
  IL_0003:  stloc.1
  IL_0004:  ldloc.0
  IL_0005:  ldfld      ""int System.ValueTuple<int, int>.Item1""
  IL_000a:  ldloc.1
  IL_000b:  ldfld      ""int System.ValueTuple<int, int>.Item1""
  IL_0010:  bne.un.s   IL_0024
  IL_0012:  ldloc.0
  IL_0013:  ldfld      ""int System.ValueTuple<int, int>.Item2""
  IL_0018:  ldloc.1
  IL_0019:  ldfld      ""int System.ValueTuple<int, int>.Item2""
  IL_001e:  ceq
  IL_0020:  ldc.i4.0
  IL_0021:  ceq
  IL_0023:  ret
  IL_0024:  ldc.i4.1
  IL_0025:  ret
}");
        }
 
        [Fact]
        public void TestILForSimpleEqualOnInTuple()
        {
            var source = @"
class C
{
    static bool M(in (int, int) t1, in (int, int) t2)
    {
        return t1 == t2;
    }
}";
            var comp = CompileAndVerify(source);
            comp.VerifyDiagnostics();
 
            // note: the logic to save variables and side-effects results in copying the inputs
            comp.VerifyIL("C.M", @"{
  // Code size       45 (0x2d)
  .maxstack  2
  .locals init (System.ValueTuple<int, int> V_0,
                System.ValueTuple<int, int> V_1)
  IL_0000:  ldarg.0
  IL_0001:  ldobj      ""System.ValueTuple<int, int>""
  IL_0006:  stloc.0
  IL_0007:  ldarg.1
  IL_0008:  ldobj      ""System.ValueTuple<int, int>""
  IL_000d:  stloc.1
  IL_000e:  ldloc.0
  IL_000f:  ldfld      ""int System.ValueTuple<int, int>.Item1""
  IL_0014:  ldloc.1
  IL_0015:  ldfld      ""int System.ValueTuple<int, int>.Item1""
  IL_001a:  bne.un.s   IL_002b
  IL_001c:  ldloc.0
  IL_001d:  ldfld      ""int System.ValueTuple<int, int>.Item2""
  IL_0022:  ldloc.1
  IL_0023:  ldfld      ""int System.ValueTuple<int, int>.Item2""
  IL_0028:  ceq
  IL_002a:  ret
  IL_002b:  ldc.i4.0
  IL_002c:  ret
}");
        }
 
        [Fact]
        public void TestILForSimpleEqualOnTupleLiterals()
        {
            var source = @"
class C
{
    static void Main()
    {
        M(1, 1);
        M(1, 2);
        M(2, 1);
    }
    static void M(int x, byte y)
    {
        System.Console.Write($""{(x, x) == (y, y)} "");
    }
}";
            var comp = CompileAndVerify(source, expectedOutput: "True False False");
            comp.VerifyDiagnostics();
 
            comp.VerifyIL("C.M", @"{
// Code size       38 (0x26)
  .maxstack  3
  .locals init (int V_0,
                byte V_1,
                byte V_2)
  IL_0000:  ldstr      ""{0} ""
  IL_0005:  ldarg.0
  IL_0006:  ldarg.0
  IL_0007:  stloc.0
  IL_0008:  ldarg.1
  IL_0009:  stloc.1
  IL_000a:  ldarg.1
  IL_000b:  stloc.2
  IL_000c:  ldloc.1
  IL_000d:  bne.un.s   IL_0015
  IL_000f:  ldloc.0
  IL_0010:  ldloc.2
  IL_0011:  ceq
  IL_0013:  br.s       IL_0016
  IL_0015:  ldc.i4.0
  IL_0016:  box        ""bool""
  IL_001b:  call       ""string string.Format(string, object)""
  IL_0020:  call       ""void System.Console.Write(string)""
  IL_0025:  ret
}");
 
            var tree = comp.Compilation.SyntaxTrees.First();
            var model = comp.Compilation.GetSemanticModel(tree);
 
            // check x
            var tupleX = tree.GetCompilationUnitRoot().DescendantNodes().OfType<TupleExpressionSyntax>().First();
            Assert.Equal("(x, x)", tupleX.ToString());
            Assert.Equal(Conversion.Identity, model.GetConversion(tupleX));
            Assert.Null(model.GetSymbolInfo(tupleX).Symbol);
 
            var lastX = tupleX.Arguments[1].Expression;
            Assert.Equal("x", lastX.ToString());
            Assert.Equal(Conversion.Identity, model.GetConversion(lastX));
            Assert.Equal("System.Int32 x", model.GetSymbolInfo(lastX).Symbol.ToTestDisplayString());
 
            var xSymbol = model.GetTypeInfo(lastX);
            Assert.Equal("System.Int32", xSymbol.Type.ToTestDisplayString());
            Assert.Equal("System.Int32", xSymbol.ConvertedType.ToTestDisplayString());
 
            var tupleXSymbol = model.GetTypeInfo(tupleX);
            Assert.Equal("(System.Int32, System.Int32)", tupleXSymbol.Type.ToTestDisplayString());
            Assert.Equal("(System.Int32, System.Int32)", tupleXSymbol.ConvertedType.ToTestDisplayString());
 
            // check y
            var tupleY = tree.GetCompilationUnitRoot().DescendantNodes().OfType<TupleExpressionSyntax>().Last();
            Assert.Equal("(y, y)", tupleY.ToString());
            Assert.Equal(ConversionKind.ImplicitTupleLiteral, model.GetConversion(tupleY).Kind);
 
            var lastY = tupleY.Arguments[1].Expression;
            Assert.Equal("y", lastY.ToString());
            Assert.Equal(Conversion.ImplicitNumeric, model.GetConversion(lastY));
 
            var ySymbol = model.GetTypeInfo(lastY);
            Assert.Equal("System.Byte", ySymbol.Type.ToTestDisplayString());
            Assert.Equal("System.Int32", ySymbol.ConvertedType.ToTestDisplayString());
 
            var tupleYSymbol = model.GetTypeInfo(tupleY);
            Assert.Equal("(System.Byte, System.Byte)", tupleYSymbol.Type.ToTestDisplayString());
            Assert.Equal("(System.Int32, System.Int32)", tupleYSymbol.ConvertedType.ToTestDisplayString());
        }
 
        [Fact]
        public void TestILForAlwaysValuedNullable()
        {
            var source = @"
class C
{
    static void Main()
    {
        System.Console.Write($""{(new int?(Identity(42)), (int?)2) == (new int?(42), new int?(2))} "");
    }
    static int Identity(int x) => x;
}";
            var comp = CompileAndVerify(source, expectedOutput: "True");
            comp.VerifyDiagnostics();
 
            comp.VerifyIL("C.Main", @"{
  // Code size       39 (0x27)
  .maxstack  3
  IL_0000:  ldstr      ""{0} ""
  IL_0005:  ldc.i4.s   42
  IL_0007:  call       ""int C.Identity(int)""
  IL_000c:  ldc.i4.s   42
  IL_000e:  bne.un.s   IL_0016
  IL_0010:  ldc.i4.2
  IL_0011:  ldc.i4.2
  IL_0012:  ceq
  IL_0014:  br.s       IL_0017
  IL_0016:  ldc.i4.0
  IL_0017:  box        ""bool""
  IL_001c:  call       ""string string.Format(string, object)""
  IL_0021:  call       ""void System.Console.Write(string)""
  IL_0026:  ret
}");
        }
 
        [Fact]
        public void TestILForNullableElementsEqualsToNull()
        {
            var source = @"
class C
{
    static void Main()
    {
        System.Console.Write(M(null, null));
        System.Console.Write(M(1, true));
    }
    static bool M(int? i1, bool? b1)
    {
        var t1 = (i1, b1);
        return t1 == (null, null);
    }
}";
            var comp = CompileAndVerify(source, expectedOutput: "TrueFalse");
            comp.VerifyDiagnostics();
 
            comp.VerifyIL("C.M", @"{
  // Code size       40 (0x28)
  .maxstack  2
  .locals init (System.ValueTuple<int?, bool?> V_0)
  IL_0000:  ldarg.0
  IL_0001:  ldarg.1
  IL_0002:  newobj     ""System.ValueTuple<int?, bool?>..ctor(int?, bool?)""
  IL_0007:  stloc.0
  IL_0008:  ldloca.s   V_0
  IL_000a:  ldflda     ""int? System.ValueTuple<int?, bool?>.Item1""
  IL_000f:  call       ""bool int?.HasValue.get""
  IL_0014:  brtrue.s   IL_0026
  IL_0016:  ldloca.s   V_0
  IL_0018:  ldflda     ""bool? System.ValueTuple<int?, bool?>.Item2""
  IL_001d:  call       ""bool bool?.HasValue.get""
  IL_0022:  ldc.i4.0
  IL_0023:  ceq
  IL_0025:  ret
  IL_0026:  ldc.i4.0
  IL_0027:  ret
}");
        }
 
        [Fact]
        public void TestILForNullableElementsNotEqualsToNull()
        {
            var source = @"
class C
{
    static void Main()
    {
        System.Console.Write(M(null, null));
        System.Console.Write(M(1, true));
    }
    static bool M(int? i1, bool? b1)
    {
        var t1 = (i1, b1);
        return t1 != (null, null);
    }
}";
            var comp = CompileAndVerify(source, expectedOutput: "FalseTrue");
            comp.VerifyDiagnostics();
 
            comp.VerifyIL("C.M", @"{
  // Code size       37 (0x25)
  .maxstack  2
  .locals init (System.ValueTuple<int?, bool?> V_0)
  IL_0000:  ldarg.0
  IL_0001:  ldarg.1
  IL_0002:  newobj     ""System.ValueTuple<int?, bool?>..ctor(int?, bool?)""
  IL_0007:  stloc.0
  IL_0008:  ldloca.s   V_0
  IL_000a:  ldflda     ""int? System.ValueTuple<int?, bool?>.Item1""
  IL_000f:  call       ""bool int?.HasValue.get""
  IL_0014:  brtrue.s   IL_0023
  IL_0016:  ldloca.s   V_0
  IL_0018:  ldflda     ""bool? System.ValueTuple<int?, bool?>.Item2""
  IL_001d:  call       ""bool bool?.HasValue.get""
  IL_0022:  ret
  IL_0023:  ldc.i4.1
  IL_0024:  ret
}");
        }
 
        [Fact]
        public void TestILForNullableElementsComparedToNonNullValues()
        {
            var source = @"
class C
{
    static void Main()
    {
        System.Console.Write(M((null, null)));
        System.Console.Write(M((2, true)));
    }
    static bool M((int?, bool?) t1)
    {
        return t1 == (2, true);
    }
}";
            var comp = CompileAndVerify(source, expectedOutput: "FalseTrue");
            comp.VerifyDiagnostics();
 
            comp.VerifyIL("C.M", @"{
  // Code size       32 (0x20)
  .maxstack  2
  .locals init (System.ValueTuple<int?, bool?> V_0)
  IL_0000:  ldarg.0
  IL_0001:  stloc.0
  IL_0002:  ldloca.s   V_0
  IL_0004:  ldflda     ""int? System.ValueTuple<int?, bool?>.Item1""
  IL_0009:  call       ""int int?.GetValueOrDefault()""
  IL_000e:  ldc.i4.2
  IL_000f:  bne.un.s   IL_001e
  IL_0011:  ldloca.s   V_0
  IL_0013:  ldflda     ""bool? System.ValueTuple<int?, bool?>.Item2""
  IL_0018:  call       ""bool bool?.GetValueOrDefault()""
  IL_001d:  ret
  IL_001e:  ldc.i4.0
  IL_001f:  ret
}");
        }
 
        [Fact]
        public void TestILForNullableStructEqualsToNull()
        {
            var source = @"
struct S
{
    static void Main()
    {
        S? s = null;
        _ = s == null;
        System.Console.Write((s, null) == (null, s));
    }
}";
            var comp = CompileAndVerify(source, expectedOutput: "True");
            comp.VerifyDiagnostics();
 
            comp.VerifyIL("S.Main", @"{
  // Code size       48 (0x30)
  .maxstack  2
  .locals init (S? V_0, //s
                S? V_1,
                S? V_2)
  IL_0000:  ldloca.s   V_0
  IL_0002:  initobj    ""S?""
  IL_0008:  ldloca.s   V_0
  IL_000a:  call       ""bool S?.HasValue.get""
  IL_000f:  pop
  IL_0010:  ldloc.0
  IL_0011:  stloc.1
  IL_0012:  ldloc.0
  IL_0013:  stloc.2
  IL_0014:  ldloca.s   V_1
  IL_0016:  call       ""bool S?.HasValue.get""
  IL_001b:  brtrue.s   IL_0029
  IL_001d:  ldloca.s   V_2
  IL_001f:  call       ""bool S?.HasValue.get""
  IL_0024:  ldc.i4.0
  IL_0025:  ceq
  IL_0027:  br.s       IL_002a
  IL_0029:  ldc.i4.0
  IL_002a:  call       ""void System.Console.Write(bool)""
  IL_002f:  ret
}");
        }
 
        [Fact, WorkItem(25488, "https://github.com/dotnet/roslyn/issues/25488")]
        public void TestThisStruct()
        {
            var source = @"
public struct S
{
    public int I;
    public static void Main()
    {
        S s = new S() { I = 1 };
        s.M();
    }
    void M()
    {
        System.Console.Write((this, 2) == (1, this.Mutate()));
    }
 
    S Mutate()
    {
        I++;
        return this;
    }
    public static implicit operator S(int value) { return new S() { I = value }; }
    public static bool operator==(S s1, S s2) { System.Console.Write($""{s1.I} == {s2.I}, ""); return s1.I == s2.I; }
    public static bool operator!=(S s1, S s2) { throw null; }
    public override bool Equals(object o) { throw null; }
    public override int GetHashCode() { throw null; }
}";
            var comp = CompileAndVerify(source, expectedOutput: "1 == 1, 2 == 2, True");
            comp.VerifyDiagnostics();
        }
 
        [Fact]
        public void TestThisClass()
        {
            var source = @"
public class C
{
    public int I;
    public static void Main()
    {
        C c = new C() { I = 1 };
        c.M();
    }
    void M()
    {
        System.Console.Write((this, 2) == (2, this.Mutate()));
    }
 
    C Mutate()
    {
        I++;
        return this;
    }
    public static implicit operator C(int value) { return new C() { I = value }; }
    public static bool operator==(C c1, C c2) { System.Console.Write($""{c1.I} == {c2.I}, ""); return c1.I == c2.I; }
    public static bool operator!=(C c1, C c2) { throw null; }
    public override bool Equals(object o) { throw null; }
    public override int GetHashCode() { throw null; }
}";
            var comp = CompileAndVerify(source, expectedOutput: "2 == 2, 2 == 2, True");
            comp.VerifyDiagnostics();
        }
 
        [Fact]
        public void TestSimpleEqualOnTypelessTupleLiteral()
        {
            var source = @"
class C
{
    static bool M((string, long) t)
    {
        return t == (null, 1) && t == (""hello"", 1);
    }
}";
            var comp = CompileAndVerify(source);
            comp.VerifyDiagnostics();
 
            var tree = comp.Compilation.SyntaxTrees.First();
            var model = comp.Compilation.GetSemanticModel(tree);
 
            var tuple1 = tree.GetCompilationUnitRoot().DescendantNodes().OfType<TupleExpressionSyntax>().ElementAt(0);
            var symbol1 = model.GetTypeInfo(tuple1);
            Assert.Null(symbol1.Type);
            Assert.Equal("(System.String, System.Int64)", symbol1.ConvertedType.ToTestDisplayString());
            Assert.Equal("(System.String, System.Int64)", model.GetDeclaredSymbol(tuple1).ToTestDisplayString());
 
            var tuple2 = tree.GetCompilationUnitRoot().DescendantNodes().OfType<TupleExpressionSyntax>().ElementAt(1);
            var symbol2 = model.GetTypeInfo(tuple2);
            Assert.Equal("(System.String, System.Int32)", symbol2.Type.ToTestDisplayString());
            Assert.Equal("(System.String, System.Int64)", symbol2.ConvertedType.ToTestDisplayString());
            Assert.False(model.GetConstantValue(tuple2).HasValue);
            Assert.Equal(1, model.GetConstantValue(tuple2.Arguments[1].Expression).Value);
        }
 
        [Fact]
        public void TestConversionOnTupleExpression()
        {
            var source = @"
class C
{
    static bool M((int, byte) t)
    {
        return t == (1L, 2);
    }
}";
            var comp = CompileAndVerify(source);
            comp.VerifyDiagnostics();
 
            var tree = comp.Compilation.SyntaxTrees.First();
            var model = comp.Compilation.GetSemanticModel(tree);
 
            var equals = tree.GetCompilationUnitRoot().DescendantNodes().OfType<BinaryExpressionSyntax>().Single();
 
            // check t
            var t = equals.Left;
            Assert.Equal("t", t.ToString());
            Assert.Equal("(System.Int32, System.Byte) t", model.GetSymbolInfo(t).Symbol.ToTestDisplayString());
            Assert.Equal(ConversionKind.ImplicitTuple, model.GetConversion(t).Kind);
 
            var tTypeInfo = model.GetTypeInfo(t);
            Assert.Equal("(System.Int32, System.Byte)", tTypeInfo.Type.ToTestDisplayString());
            Assert.Equal("(System.Int64, System.Int32)", tTypeInfo.ConvertedType.ToTestDisplayString());
 
            // check tuple
            var tuple = equals.Right;
            Assert.Equal("(1L, 2)", tuple.ToString());
            Assert.Null(model.GetSymbolInfo(tuple).Symbol);
            Assert.Equal(Conversion.Identity, model.GetConversion(tuple));
 
            var tupleTypeInfo = model.GetTypeInfo(tuple);
            Assert.Equal("(System.Int64, System.Int32)", tupleTypeInfo.Type.ToTestDisplayString());
            Assert.Equal("(System.Int64, System.Int32)", tupleTypeInfo.ConvertedType.ToTestDisplayString());
        }
 
        [Fact]
        public void TestOtherOperatorsOnTuples()
        {
            var source = @"
class C
{
    void M()
    {
        var t1 = (1, 2);
        _ = t1 + t1; // error 1
        _ = t1 > t1; // error 2
        _ = t1 >= t1; // error 3
        _ = !t1; // error 4
    }
}";
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (7,13): error CS0019: Operator '+' cannot be applied to operands of type '(int, int)' and '(int, int)'
                //         _ = t1 + t1; // error 1
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "t1 + t1").WithArguments("+", "(int, int)", "(int, int)").WithLocation(7, 13),
                // (8,13): error CS0019: Operator '>' cannot be applied to operands of type '(int, int)' and '(int, int)'
                //         _ = t1 > t1; // error 2
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "t1 > t1").WithArguments(">", "(int, int)", "(int, int)").WithLocation(8, 13),
                // (9,13): error CS0019: Operator '>=' cannot be applied to operands of type '(int, int)' and '(int, int)'
                //         _ = t1 >= t1; // error 3
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "t1 >= t1").WithArguments(">=", "(int, int)", "(int, int)").WithLocation(9, 13),
                // (10,13): error CS0023: Operator '!' cannot be applied to operand of type '(int, int)'
                //         _ = !t1; // error 4
                Diagnostic(ErrorCode.ERR_BadUnaryOp, "!t1").WithArguments("!", "(int, int)").WithLocation(10, 13)
                );
 
            var tree = comp.SyntaxTrees.First();
            var model = comp.GetSemanticModel(tree);
            var tuple = tree.GetCompilationUnitRoot().DescendantNodes().OfType<TupleExpressionSyntax>().Single();
            Assert.Equal("(System.Int32, System.Int32)", model.GetDeclaredSymbol(tuple).ToTestDisplayString());
        }
 
        [Fact]
        public void TestTypelessTuples()
        {
            var source = @"
class C
{
    static void Main()
    {
        string s = null;
        System.Console.Write((s, null) == (null, s));
    }
}";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "True");
 
            var tree = comp.SyntaxTrees[0];
            var model = comp.GetSemanticModel(tree);
 
            // check first tuple and its null
            var tuple1 = tree.GetCompilationUnitRoot().DescendantNodes().OfType<TupleExpressionSyntax>().ElementAt(0);
            Assert.Equal("(s, null)", tuple1.ToString());
            var tupleType1 = model.GetTypeInfo(tuple1);
            Assert.Null(tupleType1.Type);
            Assert.Equal("(System.String s, System.String)", tupleType1.ConvertedType.ToTestDisplayString());
 
            var tuple1Null = tuple1.Arguments[1].Expression;
            var tuple1NullTypeInfo = model.GetTypeInfo(tuple1Null);
            Assert.Null(tuple1NullTypeInfo.Type);
            Assert.Equal("System.String", tuple1NullTypeInfo.ConvertedType.ToTestDisplayString());
            Assert.Equal(ConversionKind.ImplicitReference, model.GetConversion(tuple1Null).Kind);
 
            // check second tuple and its null
            var tuple2 = tree.GetCompilationUnitRoot().DescendantNodes().OfType<TupleExpressionSyntax>().ElementAt(1);
            Assert.Equal("(null, s)", tuple2.ToString());
            var tupleType2 = model.GetTypeInfo(tuple2);
            Assert.Null(tupleType2.Type);
            Assert.Equal("(System.String, System.String s)", tupleType2.ConvertedType.ToTestDisplayString());
 
            var tuple2Null = tuple2.Arguments[0].Expression;
            var tuple2NullTypeInfo = model.GetTypeInfo(tuple2Null);
            Assert.Null(tuple2NullTypeInfo.Type);
            Assert.Equal("System.String", tuple2NullTypeInfo.ConvertedType.ToTestDisplayString());
            Assert.Equal(ConversionKind.ImplicitReference, model.GetConversion(tuple2Null).Kind);
        }
 
        [Fact]
        public void TestWithNoSideEffectsOrTemps()
        {
            var source = @"
class C
{
    static void Main()
    {
        System.Console.Write((1, 2) == (1, 3));
    }
}";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "False");
        }
 
        [Fact]
        public void TestSimpleTupleAndTupleType_01()
        {
            var source = @"
class C
{
    static void Main()
    {
        var t1 = (1, 2L);
        System.Console.Write(t1 == (1L, 2));
    }
}";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "True");
 
            var tree = comp.SyntaxTrees[0];
            var model = comp.GetSemanticModel(tree);
 
            var equals = tree.GetCompilationUnitRoot().DescendantNodes().OfType<BinaryExpressionSyntax>().Single();
 
            // check t1
            var t1 = equals.Left;
            Assert.Equal("t1", t1.ToString());
 
            var t1TypeInfo = model.GetTypeInfo(t1);
            Assert.Equal("(System.Int32, System.Int64)", t1TypeInfo.Type.ToTestDisplayString());
            Assert.Equal("(System.Int64, System.Int64)", t1TypeInfo.ConvertedType.ToTestDisplayString());
            Assert.Equal(ConversionKind.ImplicitTuple, model.GetConversion(t1).Kind);
 
            // check tuple and its literal 2
            var tuple = (TupleExpressionSyntax)equals.Right;
            Assert.Equal("(1L, 2)", tuple.ToString());
 
            var tupleType = model.GetTypeInfo(tuple);
            Assert.Equal("(System.Int64, System.Int32)", tupleType.Type.ToTestDisplayString());
            Assert.Equal("(System.Int64, System.Int64)", tupleType.ConvertedType.ToTestDisplayString());
            Assert.Equal(ConversionKind.ImplicitTupleLiteral, model.GetConversion(tuple).Kind);
 
            var two = tuple.Arguments[1].Expression;
            Assert.Equal("2", two.ToString());
 
            var twoType = model.GetTypeInfo(two);
            Assert.Equal("System.Int32", twoType.Type.ToTestDisplayString());
            Assert.Equal("System.Int64", twoType.ConvertedType.ToTestDisplayString());
            Assert.Equal(ConversionKind.ImplicitNumeric, model.GetConversion(two).Kind);
        }
 
        [Fact]
        public void TestSimpleTupleAndTupleType_02()
        {
            var source = @"
class C
{
    static void Main()
    {
        var t1 = (1, 2UL);
        System.Console.Write(t1 == (1L, 2));
    }
}";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "True");
 
            var tree = comp.SyntaxTrees[0];
            var model = comp.GetSemanticModel(tree);
 
            var equals = tree.GetCompilationUnitRoot().DescendantNodes().OfType<BinaryExpressionSyntax>().Single();
 
            // check t1
            var t1 = equals.Left;
            Assert.Equal("t1", t1.ToString());
 
            var t1TypeInfo = model.GetTypeInfo(t1);
            Assert.Equal("(System.Int32, System.UInt64)", t1TypeInfo.Type.ToTestDisplayString());
            Assert.Equal("(System.Int64, System.UInt64)", t1TypeInfo.ConvertedType.ToTestDisplayString());
            Assert.Equal(ConversionKind.ImplicitTuple, model.GetConversion(t1).Kind);
 
            // check tuple and its literal 2
            var tuple = (TupleExpressionSyntax)equals.Right;
            Assert.Equal("(1L, 2)", tuple.ToString());
 
            var tupleType = model.GetTypeInfo(tuple);
            Assert.Equal("(System.Int64, System.Int32)", tupleType.Type.ToTestDisplayString());
            Assert.Equal("(System.Int64, System.UInt64)", tupleType.ConvertedType.ToTestDisplayString());
            Assert.Equal(ConversionKind.ImplicitTupleLiteral, model.GetConversion(tuple).Kind);
 
            var two = tuple.Arguments[1].Expression;
            Assert.Equal("2", two.ToString());
 
            var twoType = model.GetTypeInfo(two);
            Assert.Equal("System.Int32", twoType.Type.ToTestDisplayString());
            Assert.Equal("System.UInt64", twoType.ConvertedType.ToTestDisplayString());
            Assert.Equal(ConversionKind.ImplicitConstant, model.GetConversion(two).Kind);
        }
 
        [Fact]
        public void TestNestedTupleAndTupleType()
        {
            var source = @"
class C
{
    static void Main()
    {
        var t1 = (1, (2L, ""hello""));
        var t2 = (2, ""hello"");
        System.Console.Write(t1 == (1L, t2));
    }
}";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "True");
 
            var tree = comp.SyntaxTrees[0];
            var model = comp.GetSemanticModel(tree);
 
            var equals = tree.GetCompilationUnitRoot().DescendantNodes().OfType<BinaryExpressionSyntax>().Single();
 
            // check t1
            var t1 = equals.Left;
            Assert.Equal("t1", t1.ToString());
 
            var t1TypeInfo = model.GetTypeInfo(t1);
            Assert.Equal("(System.Int32, (System.Int64, System.String))", t1TypeInfo.Type.ToTestDisplayString());
            Assert.Equal("(System.Int64, (System.Int64, System.String))", t1TypeInfo.ConvertedType.ToTestDisplayString());
            Assert.Equal(ConversionKind.ImplicitTuple, model.GetConversion(t1).Kind);
            Assert.Equal("(System.Int32, (System.Int64, System.String)) t1", model.GetSymbolInfo(t1).Symbol.ToTestDisplayString());
 
            // check tuple and its t2
            var tuple = (TupleExpressionSyntax)equals.Right;
            Assert.Equal("(1L, t2)", tuple.ToString());
            var tupleType = model.GetTypeInfo(tuple);
            Assert.Equal("(System.Int64, (System.Int32, System.String) t2)", tupleType.Type.ToTestDisplayString());
            Assert.Equal("(System.Int64, (System.Int64, System.String) t2)", tupleType.ConvertedType.ToTestDisplayString());
 
            var t2 = tuple.Arguments[1].Expression;
            Assert.Equal("t2", t2.ToString());
 
            var t2TypeInfo = model.GetTypeInfo(t2);
            Assert.Equal("(System.Int32, System.String)", t2TypeInfo.Type.ToTestDisplayString());
            Assert.Equal("(System.Int64, System.String)", t2TypeInfo.ConvertedType.ToTestDisplayString());
            Assert.Equal(ConversionKind.ImplicitTuple, model.GetConversion(t2).Kind);
            Assert.Equal("(System.Int32, System.String) t2", model.GetSymbolInfo(t2).Symbol.ToTestDisplayString());
        }
 
        [Fact]
        public void TestTypelessTupleAndTupleType()
        {
            var source = @"
class C
{
    static void Main()
    {
        (string, string) t = (null, null);
        System.Console.Write(t == (null, null));
        System.Console.Write(t != (null, null));
        System.Console.Write((1, t) == (1, (null, null)));
    }
}";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "TrueFalseTrue");
 
            var tree = comp.SyntaxTrees[0];
            var model = comp.GetSemanticModel(tree);
 
            var tuple = tree.GetCompilationUnitRoot().DescendantNodes().OfType<TupleExpressionSyntax>().ElementAt(1);
            Assert.Equal("(null, null)", tuple.ToString());
            var tupleType = model.GetTypeInfo(tuple);
            Assert.Null(tupleType.Type);
            Assert.Equal("(System.String, System.String)", tupleType.ConvertedType.ToTestDisplayString());
 
            // check last tuple ...
            var lastEquals = tree.GetCompilationUnitRoot().DescendantNodes().OfType<BinaryExpressionSyntax>().Last();
            var lastTuple = (TupleExpressionSyntax)lastEquals.Right;
            Assert.Equal("(1, (null, null))", lastTuple.ToString());
            TypeInfo lastTupleTypeInfo = model.GetTypeInfo(lastTuple);
            Assert.Null(lastTupleTypeInfo.Type);
            Assert.Equal("(System.Int32, (System.String, System.String))", lastTupleTypeInfo.ConvertedType.ToTestDisplayString());
            Assert.Equal(ConversionKind.ImplicitTupleLiteral, model.GetConversion(lastTuple).Kind);
 
            // ... and its nested (null, null) tuple ...
            var nullNull = (TupleExpressionSyntax)lastTuple.Arguments[1].Expression;
            TypeInfo nullNullTypeInfo = model.GetTypeInfo(nullNull);
            Assert.Null(nullNullTypeInfo.Type);
            Assert.Equal("(System.String, System.String)", nullNullTypeInfo.ConvertedType.ToTestDisplayString());
            Assert.Equal(ConversionKind.ImplicitTupleLiteral, model.GetConversion(nullNull).Kind);
 
            // ... and its last null.
            var lastNull = nullNull.Arguments[1].Expression;
            TypeInfo lastNullTypeInfo = model.GetTypeInfo(lastNull);
            Assert.Null(lastNullTypeInfo.Type);
            Assert.Equal("System.String", lastNullTypeInfo.ConvertedType.ToTestDisplayString());
            Assert.Equal(ConversionKind.ImplicitReference, model.GetConversion(lastNull).Kind);
        }
 
        [Fact]
        public void TestTypedTupleAndDefault()
        {
            var source = @"
class C
{
    static void Main()
    {
        (string, string) t = (null, null);
        System.Console.Write(t == default);
        System.Console.Write(t != default);
 
        (string, string) t2 = (null, ""hello"");
        System.Console.Write(t2 == default);
        System.Console.Write(t2 != default);
    }
}";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "TrueFalseFalseTrue");
 
            var tree = comp.SyntaxTrees[0];
            var model = comp.GetSemanticModel(tree);
 
            var defaultLiterals = tree.GetCompilationUnitRoot().DescendantNodes().OfType<LiteralExpressionSyntax>()
                .Where(e => e.Kind() == SyntaxKind.DefaultLiteralExpression);
 
            foreach (var literal in defaultLiterals)
            {
                Assert.Equal("default", literal.ToString());
                var info = model.GetTypeInfo(literal);
                Assert.Equal("(System.String, System.String)", info.Type.ToTestDisplayString());
                Assert.Equal("(System.String, System.String)", info.ConvertedType.ToTestDisplayString());
            }
        }
 
        [Fact]
        public void TestNullableTupleAndDefault()
        {
            var source = @"
class C
{
    static void Main()
    {
        (string, string)? t = (null, null);
        System.Console.Write(t == default);
        System.Console.Write(t != default);
        System.Console.Write(default == t);
        System.Console.Write(default != t);
    }
}";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "FalseTrueFalseTrue");
 
            var tree = comp.SyntaxTrees[0];
            var model = comp.GetSemanticModel(tree);
 
            var defaultLiterals = tree.GetCompilationUnitRoot().DescendantNodes().OfType<LiteralExpressionSyntax>()
                .Where(e => e.Kind() == SyntaxKind.DefaultLiteralExpression);
 
            foreach (var literal in defaultLiterals)
            {
                Assert.Equal("default", literal.ToString());
                var info = model.GetTypeInfo(literal);
                Assert.Equal("(System.String, System.String)?", info.Type.ToTestDisplayString());
                Assert.Equal("(System.String, System.String)?", info.ConvertedType.ToTestDisplayString());
            }
        }
 
        [Fact]
        public void TestNullableTupleAndDefault_Nested()
        {
            var source = @"
class C
{
    static void Main()
    {
        (string, string)? t = (null, null);
        System.Console.Write((null, t) == (null, default));
        System.Console.Write((t, null) != (default, null));
    }
}";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "FalseTrue");
 
            var tree = comp.SyntaxTrees[0];
            var model = comp.GetSemanticModel(tree);
 
            var defaultLiterals = tree.GetCompilationUnitRoot().DescendantNodes().OfType<LiteralExpressionSyntax>()
                .Where(e => e.Kind() == SyntaxKind.DefaultLiteralExpression);
 
            foreach (var literal in defaultLiterals)
            {
                Assert.Equal("default", literal.ToString());
                var info = model.GetTypeInfo(literal);
                Assert.Equal("(System.String, System.String)?", info.Type.ToTestDisplayString());
                Assert.Equal("(System.String, System.String)?", info.ConvertedType.ToTestDisplayString());
            }
        }
 
        [Fact]
        public void TestNestedDefaultWithNonTupleType()
        {
            var source = @"
class C
{
    static void Main()
    {
        System.Console.Write((null, 1) == (null, default));
        System.Console.Write((0, null) != (default, null));
    }
}";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "FalseFalse");
 
            var tree = comp.SyntaxTrees[0];
            var model = comp.GetSemanticModel(tree);
 
            var defaultLiterals = tree.GetCompilationUnitRoot().DescendantNodes().OfType<LiteralExpressionSyntax>()
                .Where(e => e.Kind() == SyntaxKind.DefaultLiteralExpression);
 
            foreach (var literal in defaultLiterals)
            {
                Assert.Equal("default", literal.ToString());
                var info = model.GetTypeInfo(literal);
                Assert.Equal("System.Int32", info.Type.ToTestDisplayString());
                Assert.Equal("System.Int32", info.ConvertedType.ToTestDisplayString());
            }
        }
 
        [Fact]
        public void TestNestedDefaultWithNullableNonTupleType()
        {
            var source = @"
struct S
{
    static void Main()
    {
        S? ns = null;
        _ = (null, ns) == (null, default);
        _ = (ns, null) != (default, null);
    }
}";
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (7,13): error CS0019: Operator '==' cannot be applied to operands of type 'S?' and 'default'
                //         _ = (null, ns) == (null, default);
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "(null, ns) == (null, default)").WithArguments("==", "S?", "default").WithLocation(7, 13),
                // (8,13): error CS0019: Operator '!=' cannot be applied to operands of type 'S?' and 'default'
                //         _ = (ns, null) != (default, null);
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "(ns, null) != (default, null)").WithArguments("!=", "S?", "default").WithLocation(8, 13)
                );
        }
 
        [Fact]
        public void TestNestedDefaultWithNullableNonTupleType_WithComparisonOperator()
        {
            var source = @"
public struct S
{
    public static void Main()
    {
        S? ns = new S();
        System.Console.Write((null, ns) == (null, default));
        System.Console.Write((ns, null) != (default, null));
    }
    public static bool operator==(S s1, S s2) => throw null;
    public static bool operator!=(S s1, S s2) => throw null;
    public override int GetHashCode() => throw null;
    public override bool Equals(object o) => throw null;
}";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "FalseTrue");
 
            var tree = comp.SyntaxTrees[0];
            var model = comp.GetSemanticModel(tree);
            var defaults = tree.GetCompilationUnitRoot().DescendantNodes().OfType<LiteralExpressionSyntax>()
                .Where(e => e.Kind() == SyntaxKind.DefaultLiteralExpression);
 
            foreach (var literal in defaults)
            {
                var type = model.GetTypeInfo(literal);
                Assert.Equal("S?", type.Type.ToTestDisplayString());
                Assert.Equal("S?", type.ConvertedType.ToTestDisplayString());
            }
        }
 
        [Fact]
        public void TestAllDefaults()
        {
            var source = @"
class C
{
    static void Main()
    {
        System.Console.Write((default, default) == (default, default));
        System.Console.Write(default == (default, default));
    }
}";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics(
                // (6,30): error CS8315: Operator '==' is ambiguous on operands 'default' and 'default'
                //         System.Console.Write((default, default) == (default, default));
                Diagnostic(ErrorCode.ERR_AmbigBinaryOpsOnDefault, "(default, default) == (default, default)").WithArguments("==", "default", "default").WithLocation(6, 30),
                // (6,30): error CS8315: Operator '==' is ambiguous on operands 'default' and 'default'
                //         System.Console.Write((default, default) == (default, default));
                Diagnostic(ErrorCode.ERR_AmbigBinaryOpsOnDefault, "(default, default) == (default, default)").WithArguments("==", "default", "default").WithLocation(6, 30),
                // (7,30): error CS0034: Operator '==' is ambiguous on operands of type 'default' and '(default, default)'
                //         System.Console.Write(default == (default, default));
                Diagnostic(ErrorCode.ERR_AmbigBinaryOps, "default == (default, default)").WithArguments("==", "default", "(default, default)").WithLocation(7, 30)
                );
        }
 
        [Fact]
        public void TestNullsAndDefaults()
        {
            var source = @"
class C
{
    static void Main()
    {
        _ = (null, default) != (default, null);
    }
}";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics(
                // (6,13): error CS0034: Operator '!=' is ambiguous on operands of type '<null>' and 'default'
                //         _ = (null, default) != (default, null);
                Diagnostic(ErrorCode.ERR_AmbigBinaryOps, "(null, default) != (default, null)").WithArguments("!=", "<null>", "default").WithLocation(6, 13),
                // (6,13): error CS0034: Operator '!=' is ambiguous on operands of type 'default' and '<null>'
                //         _ = (null, default) != (default, null);
                Diagnostic(ErrorCode.ERR_AmbigBinaryOps, "(null, default) != (default, null)").WithArguments("!=", "default", "<null>").WithLocation(6, 13)
                );
        }
 
        [Fact]
        public void TestAllDefaults_Nested()
        {
            var source = @"
class C
{
    static void Main()
    {
        System.Console.Write((null, (default, default)) == (null, (default, default)));
    }
}";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics(
                // (6,30): error CS8315: Operator '==' is ambiguous on operands 'default' and 'default'
                //         System.Console.Write((null, (default, default)) == (null, (default, default)));
                Diagnostic(ErrorCode.ERR_AmbigBinaryOpsOnDefault, "(null, (default, default)) == (null, (default, default))").WithArguments("==", "default", "default").WithLocation(6, 30),
                // (6,30): error CS8315: Operator '==' is ambiguous on operands 'default' and 'default'
                //         System.Console.Write((null, (default, default)) == (null, (default, default)));
                Diagnostic(ErrorCode.ERR_AmbigBinaryOpsOnDefault, "(null, (default, default)) == (null, (default, default))").WithArguments("==", "default", "default").WithLocation(6, 30)
                );
        }
 
        [Fact]
        public void TestTypedTupleAndTupleOfDefaults()
        {
            var source = @"
class C
{
    static void Main()
    {
        (string, string)? t = (null, null);
        System.Console.Write(t == (default, default));
        System.Console.Write(t != (default, default));
    }
}";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "TrueFalse");
 
            var tree = comp.SyntaxTrees[0];
            var model = comp.GetSemanticModel(tree);
            var lastTuple = tree.GetCompilationUnitRoot().DescendantNodes().OfType<TupleExpressionSyntax>().Last();
 
            Assert.Equal("(default, default)", lastTuple.ToString());
            Assert.Null(model.GetTypeInfo(lastTuple).Type);
            Assert.Equal("(System.String, System.String)?", model.GetTypeInfo(lastTuple).ConvertedType.ToTestDisplayString());
 
            var lastDefault = lastTuple.Arguments[1].Expression;
            Assert.Equal("default", lastDefault.ToString());
            Assert.Equal("System.String", model.GetTypeInfo(lastDefault).Type.ToTestDisplayString());
            Assert.Equal("System.String", model.GetTypeInfo(lastDefault).ConvertedType.ToTestDisplayString());
        }
 
        [Fact]
        public void TestTypelessTupleAndTupleOfDefaults()
        {
            var source = @"
class C
{
    static void Main()
    {
        System.Console.Write((null, () => 1) == (default, default));
        System.Console.Write((null, () => 2) == default);
    }
}";
            var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9);
            comp.VerifyDiagnostics(
                // (6,30): error CS0034: Operator '==' is ambiguous on operands of type '<null>' and 'default'
                //         System.Console.Write((null, () => 1) == (default, default));
                Diagnostic(ErrorCode.ERR_AmbigBinaryOps, "(null, () => 1) == (default, default)").WithArguments("==", "<null>", "default").WithLocation(6, 30),
                // (6,30): error CS0019: Operator '==' cannot be applied to operands of type 'lambda expression' and 'default'
                //         System.Console.Write((null, () => 1) == (default, default));
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "(null, () => 1) == (default, default)").WithArguments("==", "lambda expression", "default").WithLocation(6, 30),
                // (7,30): error CS0034: Operator '==' is ambiguous on operands of type '(<null>, lambda expression)' and 'default'
                //         System.Console.Write((null, () => 2) == default);
                Diagnostic(ErrorCode.ERR_AmbigBinaryOps, "(null, () => 2) == default").WithArguments("==", "(<null>, lambda expression)", "default").WithLocation(7, 30)
                );
 
            comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (6,30): error CS0034: Operator '==' is ambiguous on operands of type '<null>' and 'default'
                //         System.Console.Write((null, () => 1) == (default, default));
                Diagnostic(ErrorCode.ERR_AmbigBinaryOps, "(null, () => 1) == (default, default)").WithArguments("==", "<null>", "default").WithLocation(6, 30),
                // (7,30): error CS0034: Operator '==' is ambiguous on operands of type '(<null>, lambda expression)' and 'default'
                //         System.Console.Write((null, () => 2) == default);
                Diagnostic(ErrorCode.ERR_AmbigBinaryOps, "(null, () => 2) == default").WithArguments("==", "(<null>, lambda expression)", "default").WithLocation(7, 30)
                );
        }
 
        [Fact]
        public void TestNullableStructAndDefault()
        {
            var source = @"
struct S
{
    static void M(string s)
    {
        S? ns = new S();
        _ = ns == null;
        _ = s == null;
        _ = ns == default; // error 1
        _ = (ns, ns) == (null, null);
        _ = (ns, ns) == (default, default); // errors 2 and 3
        _ = (ns, ns) == default; // error 4
    }
}";
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (9,13): error CS0019: Operator '==' cannot be applied to operands of type 'S?' and 'default'
                //         _ = ns == default; // error 1
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "ns == default").WithArguments("==", "S?", "default").WithLocation(9, 13),
                // (11,13): error CS0019: Operator '==' cannot be applied to operands of type 'S?' and 'default'
                //         _ = (ns, ns) == (default, default); // errors 2 and 3
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "(ns, ns) == (default, default)").WithArguments("==", "S?", "default").WithLocation(11, 13),
                // (11,13): error CS0019: Operator '==' cannot be applied to operands of type 'S?' and 'default'
                //         _ = (ns, ns) == (default, default); // errors 2 and 3
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "(ns, ns) == (default, default)").WithArguments("==", "S?", "default").WithLocation(11, 13),
                // (12,13): error CS0019: Operator '==' cannot be applied to operands of type 'S?' and 'S?'
                //         _ = (ns, ns) == default; // error 4
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "(ns, ns) == default").WithArguments("==", "S?", "S?").WithLocation(12, 13),
                // (12,13): error CS0019: Operator '==' cannot be applied to operands of type 'S?' and 'S?'
                //         _ = (ns, ns) == default; // error 4
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "(ns, ns) == default").WithArguments("==", "S?", "S?").WithLocation(12, 13)
                );
 
            var tree = comp.SyntaxTrees[0];
            var model = comp.GetSemanticModel(tree);
            var literals = tree.GetCompilationUnitRoot().DescendantNodes().OfType<LiteralExpressionSyntax>();
 
            var nullLiteral = literals.ElementAt(0);
            Assert.Equal("null", nullLiteral.ToString());
            Assert.Null(model.GetTypeInfo(nullLiteral).ConvertedType);
 
            var nullLiteral2 = literals.ElementAt(1);
            Assert.Equal("null", nullLiteral2.ToString());
            Assert.Null(model.GetTypeInfo(nullLiteral2).Type);
            Assert.Equal("System.String", model.GetTypeInfo(nullLiteral2).ConvertedType.ToTestDisplayString());
 
            var defaultLiterals = tree.GetCompilationUnitRoot().DescendantNodes().OfType<LiteralExpressionSyntax>()
                .Where(e => e.Kind() == SyntaxKind.DelegateDeclaration);
 
            foreach (var defaultLiteral in defaultLiterals)
            {
                Assert.Equal("default", defaultLiteral.ToString());
                Assert.Null(model.GetTypeInfo(defaultLiteral).Type);
                Assert.Null(model.GetTypeInfo(defaultLiteral).ConvertedType);
            }
        }
 
        [Fact, WorkItem(25318, "https://github.com/dotnet/roslyn/issues/25318")]
        public void TestNullableStructAndDefault_WithComparisonOperator()
        {
            var source = @"
public struct S
{
    static void M(string s)
    {
        S? ns = new S();
        _ = ns == 1;
        _ = (ns, ns) == (default, default);
        _ = (ns, ns) == default;
    }
    public static bool operator==(S s, byte b) => throw null;
    public static bool operator!=(S s, byte b) => throw null;
    public override bool Equals(object other) => throw null;
    public override int GetHashCode() => throw null;
}";
            var comp = CreateCompilation(source);
 
            // https://github.com/dotnet/roslyn/issues/25318
            // This should be allowed
 
            comp.VerifyDiagnostics(
                // (7,13): error CS0019: Operator '==' cannot be applied to operands of type 'S?' and 'int'
                //         _ = ns == 1;
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "ns == 1").WithArguments("==", "S?", "int").WithLocation(7, 13),
                // (8,13): error CS0019: Operator '==' cannot be applied to operands of type 'S?' and 'default'
                //         _ = (ns, ns) == (default, default);
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "(ns, ns) == (default, default)").WithArguments("==", "S?", "default").WithLocation(8, 13),
                // (8,13): error CS0019: Operator '==' cannot be applied to operands of type 'S?' and 'default'
                //         _ = (ns, ns) == (default, default);
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "(ns, ns) == (default, default)").WithArguments("==", "S?", "default").WithLocation(8, 13),
                // (9,13): error CS0019: Operator '==' cannot be applied to operands of type 'S?' and 'S?'
                //         _ = (ns, ns) == default;
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "(ns, ns) == default").WithArguments("==", "S?", "S?").WithLocation(9, 13),
                // (9,13): error CS0019: Operator '==' cannot be applied to operands of type 'S?' and 'S?'
                //         _ = (ns, ns) == default;
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "(ns, ns) == default").WithArguments("==", "S?", "S?").WithLocation(9, 13)
                );
 
            var tree = comp.SyntaxTrees[0];
            var model = comp.GetSemanticModel(tree);
 
            var defaultLiterals = tree.GetCompilationUnitRoot().DescendantNodes().OfType<LiteralExpressionSyntax>()
                .Where(e => e.Kind() == SyntaxKind.DelegateDeclaration);
 
            // Should have types
            foreach (var defaultLiteral in defaultLiterals)
            {
                Assert.Equal("default", defaultLiteral.ToString());
                Assert.Null(model.GetTypeInfo(defaultLiteral).Type);
                Assert.Null(model.GetTypeInfo(defaultLiteral).ConvertedType);
                // https://github.com/dotnet/roslyn/issues/25318
                // default should become int
            }
        }
 
        [Fact]
        public void TestMixedTupleLiteralsAndTypes()
        {
            var source = @"
class C
{
    static void Main()
    {
        (string, string) t = (null, null);
        System.Console.Write((t, (null, null)) == ((null, null), t));
    }
}";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "True");
 
            var tree = comp.SyntaxTrees[0];
            var model = comp.GetSemanticModel(tree);
 
            // check last tuple ...
            var tuple = tree.GetCompilationUnitRoot().DescendantNodes().OfType<TupleExpressionSyntax>().ElementAt(3);
            Assert.Equal("((null, null), t)", tuple.ToString());
 
            var tupleType = model.GetTypeInfo(tuple);
            Assert.Null(tupleType.Type);
            Assert.Equal("((System.String, System.String), (System.String, System.String) t)",
                tupleType.ConvertedType.ToTestDisplayString());
 
            // ... its t ...
            var t = tuple.Arguments[1].Expression;
            Assert.Equal("t", t.ToString());
 
            var tType = model.GetTypeInfo(t);
            Assert.Equal("(System.String, System.String)", tType.Type.ToTestDisplayString());
            Assert.Equal("(System.String, System.String)", tType.ConvertedType.ToTestDisplayString());
            Assert.Equal(ConversionKind.Identity, model.GetConversion(t).Kind);
            Assert.Equal("(System.String, System.String) t", model.GetSymbolInfo(t).Symbol.ToTestDisplayString());
            Assert.Null(model.GetDeclaredSymbol(t));
 
            // ... its nested tuple ...
            var nestedTuple = (TupleExpressionSyntax)tuple.Arguments[0].Expression;
            Assert.Equal("(null, null)", nestedTuple.ToString());
 
            var nestedTupleType = model.GetTypeInfo(nestedTuple);
            Assert.Null(nestedTupleType.Type);
            Assert.Equal("(System.String, System.String)", nestedTupleType.ConvertedType.ToTestDisplayString());
            Assert.Equal(ConversionKind.ImplicitTupleLiteral, model.GetConversion(nestedTuple).Kind);
            Assert.Null(model.GetSymbolInfo(nestedTuple).Symbol);
            Assert.Equal("(System.String, System.String)", model.GetDeclaredSymbol(nestedTuple).ToTestDisplayString());
 
            // ... a nested null.
            var nestedNull = nestedTuple.Arguments[0].Expression;
            Assert.Equal("null", nestedNull.ToString());
 
            var nestedNullType = model.GetTypeInfo(nestedNull);
            Assert.Null(nestedNullType.Type);
            Assert.Equal("System.String", nestedNullType.ConvertedType.ToTestDisplayString());
            Assert.Equal(ConversionKind.ImplicitReference, model.GetConversion(nestedNull).Kind);
        }
 
        [Fact]
        public void TestAllNulls()
        {
            var source = @"
class C
{
    static void Main()
    {
        System.Console.Write(null == null);
        System.Console.Write((null, null) == (null, null));
        System.Console.Write((null, null) != (null, null));
    }
}";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "TrueTrueFalse");
 
            var tree = comp.SyntaxTrees[0];
            var model = comp.GetSemanticModel(tree);
 
            var nulls = tree.GetCompilationUnitRoot().DescendantNodes().OfType<LiteralExpressionSyntax>();
            foreach (var literal in nulls)
            {
                Assert.Equal("null", literal.ToString());
                var symbol = model.GetTypeInfo(literal);
                Assert.Null(symbol.Type);
                Assert.Null(symbol.ConvertedType);
            }
 
            var tuples = tree.GetCompilationUnitRoot().DescendantNodes().OfType<TupleExpressionSyntax>();
            foreach (var tuple in tuples)
            {
                Assert.Equal("(null, null)", tuple.ToString());
                var symbol = model.GetTypeInfo(tuple);
                Assert.Null(symbol.Type);
                Assert.Null(symbol.ConvertedType);
            }
        }
 
        [Fact]
        public void TestConvertedElementInTypelessTuple()
        {
            var source = @"
class C
{
    static void Main()
    {
        System.Console.Write((null, 1L) == (null, 2));
    }
}";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "False");
 
            var tree = comp.SyntaxTrees[0];
            var model = comp.GetSemanticModel(tree);
 
            var lastLiteral = tree.GetCompilationUnitRoot().DescendantNodes().OfType<LiteralExpressionSyntax>().Last();
            Assert.Equal("2", lastLiteral.ToString());
            var literalInfo = model.GetTypeInfo(lastLiteral);
            Assert.Equal("System.Int32", literalInfo.Type.ToTestDisplayString());
            Assert.Equal("System.Int64", literalInfo.ConvertedType.ToTestDisplayString());
        }
 
        [Fact]
        public void TestConvertedElementInTypelessTuple_Nested()
        {
            var source = @"
class C
{
    static void Main()
    {
        System.Console.Write(((null, 1L), null) == ((null, 2), null));
    }
}";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "False");
 
            var tree = comp.SyntaxTrees[0];
            var model = comp.GetSemanticModel(tree);
 
            var rightTuple = tree.GetCompilationUnitRoot().DescendantNodes().OfType<TupleExpressionSyntax>().ElementAt(2);
            Assert.Equal("((null, 2), null)", rightTuple.ToString());
            var literalInfo = model.GetTypeInfo(rightTuple);
            Assert.Null(literalInfo.Type);
            Assert.Null(literalInfo.ConvertedType);
 
            var nestedTuple = (TupleExpressionSyntax)rightTuple.Arguments[0].Expression;
            Assert.Equal("(null, 2)", nestedTuple.ToString());
            var nestedLiteralInfo = model.GetTypeInfo(rightTuple);
            Assert.Null(nestedLiteralInfo.Type);
            Assert.Null(nestedLiteralInfo.ConvertedType);
 
            var two = nestedTuple.Arguments[1].Expression;
            Assert.Equal("2", two.ToString());
            var twoInfo = model.GetTypeInfo(two);
            Assert.Equal("System.Int32", twoInfo.Type.ToTestDisplayString());
            Assert.Equal("System.Int64", twoInfo.ConvertedType.ToTestDisplayString());
        }
 
        [Fact]
        public void TestFailedInference()
        {
            var source = @"
class C
{
    static void Main()
    {
        System.Console.Write((null, null, null, null) == (null, x => x, Main, (int i) => { int j = 0; return i + j; }));
    }
}";
            var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9);
            comp.VerifyDiagnostics(
                // (6,30): error CS0019: Operator '==' cannot be applied to operands of type '<null>' and 'lambda expression'
                //         System.Console.Write((null, null, null, null) == (null, x => x, Main, (int i) => { int j = 0; return i + j; }));
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "(null, null, null, null) == (null, x => x, Main, (int i) => { int j = 0; return i + j; })").WithArguments("==", "<null>", "lambda expression").WithLocation(6, 30),
                // (6,30): error CS0019: Operator '==' cannot be applied to operands of type '<null>' and 'method group'
                //         System.Console.Write((null, null, null, null) == (null, x => x, Main, (int i) => { int j = 0; return i + j; }));
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "(null, null, null, null) == (null, x => x, Main, (int i) => { int j = 0; return i + j; })").WithArguments("==", "<null>", "method group").WithLocation(6, 30),
                // (6,30): error CS0019: Operator '==' cannot be applied to operands of type '<null>' and 'lambda expression'
                //         System.Console.Write((null, null, null, null) == (null, x => x, Main, (int i) => { int j = 0; return i + j; }));
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "(null, null, null, null) == (null, x => x, Main, (int i) => { int j = 0; return i + j; })").WithArguments("==", "<null>", "lambda expression").WithLocation(6, 30));
            verify(comp, inferDelegate: false);
 
            comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview);
            comp.VerifyDiagnostics(
                // (6,30): error CS0019: Operator '==' cannot be applied to operands of type '<null>' and 'lambda expression'
                //         System.Console.Write((null, null, null, null) == (null, x => x, Main, (int i) => { int j = 0; return i + j; }));
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "(null, null, null, null) == (null, x => x, Main, (int i) => { int j = 0; return i + j; })").WithArguments("==", "<null>", "lambda expression").WithLocation(6, 30));
            verify(comp, inferDelegate: true);
 
            static void verify(CSharpCompilation comp, bool inferDelegate)
            {
                var tree = comp.SyntaxTrees[0];
                var model = comp.GetSemanticModel(tree);
 
                // check tuple on the left
                var tuple1 = tree.GetCompilationUnitRoot().DescendantNodes().OfType<TupleExpressionSyntax>().ElementAt(0);
                Assert.Equal("(null, null, null, null)", tuple1.ToString());
 
                var tupleType1 = model.GetTypeInfo(tuple1);
                Assert.Null(tupleType1.Type);
                Assert.Null(tupleType1.ConvertedType);
 
                // check tuple on the right ...
                var tuple2 = tree.GetCompilationUnitRoot().DescendantNodes().OfType<TupleExpressionSyntax>().ElementAt(1);
                Assert.Equal("(null, x => x, Main, (int i) => { int j = 0; return i + j; })", tuple2.ToString());
 
                var tupleType2 = model.GetTypeInfo(tuple2);
                Assert.Null(tupleType2.Type);
                Assert.Null(tupleType2.ConvertedType);
 
                // ... its first lambda ...
                var firstLambda = tuple2.Arguments[1].Expression;
                Assert.Null(model.GetTypeInfo(firstLambda).Type);
                verifyType("System.Delegate", model.GetTypeInfo(firstLambda).ConvertedType, inferDelegate: false); // cannot infer delegate type for x => x
 
                // ... its method group ...
                var methodGroup = tuple2.Arguments[2].Expression;
                Assert.Null(model.GetTypeInfo(methodGroup).Type);
                verifyType("System.Delegate", model.GetTypeInfo(methodGroup).ConvertedType, inferDelegate);
                Assert.Null(model.GetSymbolInfo(methodGroup).Symbol);
                Assert.Equal(new[] { "void C.Main()" }, model.GetSymbolInfo(methodGroup).CandidateSymbols.Select(s => s.ToTestDisplayString()));
 
                // ... its second lambda and the symbols it uses
                var secondLambda = tuple2.Arguments[3].Expression;
                verifyType("System.Func<System.Int32, System.Int32>", model.GetTypeInfo(secondLambda).Type, inferDelegate);
                verifyType("System.Delegate", model.GetTypeInfo(secondLambda).ConvertedType, inferDelegate);
 
                var addition = tree.GetCompilationUnitRoot().DescendantNodes().OfType<BinaryExpressionSyntax>().Last();
                Assert.Equal("i + j", addition.ToString());
 
                var i = addition.Left;
                Assert.Equal("System.Int32 i", model.GetSymbolInfo(i).Symbol.ToTestDisplayString());
 
                var j = addition.Right;
                Assert.Equal("System.Int32 j", model.GetSymbolInfo(j).Symbol.ToTestDisplayString());
            }
 
            static void verifyType(string expectedType, ITypeSymbol type, bool inferDelegate)
            {
                if (inferDelegate)
                {
                    Assert.Equal(expectedType, type.ToTestDisplayString());
                }
                else
                {
                    Assert.Null(type);
                }
            }
        }
 
        [Fact]
        public void TestVoidTypeElement()
        {
            var source = @"
class C
{
    static void Main()
    {
        System.Console.Write((Main(), null) != (null, Main()));
    }
}";
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (6,31): error CS8210: A tuple may not contain a value of type 'void'.
                //         System.Console.Write((Main(), null) != (null, Main()));
                Diagnostic(ErrorCode.ERR_VoidInTuple, "Main()").WithLocation(6, 31),
                // (6,55): error CS8210: A tuple may not contain a value of type 'void'.
                //         System.Console.Write((Main(), null) != (null, Main()));
                Diagnostic(ErrorCode.ERR_VoidInTuple, "Main()").WithLocation(6, 55)
                );
        }
 
        [Fact]
        public void TestFailedConversion()
        {
            var source = @"
class C
{
    static void M(string s)
    {
        System.Console.Write((s, s) == (1, () => { }));
    }
}";
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (6,30): error CS0019: Operator '==' cannot be applied to operands of type 'string' and 'int'
                //         System.Console.Write((s, s) == (1, () => { }));
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "(s, s) == (1, () => { })").WithArguments("==", "string", "int").WithLocation(6, 30),
                // (6,30): error CS0019: Operator '==' cannot be applied to operands of type 'string' and 'lambda expression'
                //         System.Console.Write((s, s) == (1, () => { }));
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "(s, s) == (1, () => { })").WithArguments("==", "string", "lambda expression").WithLocation(6, 30)
                );
 
            var tree = comp.SyntaxTrees[0];
            var model = comp.GetSemanticModel(tree);
 
            var tuple1 = tree.GetCompilationUnitRoot().DescendantNodes().OfType<TupleExpressionSyntax>().ElementAt(0);
            Assert.Equal("(s, s)", tuple1.ToString());
            var tupleType1 = model.GetTypeInfo(tuple1);
            Assert.Equal("(System.String, System.String)", tupleType1.Type.ToTestDisplayString());
            Assert.Equal("(System.String, System.String)", tupleType1.ConvertedType.ToTestDisplayString());
 
            var tuple2 = tree.GetCompilationUnitRoot().DescendantNodes().OfType<TupleExpressionSyntax>().ElementAt(1);
            Assert.Equal("(1, () => { })", tuple2.ToString());
            var tupleType2 = model.GetTypeInfo(tuple2);
            Assert.Null(tupleType2.Type);
            Assert.Null(tupleType2.ConvertedType);
        }
 
        [Fact]
        public void TestDynamic()
        {
            var source = @"
public class C
{
    public static void Main()
    {
        dynamic d1 = 1;
        dynamic d2 = 2;
        System.Console.Write($""{(d1, 2) == (1, d2)} "");
        System.Console.Write($""{(d1, 2) != (1, d2)} "");
 
        System.Console.Write($""{(d1, 20) == (10, d2)} "");
        System.Console.Write($""{(d1, 20) != (10, d2)} "");
    }
}";
            var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "True False False True");
        }
 
        [Fact]
        public void TestDynamicWithConstants()
        {
            var source = @"
public class C
{
    public static void Main()
    {
        System.Console.Write($""{((dynamic)true, (dynamic)false) == ((dynamic)true, (dynamic)false)} "");
    }
}";
            var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "True");
        }
 
        [Fact]
        public void TestDynamic_WithTypelessExpression()
        {
            var source = @"
public class C
{
    public static void Main()
    {
        dynamic d1 = 1;
        dynamic d2 = 2;
        System.Console.Write((d1, 2) == (() => 1, d2));
    }
}";
            var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics(
                // (8,30): error CS0019: Operator '==' cannot be applied to operands of type 'dynamic' and 'lambda expression'
                //         System.Console.Write((d1, 2) == (() => 1, d2));
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "(d1, 2) == (() => 1, d2)").WithArguments("==", "dynamic", "lambda expression").WithLocation(8, 30)
                );
        }
 
        [Fact]
        public void TestDynamic_WithBooleanConstants()
        {
            var source = @"
public class C
{
    public static void Main()
    {
        System.Console.Write(((dynamic)true, (dynamic)false) == (true, false));
        System.Console.Write(((dynamic)true, (dynamic)false) != (true, false));
    }
}";
            var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "TrueFalse");
        }
 
        [Fact]
        public void TestDynamic_WithBadType()
        {
            var source = @"
public class C
{
    public static void Main()
    {
        System.Globalization.CultureInfo saveUICulture = System.Threading.Thread.CurrentThread.CurrentUICulture;
        System.Threading.Thread.CurrentThread.CurrentUICulture = System.Globalization.CultureInfo.InvariantCulture;
 
        dynamic d1 = 1;
        dynamic d2 = 2;
 
        try
        {
            bool b = ((d1, 2) == (""hello"", d2));
        }
        catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException e)
        {
            System.Console.Write(e.Message);
        }
        finally
        {
            System.Threading.Thread.CurrentThread.CurrentUICulture = saveUICulture;
        }
    }
}";
            var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "Operator '==' cannot be applied to operands of type 'int' and 'string'");
        }
 
        [Fact]
        public void TestDynamic_WithNull()
        {
            var source = @"
public class C
{
    public static void Main()
    {
        dynamic d1 = null;
        dynamic d2 = null;
        System.Console.Write((d1, null) == (null, d2));
    }
}";
            var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "True");
 
            var tree = comp.SyntaxTrees[0];
            var model = comp.GetSemanticModel(tree);
 
            var tuple1 = tree.GetCompilationUnitRoot().DescendantNodes().OfType<TupleExpressionSyntax>().ElementAt(0);
            Assert.Equal("(d1, null)", tuple1.ToString());
            var tupleType1 = model.GetTypeInfo(tuple1);
            Assert.Null(tupleType1.Type);
            Assert.Equal("(dynamic d1, dynamic)", tupleType1.ConvertedType.ToTestDisplayString());
 
            var tuple2 = tree.GetCompilationUnitRoot().DescendantNodes().OfType<TupleExpressionSyntax>().ElementAt(1);
            Assert.Equal("(null, d2)", tuple2.ToString());
            var tupleType2 = model.GetTypeInfo(tuple2);
            Assert.Null(tupleType2.Type);
            Assert.Equal("(dynamic, dynamic d2)", tupleType2.ConvertedType.ToTestDisplayString());
        }
 
        [Fact]
        public void TestBadConstraintOnTuple()
        {
            // https://github.com/dotnet/roslyn/issues/37121 : This test appears to produce a duplicate diagnostic at (6, 35)
            var source = @"
ref struct S
{
    void M(S s1, S s2)
    {
        System.Console.Write(("""", s1) == (null, s2));
    }
}";
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (6,35): error CS9244: The type 'S' may not be a ref struct or a type parameter allowing ref structs in order to use it as parameter 'T2' in the generic type or method '(T1, T2)'
                //         System.Console.Write(("", s1) == (null, s2));
                Diagnostic(ErrorCode.ERR_NotRefStructConstraintNotSatisfied, "s1").WithArguments("(T1, T2)", "T2", "S").WithLocation(6, 35),
                // (6,30): error CS0019: Operator '==' cannot be applied to operands of type 'S' and 'S'
                //         System.Console.Write(("", s1) == (null, s2));
                Diagnostic(ErrorCode.ERR_BadBinaryOps, @"("""", s1) == (null, s2)").WithArguments("==", "S", "S").WithLocation(6, 30),
                // (6,35): error CS9244: The type 'S' may not be a ref struct or a type parameter allowing ref structs in order to use it as parameter 'T2' in the generic type or method '(T1, T2)'
                //         System.Console.Write(("", s1) == (null, s2));
                Diagnostic(ErrorCode.ERR_NotRefStructConstraintNotSatisfied, "s1").WithArguments("(T1, T2)", "T2", "S").WithLocation(6, 35),
                // (6,49): error CS9244: The type 'S' may not be a ref struct or a type parameter allowing ref structs in order to use it as parameter 'T2' in the generic type or method '(T1, T2)'
                //         System.Console.Write(("", s1) == (null, s2));
                Diagnostic(ErrorCode.ERR_NotRefStructConstraintNotSatisfied, "s2").WithArguments("(T1, T2)", "T2", "S").WithLocation(6, 49)
                );
        }
 
        [Fact]
        public void TestErrorInTuple()
        {
            var source = @"
public class C
{
    public void M()
    {
        if (error1 == (error2, 3)) { }
    }
}";
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (6,13): error CS0103: The name 'error1' does not exist in the current context
                //         if (error1 == (error2, 3)) { }
                Diagnostic(ErrorCode.ERR_NameNotInContext, "error1").WithArguments("error1").WithLocation(6, 13),
                // (6,24): error CS0103: The name 'error2' does not exist in the current context
                //         if (error1 == (error2, 3)) { }
                Diagnostic(ErrorCode.ERR_NameNotInContext, "error2").WithArguments("error2").WithLocation(6, 24)
                );
        }
 
        [Fact]
        public void TestWithTypelessTuple()
        {
            var source = @"
public class C
{
    public void M()
    {
        var t = (null, null);
        if (null == (() => {}) ) {}
        if ("""" == 1) {}
    }
}";
            var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9);
            comp.VerifyDiagnostics(
                // (6,13): error CS0815: Cannot assign (<null>, <null>) to an implicitly-typed variable
                //         var t = (null, null);
                Diagnostic(ErrorCode.ERR_ImplicitlyTypedVariableAssignedBadValue, "t = (null, null)").WithArguments("(<null>, <null>)").WithLocation(6, 13),
                // (7,13): error CS0019: Operator '==' cannot be applied to operands of type '<null>' and 'lambda expression'
                //         if (null == (() => {}) ) {}
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "null == (() => {})").WithArguments("==", "<null>", "lambda expression").WithLocation(7, 13),
                // (8,13): error CS0019: Operator '==' cannot be applied to operands of type 'string' and 'int'
                //         if ("" == 1) {}
                Diagnostic(ErrorCode.ERR_BadBinaryOps, @""""" == 1").WithArguments("==", "string", "int").WithLocation(8, 13)
                );
 
            comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (6,13): error CS0815: Cannot assign (<null>, <null>) to an implicitly-typed variable
                //         var t = (null, null);
                Diagnostic(ErrorCode.ERR_ImplicitlyTypedVariableAssignedBadValue, "t = (null, null)").WithArguments("(<null>, <null>)").WithLocation(6, 13),
                // (8,13): error CS0019: Operator '==' cannot be applied to operands of type 'string' and 'int'
                //         if ("" == 1) {}
                Diagnostic(ErrorCode.ERR_BadBinaryOps, @""""" == 1").WithArguments("==", "string", "int").WithLocation(8, 13)
                );
        }
 
        [Fact]
        public void TestTupleEqualityPreferredOverCustomOperator()
        {
            var source = @"
namespace System
{
    public struct ValueTuple<T1, T2>
    {
        public T1 Item1;
        public T2 Item2;
 
        public ValueTuple(T1 item1, T2 item2)
        {
            this.Item1 = item1;
            this.Item2 = item2;
        }
 
        public static bool operator ==(ValueTuple<T1, T2> t1, ValueTuple<T1, T2> t2)
            => throw null;
        public static bool operator !=(ValueTuple<T1, T2> t1, ValueTuple<T1, T2> t2)
            => throw null;
 
        public override bool Equals(object o)
            => throw null;
        public override int GetHashCode()
            => throw null;
    }
}
public class C
{
    public static void Main()
    {
        var t1 = (1, 1);
        var t2 = (2, 2);
        System.Console.Write(t1 == t2);
        System.Console.Write(t1 != t2);
    }
}
";
            var comp = CreateCompilationWithMscorlib40(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            // Note: tuple equality picked ahead of custom operator== (small compat break)
            CompileAndVerify(comp, expectedOutput: "FalseTrue");
        }
 
        [Fact]
        public void TestCustomOperatorPlusAllowed()
        {
            var source = @"
namespace System
{
    public struct ValueTuple<T1, T2>
    {
        public T1 Item1;
        public T2 Item2;
 
        public ValueTuple(T1 item1, T2 item2)
        {
            this.Item1 = item1;
            this.Item2 = item2;
        }
 
        public static ValueTuple<T1, T2> operator +(ValueTuple<T1, T2> t1, ValueTuple<T1, T2> t2)
            => (default(T1), default(T2));
 
        public override string ToString()
            => $""({Item1}, {Item2})"";
    }
}
public class C
{
    public static void Main()
    {
        var t1 = (0, 1);
        var t2 = (2, 3);
        System.Console.Write(t1 + t2);
    }
}
";
            var comp = CreateCompilationWithMscorlib40(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "(0, 0)");
        }
 
        [Fact]
        public void TestTupleEqualityPreferredOverCustomOperator_Nested()
        {
            string source = @"
public class C
{
    public static void Main()
    {
        System.Console.Write( (1, 2, (3, 4)) == (1, 2, (3, 4)) );
    }
}
namespace System
{
    public struct ValueTuple<T1, T2>
    {
        public T1 Item1;
        public T2 Item2;
 
        public ValueTuple(T1 item1, T2 item2)
        {
            this.Item1 = item1;
            this.Item2 = item2;
        }
 
        public static bool operator ==(ValueTuple<T1, T2> t1, ValueTuple<T1, T2> t2)
            => throw null;
        public static bool operator !=(ValueTuple<T1, T2> t1, ValueTuple<T1, T2> t2)
            => throw null;
 
        public override bool Equals(object o)
            => throw null;
 
        public override int GetHashCode()
            => throw null;
    }
    public struct ValueTuple<T1, T2, T3>
    {
        public T1 Item1;
        public T2 Item2;
        public T3 Item3;
 
        public ValueTuple(T1 item1, T2 item2, T3 item3)
        {
            this.Item1 = item1;
            this.Item2 = item2;
            this.Item3 = item3;
        }
    }
}
";
 
            var comp = CreateCompilationWithMscorlib40(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
 
            // Note: tuple equality picked ahead of custom operator==
            CompileAndVerify(comp, expectedOutput: "True");
        }
 
        [Fact]
        public void TestNaN()
        {
            var source = @"
public class C
{
    public static void Main()
    {
        var t1 = (System.Double.NaN, 1);
        var t2 = (System.Double.NaN, 1);
        System.Console.Write($""{t1 == t2} {t1.Equals(t2)} {t1 != t2} {t1 == (System.Double.NaN, 1)}"");
    }
}
";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "False True True False");
        }
 
        [Fact]
        public void TestTopLevelDynamic()
        {
            var source = @"
public class C
{
    public static void Main()
    {
        System.Globalization.CultureInfo saveUICulture = System.Threading.Thread.CurrentThread.CurrentUICulture;
        System.Threading.Thread.CurrentThread.CurrentUICulture = System.Globalization.CultureInfo.InvariantCulture;
        dynamic d1 = (1, 1);
 
        try
        {
            try
            {
                _ = d1 == (1, 1);
            }
            catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException e)
            {
                System.Console.WriteLine(e.Message);
            }
 
            try
            {
                _ = d1 != (1, 2);
            }
            catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException e)
            {
                System.Console.WriteLine(e.Message);
            }
        }
        finally
        {
            System.Threading.Thread.CurrentThread.CurrentUICulture = saveUICulture;
        }
    }
}
";
            var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
 
            CompileAndVerify(comp, expectedOutput:
@"Operator '==' cannot be applied to operands of type 'System.ValueTuple<int,int>' and 'System.ValueTuple<int,int>'
Operator '!=' cannot be applied to operands of type 'System.ValueTuple<int,int>' and 'System.ValueTuple<int,int>'");
        }
 
        [Fact]
        public void TestNestedDynamic()
        {
            var source = @"
public class C
{
    public static void Main()
    {
        System.Globalization.CultureInfo saveUICulture = System.Threading.Thread.CurrentThread.CurrentUICulture;
        System.Threading.Thread.CurrentThread.CurrentUICulture = System.Globalization.CultureInfo.InvariantCulture;
        dynamic d1 = (1, 1, 1);
 
        try
        {
            try
            {
                _ = (2, d1) == (2, (1, 1, 1));
            }
            catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException e)
            {
                System.Console.WriteLine(e.Message);
            }
 
            try
            {
                _ = (3, d1) != (3, (1, 2, 3));
            }
            catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException e)
            {
                System.Console.WriteLine(e.Message);
            }
        }
        finally
        {
            System.Threading.Thread.CurrentThread.CurrentUICulture = saveUICulture;
        }
    }
}
";
            var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
 
            CompileAndVerify(comp, expectedOutput:
@"Operator '==' cannot be applied to operands of type 'System.ValueTuple<int,int,int>' and 'System.ValueTuple<int,int,int>'
Operator '!=' cannot be applied to operands of type 'System.ValueTuple<int,int,int>' and 'System.ValueTuple<int,int,int>'");
        }
 
        [Fact]
        public void TestComparisonWithDeconstructionResult()
        {
            var source = @"
public class C
{
    public static void Main()
    {
        var b1 = (1, 2) == ((_, _) = new C());
        var b2 = (1, 42) != ((_, _) = new C());
        var b3 = (1, 42) == ((_, _) = new C()); // false
        var b4 = ((_, _) = new C()) == (1, 2);
        var b5 = ((_, _) = new C()) != (1, 42);
        var b6 = ((_, _) = new C()) == (1, 42); // false
        System.Console.Write($""{b1} {b2} {b3} {b4} {b5} {b6}"");
    }
    public void Deconstruct(out int x, out int y)
    {
        x = 1;
        y = 2;
    }
}
";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
 
            CompileAndVerify(comp, expectedOutput: @"True True False True True False");
        }
 
        [Fact]
        public void TestComparisonWithDeconstruction()
        {
            var source = @"
public class C
{
    public static void Main()
    {
        var b1 = (1, 2) == new C();
    }
    public void Deconstruct(out int x, out int y)
    {
        x = 1;
        y = 2;
    }
}
";
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (6,18): error CS0019: Operator '==' cannot be applied to operands of type '(int, int)' and 'C'
                //         var b1 = (1, 2) == new C();
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "(1, 2) == new C()").WithArguments("==", "(int, int)", "C").WithLocation(6, 18)
                );
        }
 
        [Fact]
        public void TestEvaluationOrderOnTupleLiteral()
        {
            var source = @"
public class C
{
    public static void Main()
    {
        System.Console.Write($""{EXPRESSION}"");
    }
}
namespace System
{
    public struct ValueTuple<T1, T2>
    {
        public T1 Item1;
        public T2 Item2;
 
        public ValueTuple(T1 item1, T2 item2)
        {
            this.Item1 = item1;
            this.Item2 = item2;
            throw null;
        }
    }
}
public class Base
{
    public int I;
    public Base(int i) { I = i; }
}
public class A : Base
{
    public A(int i) : base(i)
    {
        System.Console.Write($""A:{i}, "");
    }
    public static bool operator ==(A a, Y y)
    {
        System.Console.Write($""A({a.I}) == Y({y.I}), "");
        return a.I == y.I;
    }
    public static bool operator !=(A a, Y y)
    {
        System.Console.Write($""A({a.I}) != Y({y.I}), "");
        return a.I != y.I;
    }
    public override bool Equals(object o)
        => throw null;
    public override int GetHashCode()
        => throw null;
}
public class X : Base
{
    public X(int i) : base(i)
    {
        System.Console.Write($""X:{i}, "");
    }
}
public class Y : Base
{
    public Y(int i) : base(i)
    {
        System.Console.Write($""Y:{i}, "");
    }
    public static implicit operator Y(X x)
    {
        System.Console.Write(""X -> "");
        return new Y(x.I);
    }
}
";
 
            validate("(new A(1), new A(2)) == (new X(1), new Y(2))", "A:1, A:2, X:1, Y:2, X -> Y:1, A(1) == Y(1), A(2) == Y(2), True");
            validate("(new A(1), new A(2)) == (new X(30), new Y(40))", "A:1, A:2, X:30, Y:40, X -> Y:30, A(1) == Y(30), False");
            validate("(new A(1), new A(2)) == (new X(1), new Y(50))", "A:1, A:2, X:1, Y:50, X -> Y:1, A(1) == Y(1), A(2) == Y(50), False");
 
            validate("(new A(1), new A(2)) != (new X(1), new Y(2))", "A:1, A:2, X:1, Y:2, X -> Y:1, A(1) != Y(1), A(2) != Y(2), False");
            validate("(new A(1), new A(2)) != (new Y(1), new X(2))", "A:1, A:2, Y:1, X:2, A(1) != Y(1), X -> Y:2, A(2) != Y(2), False");
 
            validate("(new A(1), new A(2)) != (new X(30), new Y(40))", "A:1, A:2, X:30, Y:40, X -> Y:30, A(1) != Y(30), True");
            validate("(new A(1), new A(2)) != (new X(50), new Y(2))", "A:1, A:2, X:50, Y:2, X -> Y:50, A(1) != Y(50), True");
            validate("(new A(1), new A(2)) != (new X(1), new Y(60))", "A:1, A:2, X:1, Y:60, X -> Y:1, A(1) != Y(1), A(2) != Y(60), True");
 
            void validate(string expression, string expected)
            {
                var comp = CreateCompilation(source.Replace("EXPRESSION", expression), options: TestOptions.DebugExe);
                comp.VerifyDiagnostics();
                CompileAndVerify(comp, expectedOutput: expected);
            }
        }
 
        [Fact]
        public void TestEvaluationOrderOnTupleType()
        {
            var source = @"
namespace System
{
    public struct ValueTuple<T1, T2>
    {
        public T1 Item1;
        public T2 Item2;
 
        public ValueTuple(T1 item1, T2 item2)
        {
            System.Console.Write(""ValueTuple2, "");
            this.Item1 = item1;
            this.Item2 = item2;
        }
    }
    public struct ValueTuple<T1, T2, T3>
    {
        public ValueTuple(T1 item1, T2 item2, T3 item3)
        {
            // ValueTuple'3 required (constructed in bound tree), but not emitted
            throw null;
        }
    }
}
public class Base
{
    public int I;
    public Base(int i) { I = i; }
}
public class A : Base
{
    public A(int i) : base(i)
    {
        System.Console.Write($""A:{i}, "");
    }
    public static bool operator ==(A a, Y y)
    {
        System.Console.Write($""A({a.I}) == Y({y.I}), "");
        return true;
    }
    public static bool operator !=(A a, Y y)
        => throw null;
    public override bool Equals(object o)
        => throw null;
    public override int GetHashCode()
        => throw null;
}
public class X : Base
{
    public X(int i) : base(i)
    {
        System.Console.Write($""X:{i}, "");
    }
}
public class Y : Base
{
    public Y(int i) : base(i)
    {
        System.Console.Write($""Y:{i}, "");
    }
    public static implicit operator Y(X x)
    {
        System.Console.Write(""X -> "");
        return new Y(x.I);
    }
}
public class C
{
    public static void Main()
    {
        System.Console.Write($""{(new A(1), GetTuple(), new A(4)) == (new X(5), (new X(6), new Y(7)), new Y(8))}"");
    }
    public static (A, A) GetTuple()
    {
        System.Console.Write($""GetTuple, "");
        return (new A(30), new A(40));
    }
}
";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "A:1, GetTuple, A:30, A:40, ValueTuple2, A:4, X:5, X:6, Y:7, Y:8, X -> Y:5, A(1) == Y(5), X -> Y:6, A(30) == Y(6), A(40) == Y(7), A(4) == Y(8), True");
        }
 
        [Fact]
        public void TestConstrainedValueTuple()
        {
            var source = @"
class C
{
    void M()
    {
        _ = (this, this) == (0, 1); // constraint violated by tuple in source
        _ = (this, this) == (this, this); // constraint violated by converted tuple
    }
    public static bool operator ==(C c, int i)
        => throw null;
    public static bool operator !=(C c, int i)
        => throw null;
    public override bool Equals(object o)
        => throw null;
    public override int GetHashCode()
        => throw null;
    public static implicit operator int(C c)
        => throw null;
}
namespace System
{
    public struct ValueTuple<T1, T2>
        where T1 : class
        where T2 : class
    {
        public T1 Item1;
        public T2 Item2;
 
        public ValueTuple(T1 item1, T2 item2)
        {
            this.Item1 = item1;
            this.Item2 = item2;
            throw null;
        }
    }
}
";
 
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (6,30): error CS0452: The type 'int' must be a reference type in order to use it as parameter 'T1' in the generic type or method '(T1, T2)'
                //         _ = (this, this) == (0, 1); // constraint violated by tuple in source
                Diagnostic(ErrorCode.ERR_RefConstraintNotSatisfied, "0").WithArguments("(T1, T2)", "T1", "int").WithLocation(6, 30),
                // (6,30): error CS0452: The type 'int' must be a reference type in order to use it as parameter 'T1' in the generic type or method '(T1, T2)'
                //         _ = (this, this) == (0, 1); // constraint violated by tuple in source
                Diagnostic(ErrorCode.ERR_RefConstraintNotSatisfied, "0").WithArguments("(T1, T2)", "T1", "int").WithLocation(6, 30),
                // (6,33): error CS0452: The type 'int' must be a reference type in order to use it as parameter 'T2' in the generic type or method '(T1, T2)'
                //         _ = (this, this) == (0, 1); // constraint violated by tuple in source
                Diagnostic(ErrorCode.ERR_RefConstraintNotSatisfied, "1").WithArguments("(T1, T2)", "T2", "int").WithLocation(6, 33),
                // (6,33): error CS0452: The type 'int' must be a reference type in order to use it as parameter 'T2' in the generic type or method '(T1, T2)'
                //         _ = (this, this) == (0, 1); // constraint violated by tuple in source
                Diagnostic(ErrorCode.ERR_RefConstraintNotSatisfied, "1").WithArguments("(T1, T2)", "T2", "int").WithLocation(6, 33),
                // (7,30): error CS0452: The type 'int' must be a reference type in order to use it as parameter 'T1' in the generic type or method '(T1, T2)'
                //         _ = (this, this) == (this, this); // constraint violated by converted tuple
                Diagnostic(ErrorCode.ERR_RefConstraintNotSatisfied, "this").WithArguments("(T1, T2)", "T1", "int").WithLocation(7, 30),
                // (7,36): error CS0452: The type 'int' must be a reference type in order to use it as parameter 'T2' in the generic type or method '(T1, T2)'
                //         _ = (this, this) == (this, this); // constraint violated by converted tuple
                Diagnostic(ErrorCode.ERR_RefConstraintNotSatisfied, "this").WithArguments("(T1, T2)", "T2", "int").WithLocation(7, 36)
                );
 
            var tree = comp.SyntaxTrees.First();
            var model = comp.GetSemanticModel(tree);
 
            // check the int tuple
            var firstEquals = tree.GetCompilationUnitRoot().DescendantNodes().OfType<BinaryExpressionSyntax>().First();
            var intTuple = firstEquals.Right;
            Assert.Equal("(0, 1)", intTuple.ToString());
            var intTupleType = model.GetTypeInfo(intTuple);
            Assert.Equal("(System.Int32, System.Int32)", intTupleType.Type.ToTestDisplayString());
            Assert.Equal("(System.Int32, System.Int32)", intTupleType.ConvertedType.ToTestDisplayString());
 
            // check the last tuple
            var secondEquals = tree.GetCompilationUnitRoot().DescendantNodes().OfType<BinaryExpressionSyntax>().Last();
            var lastTuple = secondEquals.Right;
            Assert.Equal("(this, this)", lastTuple.ToString());
            var lastTupleType = model.GetTypeInfo(lastTuple);
            Assert.Equal("(C, C)", lastTupleType.Type.ToTestDisplayString());
            Assert.Equal("(System.Int32, System.Int32)", lastTupleType.ConvertedType.ToTestDisplayString());
        }
 
        [Fact]
        public void TestConstrainedNullable()
        {
            var source = @"
class C
{
    void M((int, int)? t1, (long, long)? t2)
    {
        _ = t1 == t2;
    }
}
public interface IInterface { }
namespace System
{
    public class Object { }
    public abstract class ValueType { }
    public struct Void { }
    public struct Boolean { }
    public struct Int32 { }
    public struct Int64 { }
    public struct Nullable<T> where T : struct, IInterface { public T GetValueOrDefault() => default(T); }
    public class Exception { }
 
    public struct ValueTuple<T1, T2>
    {
        public T1 Item1;
        public T2 Item2;
 
        public ValueTuple(T1 item1, T2 item2)
        {
            this.Item1 = item1;
            this.Item2 = item2;
            throw null;
        }
    }
}
";
 
            var comp = CreateEmptyCompilation(source);
            comp.VerifyDiagnostics(
                // (4,24): error CS0315: The type '(int, int)' cannot be used as type parameter 'T' in the generic type or method 'Nullable<T>'. There is no boxing conversion from '(int, int)' to 'IInterface'.
                //     void M((int, int)? t1, (long, long)? t2)
                Diagnostic(ErrorCode.ERR_GenericConstraintNotSatisfiedValType, "t1").WithArguments("System.Nullable<T>", "IInterface", "T", "(int, int)").WithLocation(4, 24),
                // (4,42): error CS0315: The type '(long, long)' cannot be used as type parameter 'T' in the generic type or method 'Nullable<T>'. There is no boxing conversion from '(long, long)' to 'IInterface'.
                //     void M((int, int)? t1, (long, long)? t2)
                Diagnostic(ErrorCode.ERR_GenericConstraintNotSatisfiedValType, "t2").WithArguments("System.Nullable<T>", "IInterface", "T", "(long, long)").WithLocation(4, 42)
                );
 
            var tree = comp.SyntaxTrees.First();
            var model = comp.GetSemanticModel(tree);
 
            // check t1
            var equals = tree.GetCompilationUnitRoot().DescendantNodes().OfType<BinaryExpressionSyntax>().Last();
            var t1 = equals.Left;
            Assert.Equal("t1", t1.ToString());
            var t1Type = model.GetTypeInfo(t1);
            Assert.Equal("(System.Int32, System.Int32)?", t1Type.Type.ToTestDisplayString());
            Assert.Equal("(System.Int64, System.Int64)?", t1Type.ConvertedType.ToTestDisplayString());
        }
 
        [Fact]
        public void TestEvaluationOrderOnTupleType2()
        {
            var source = @"
namespace System
{
    public struct ValueTuple<T1, T2>
    {
        public T1 Item1;
        public T2 Item2;
 
        public ValueTuple(T1 item1, T2 item2)
        {
            System.Console.Write(""ValueTuple2, "");
            this.Item1 = item1;
            this.Item2 = item2;
        }
    }
    public struct ValueTuple<T1, T2, T3>
    {
        public ValueTuple(T1 item1, T2 item2, T3 item3)
        {
            // ValueTuple'3 required (constructed in bound tree), but not emitted
            throw null;
        }
    }
}
public class Base
{
    public int I;
    public Base(int i) { I = i; }
}
public class A : Base
{
    public A(int i) : base(i)
    {
        System.Console.Write($""A:{i}, "");
    }
    public static bool operator ==(A a, Y y)
    {
        System.Console.Write($""A({a.I}) == Y({y.I}), "");
        return true;
    }
    public static bool operator !=(A a, Y y)
        => throw null;
    public override bool Equals(object o)
        => throw null;
    public override int GetHashCode()
        => throw null;
}
public class X : Base
{
    public X(int i) : base(i)
    {
        System.Console.Write($""X:{i}, "");
    }
}
public class Y : Base
{
    public Y(int i) : base(i)
    {
        System.Console.Write($""Y:{i}, "");
    }
    public static implicit operator Y(X x)
    {
        System.Console.Write($""X:{x.I} -> "");
        return new Y(x.I);
    }
}
public class C
{
    public static void Main()
    {
        System.Console.Write($""{(new A(1), (new A(2), new A(3)), new A(4)) == (new X(5), GetTuple(), new Y(8))}"");
    }
    public static (X, Y) GetTuple()
    {
        System.Console.Write($""GetTuple, "");
        return (new X(6), new Y(7));
    }
}
";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: @"A:1, A:2, A:3, A:4, X:5, GetTuple, X:6, Y:7, ValueTuple2, Y:8, X:5 -> Y:5, A(1) == Y(5), X:6 -> Y:6, A(2) == Y(6), A(3) == Y(7), A(4) == Y(8), True");
        }
 
        [Fact]
        public void TestEvaluationOrderOnTupleType3()
        {
            var source = @"
namespace System
{
    public struct ValueTuple<T1, T2>
    {
        public T1 Item1;
        public T2 Item2;
 
        public ValueTuple(T1 item1, T2 item2)
        {
            System.Console.Write(""ValueTuple2, "");
            this.Item1 = item1;
            this.Item2 = item2;
        }
    }
    public struct ValueTuple<T1, T2, T3>
    {
        public ValueTuple(T1 item1, T2 item2, T3 item3)
        {
            // ValueTuple'3 required (constructed in bound tree), but not emitted
            throw null;
        }
    }
}
public class Base
{
    public int I;
    public Base(int i) { I = i; }
}
public class A : Base
{
    public A(int i) : base(i)
    {
        System.Console.Write($""A:{i}, "");
    }
    public static bool operator ==(A a, Y y)
    {
        System.Console.Write($""A({a.I}) == Y({y.I}), "");
        return true;
    }
    public static bool operator !=(A a, Y y)
        => throw null;
    public override bool Equals(object o)
        => throw null;
    public override int GetHashCode()
        => throw null;
}
public class X : Base
{
    public X(int i) : base(i)
    {
        System.Console.Write($""X:{i}, "");
    }
}
public class Y : Base
{
    public Y(int i) : base(i)
    {
        System.Console.Write($""Y:{i}, "");
    }
    public static implicit operator Y(X x)
    {
        System.Console.Write(""X -> "");
        return new Y(x.I);
    }
}
public class C
{
    public static void Main()
    {
        System.Console.Write($""{GetTuple() == (new X(6), new Y(7))}"");
    }
    public static (A, A) GetTuple()
    {
        System.Console.Write($""GetTuple, "");
        return (new A(30), new A(40));
    }
}
";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "GetTuple, A:30, A:40, ValueTuple2, X:6, Y:7, X -> Y:6, A(30) == Y(6), A(40) == Y(7), True");
        }
 
        [Fact]
        public void TestObsoleteEqualityOperator()
        {
            var source = @"
class C
{
    void M()
    {
        System.Console.WriteLine($""{(new A(), new A()) == (new X(), new Y())}"");
        System.Console.WriteLine($""{(new A(), new A()) != (new X(), new Y())}"");
    }
}
public class A
{
    [System.Obsolete(""obsolete"", true)]
    public static bool operator ==(A a, Y y)
        => throw null;
    [System.Obsolete(""obsolete too"", true)]
    public static bool operator !=(A a, Y y)
        => throw null;
    public override bool Equals(object o)
        => throw null;
    public override int GetHashCode()
        => throw null;
}
public class X
{
}
public class Y
{
    public static implicit operator Y(X x)
        => throw null;
}
";
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (6,37): error CS0619: 'A.operator ==(A, Y)' is obsolete: 'obsolete'
                //         System.Console.WriteLine($"{(new A(), new A()) == (new X(), new Y())}");
                Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "(new A(), new A()) == (new X(), new Y())").WithArguments("A.operator ==(A, Y)", "obsolete").WithLocation(6, 37),
                // (6,37): error CS0619: 'A.operator ==(A, Y)' is obsolete: 'obsolete'
                //         System.Console.WriteLine($"{(new A(), new A()) == (new X(), new Y())}");
                Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "(new A(), new A()) == (new X(), new Y())").WithArguments("A.operator ==(A, Y)", "obsolete").WithLocation(6, 37),
                // (7,37): error CS0619: 'A.operator !=(A, Y)' is obsolete: 'obsolete too'
                //         System.Console.WriteLine($"{(new A(), new A()) != (new X(), new Y())}");
                Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "(new A(), new A()) != (new X(), new Y())").WithArguments("A.operator !=(A, Y)", "obsolete too").WithLocation(7, 37),
                // (7,37): error CS0619: 'A.operator !=(A, Y)' is obsolete: 'obsolete too'
                //         System.Console.WriteLine($"{(new A(), new A()) != (new X(), new Y())}");
                Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "(new A(), new A()) != (new X(), new Y())").WithArguments("A.operator !=(A, Y)", "obsolete too").WithLocation(7, 37)
                );
        }
 
        [Fact]
        public void TestDefiniteAssignment()
        {
            var source = @"
class C
{
    void M()
    {
        int error1;
        System.Console.Write((1, 2) == (error1, 2));
 
        int error2;
        System.Console.Write((1, (error2, 3)) == (1, (2, 3)));
    }
}
";
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (7,41): error CS0165: Use of unassigned local variable 'error1'
                //         System.Console.Write((1, 2) == (error1, 2));
                Diagnostic(ErrorCode.ERR_UseDefViolation, "error1").WithArguments("error1").WithLocation(7, 41),
                // (10,35): error CS0165: Use of unassigned local variable 'error2'
                //         System.Console.Write((1, (error2, 3)) == (1, (2, 3)));
                Diagnostic(ErrorCode.ERR_UseDefViolation, "error2").WithArguments("error2").WithLocation(10, 35)
                );
        }
 
        [Fact]
        public void TestDefiniteAssignment2()
        {
            var source = @"
class C
{
    int M(out int x)
    {
        _ = (M(out int y), y) == (1, 2); // ok
        _ = (z, M(out int z)) == (1, 2); // error
        x = 1;
        return 2;
    }
}
";
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                    // (7,14): error CS0841: Cannot use local variable 'z' before it is declared
                    //         _ = (z, M(out int z)) == (1, 2); // error
                    Diagnostic(ErrorCode.ERR_VariableUsedBeforeDeclaration, "z").WithArguments("z").WithLocation(7, 14)
                );
        }
 
        [Fact]
        public void TestEqualityOfTypeConvertingToTuple()
        {
            var source = @"
class C
{
    private int i;
    void M()
    {
        System.Console.Write(this == (1, 1));
        System.Console.Write((1, 1) == this);
    }
    public static implicit operator (int, int)(C c)
    {
        return (c.i, c.i);
    }
    C(int i) { this.i = i; }
}
";
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (7,30): error CS0019: Operator '==' cannot be applied to operands of type 'C' and '(int, int)'
                //         System.Console.Write(this == (1, 1));
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "this == (1, 1)").WithArguments("==", "C", "(int, int)").WithLocation(7, 30),
                // (8,30): error CS0019: Operator '==' cannot be applied to operands of type '(int, int)' and 'C'
                //         System.Console.Write((1, 1) == this);
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "(1, 1) == this").WithArguments("==", "(int, int)", "C").WithLocation(8, 30)
                );
        }
 
        [Fact]
        public void TestEqualityOfTypeConvertingFromTuple()
        {
            var source = @"
class C
{
    private int i;
    public static void Main()
    {
        var c = new C(2);
        System.Console.Write(c == (1, 1));
        System.Console.Write((1, 1) == c);
    }
    public static implicit operator C((int, int) x)
    {
        return new C(x.Item1 + x.Item2);
    }
    public static bool operator ==(C c1, C c2)
        => c1.i == c2.i;
    public static bool operator !=(C c1, C c2)
        => throw null;
    public override int GetHashCode()
        => throw null;
    public override bool Equals(object other)
        => throw null;
    C(int i) { this.i = i; }
}
";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "TrueTrue");
        }
 
        [Fact]
        public void TestEqualityOfTypeComparableWithTuple()
        {
            var source = @"
class C
{
    private static void Main()
    {
        System.Console.Write(new C() == (1, 1));
        System.Console.Write(new C() != (1, 1));
    }
    public static bool operator ==(C c, (int, int) t)
    {
        return t.Item1 + t.Item2 == 2;
    }
    public static bool operator !=(C c, (int, int) t)
    {
        return t.Item1 + t.Item2 != 2;
    }
    public override bool Equals(object o)
        => throw null;
    public override int GetHashCode()
        => throw null;
}
";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "TrueFalse");
        }
 
        [Fact]
        public void TestOfTwoUnrelatedTypes()
        {
            var source = @"
class A { }
class C
{
    static void M()
    {
        System.Console.Write(new C() == new A());
        System.Console.Write((1, new C()) == (1, new A()));
    }
}
";
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (7,30): error CS0019: Operator '==' cannot be applied to operands of type 'C' and 'A'
                //         System.Console.Write(new C() == new A());
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "new C() == new A()").WithArguments("==", "C", "A").WithLocation(7, 30),
                // (8,30): error CS0019: Operator '==' cannot be applied to operands of type 'C' and 'A'
                //         System.Console.Write((1, new C()) == (1, new A()));
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "(1, new C()) == (1, new A())").WithArguments("==", "C", "A").WithLocation(8, 30)
                );
        }
 
        [Fact]
        public void TestOfTwoUnrelatedTypes2()
        {
            var source = @"
class A { }
class C
{
    static void M(string s, System.Exception e)
    {
        System.Console.Write(s == 3);
        System.Console.Write((1, s) == (1, e));
    }
}
";
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (7,30): error CS0019: Operator '==' cannot be applied to operands of type 'string' and 'int'
                //         System.Console.Write(s == 3);
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "s == 3").WithArguments("==", "string", "int").WithLocation(7, 30),
                // (8,30): error CS0019: Operator '==' cannot be applied to operands of type 'string' and 'Exception'
                //         System.Console.Write((1, s) == (1, e));
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "(1, s) == (1, e)").WithArguments("==", "string", "System.Exception").WithLocation(8, 30)
                );
        }
 
        [Fact]
        public void TestBadRefCompare()
        {
            var source = @"
class C
{
    static void M()
    {
        string s = ""11"";
        object o = s + s;
        (object, object) t = default;
 
        bool b = o == s;
        bool b2 = t == (s, s); // no warning
    }
}
";
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (10,18): warning CS0252: Possible unintended reference comparison; to get a value comparison, cast the left hand side to type 'string'
                //         bool b = o == s;
                Diagnostic(ErrorCode.WRN_BadRefCompareLeft, "o == s").WithArguments("string").WithLocation(10, 18)
                );
        }
 
        [Fact, WorkItem(27047, "https://github.com/dotnet/roslyn/issues/27047")]
        public void TestWithObsoleteImplicitConversion()
        {
            var source = @"
class C
{
    private static bool TupleEquals((C, int)? nt1, (int, C) nt2)
        => nt1 == nt2; // warn 1 and 2
 
    private static bool TupleNotEquals((C, int)? nt1, (int, C) nt2)
        => nt1 != nt2; // warn 3 and 4
 
    [System.Obsolete(""obsolete"", error: true)]
    public static implicit operator int(C c)
        => throw null;
}
";
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (5,12): error CS0619: 'C.implicit operator int(C)' is obsolete: 'obsolete'
                //         => nt1 == nt2; // warn 1 and 2
                Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "nt1").WithArguments("C.implicit operator int(C)", "obsolete").WithLocation(5, 12),
                // (5,12): error CS0619: 'C.implicit operator int(C)' is obsolete: 'obsolete'
                //         => nt1 == nt2; // warn 1 and 2
                Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "nt1").WithArguments("C.implicit operator int(C)", "obsolete").WithLocation(5, 12),
                // (5,19): error CS0619: 'C.implicit operator int(C)' is obsolete: 'obsolete'
                //         => nt1 == nt2; // warn 1 and 2
                Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "nt2").WithArguments("C.implicit operator int(C)", "obsolete").WithLocation(5, 19),
                // (5,19): error CS0619: 'C.implicit operator int(C)' is obsolete: 'obsolete'
                //         => nt1 == nt2; // warn 1 and 2
                Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "nt2").WithArguments("C.implicit operator int(C)", "obsolete").WithLocation(5, 19),
                // (8,12): error CS0619: 'C.implicit operator int(C)' is obsolete: 'obsolete'
                //         => nt1 != nt2; // warn 3 and 4
                Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "nt1").WithArguments("C.implicit operator int(C)", "obsolete").WithLocation(8, 12),
                // (8,12): error CS0619: 'C.implicit operator int(C)' is obsolete: 'obsolete'
                //         => nt1 != nt2; // warn 3 and 4
                Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "nt1").WithArguments("C.implicit operator int(C)", "obsolete").WithLocation(8, 12),
                // (8,19): error CS0619: 'C.implicit operator int(C)' is obsolete: 'obsolete'
                //         => nt1 != nt2; // warn 3 and 4
                Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "nt2").WithArguments("C.implicit operator int(C)", "obsolete").WithLocation(8, 19),
                // (8,19): error CS0619: 'C.implicit operator int(C)' is obsolete: 'obsolete'
                //         => nt1 != nt2; // warn 3 and 4
                Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "nt2").WithArguments("C.implicit operator int(C)", "obsolete").WithLocation(8, 19)
                );
        }
 
        [Fact, WorkItem(27047, "https://github.com/dotnet/roslyn/issues/27047")]
        public void TestWithObsoleteBoolConversion()
        {
            var source = @"
public class A
{
    public static bool TupleEquals((A, A) t) => t == t; // warn 1 and 2
    public static bool TupleNotEquals((A, A) t) => t != t; // warn 3 and 4
 
    public static NotBool operator ==(A a1, A a2) => throw null;
    public static NotBool operator !=(A a1, A a2) => throw null;
    public override bool Equals(object o) => throw null;
    public override int GetHashCode() => throw null;
}
public class NotBool
{
    [System.Obsolete(""obsolete"", error: true)]
    public static implicit operator bool(NotBool b) => throw null;
}
";
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (4,49): error CS0619: 'NotBool.implicit operator bool(NotBool)' is obsolete: 'obsolete'
                //     public static bool TupleEquals((A, A) t) => t == t; // warn 1 and 2
                Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "t == t").WithArguments("NotBool.implicit operator bool(NotBool)", "obsolete").WithLocation(4, 49),
                // (4,49): error CS0619: 'NotBool.implicit operator bool(NotBool)' is obsolete: 'obsolete'
                //     public static bool TupleEquals((A, A) t) => t == t; // warn 1 and 2
                Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "t == t").WithArguments("NotBool.implicit operator bool(NotBool)", "obsolete").WithLocation(4, 49),
                // (5,52): error CS0619: 'NotBool.implicit operator bool(NotBool)' is obsolete: 'obsolete'
                //     public static bool TupleNotEquals((A, A) t) => t != t; // warn 3 and 4
                Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "t != t").WithArguments("NotBool.implicit operator bool(NotBool)", "obsolete").WithLocation(5, 52),
                // (5,52): error CS0619: 'NotBool.implicit operator bool(NotBool)' is obsolete: 'obsolete'
                //     public static bool TupleNotEquals((A, A) t) => t != t; // warn 3 and 4
                Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "t != t").WithArguments("NotBool.implicit operator bool(NotBool)", "obsolete").WithLocation(5, 52)
                );
        }
 
        [Fact, WorkItem(27047, "https://github.com/dotnet/roslyn/issues/27047")]
        public void TestWithObsoleteComparisonOperators()
        {
            var source = @"
public class A
{
    public static bool TupleEquals((A, A) t) => t == t; // warn 1 and 2
    public static bool TupleNotEquals((A, A) t) => t != t; // warn 3 and 4
 
    [System.Obsolete("""", error: true)]
    public static bool operator ==(A a1, A a2) => throw null;
    [System.Obsolete("""", error: true)]
    public static bool operator !=(A a1, A a2) => throw null;
 
    public override bool Equals(object o) => throw null;
    public override int GetHashCode() => throw null;
}
";
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (4,49): error CS0619: 'A.operator ==(A, A)' is obsolete: ''
                //     public static bool TupleEquals((A, A) t) => t == t; // warn 1 and 2
                Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "t == t").WithArguments("A.operator ==(A, A)", "").WithLocation(4, 49),
                // (4,49): error CS0619: 'A.operator ==(A, A)' is obsolete: ''
                //     public static bool TupleEquals((A, A) t) => t == t; // warn 1 and 2
                Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "t == t").WithArguments("A.operator ==(A, A)", "").WithLocation(4, 49),
                // (5,52): error CS0619: 'A.operator !=(A, A)' is obsolete: ''
                //     public static bool TupleNotEquals((A, A) t) => t != t; // warn 3 and 4
                Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "t != t").WithArguments("A.operator !=(A, A)", "").WithLocation(5, 52),
                // (5,52): error CS0619: 'A.operator !=(A, A)' is obsolete: ''
                //     public static bool TupleNotEquals((A, A) t) => t != t; // warn 3 and 4
                Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "t != t").WithArguments("A.operator !=(A, A)", "").WithLocation(5, 52)
                );
        }
 
        [Fact, WorkItem(27047, "https://github.com/dotnet/roslyn/issues/27047")]
        public void TestWithObsoleteTruthOperators()
        {
            var source = @"
public class A
{
    public static bool TupleEquals((A, A) t) => t == t; // warn 1 and 2
    public static bool TupleNotEquals((A, A) t) => t != t; // warn 3 and 4
 
    public static NotBool operator ==(A a1, A a2) => throw null;
    public static NotBool operator !=(A a1, A a2) => throw null;
    public override bool Equals(object o) => throw null;
    public override int GetHashCode() => throw null;
}
public class NotBool
{
    [System.Obsolete(""obsolete"", error: true)]
    public static bool operator true(NotBool b) => throw null;
 
    [System.Obsolete(""obsolete"", error: true)]
    public static bool operator false(NotBool b) => throw null;
}
";
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (4,49): error CS0619: 'NotBool.operator false(NotBool)' is obsolete: 'obsolete'
                //     public static bool TupleEquals((A, A) t) => t == t; // warn 1 and 2
                Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "t == t").WithArguments("NotBool.operator false(NotBool)", "obsolete").WithLocation(4, 49),
                // (4,49): error CS0619: 'NotBool.operator false(NotBool)' is obsolete: 'obsolete'
                //     public static bool TupleEquals((A, A) t) => t == t; // warn 1 and 2
                Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "t == t").WithArguments("NotBool.operator false(NotBool)", "obsolete").WithLocation(4, 49),
                // (5,52): error CS0619: 'NotBool.operator true(NotBool)' is obsolete: 'obsolete'
                //     public static bool TupleNotEquals((A, A) t) => t != t; // warn 3 and 4
                Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "t != t").WithArguments("NotBool.operator true(NotBool)", "obsolete").WithLocation(5, 52),
                // (5,52): error CS0619: 'NotBool.operator true(NotBool)' is obsolete: 'obsolete'
                //     public static bool TupleNotEquals((A, A) t) => t != t; // warn 3 and 4
                Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "t != t").WithArguments("NotBool.operator true(NotBool)", "obsolete").WithLocation(5, 52)
                );
        }
 
        [Fact]
        public void TestEqualOnNullableVsNullableTuples()
        {
            var source = @"
class C
{
    public static void Main()
    {
        Compare(null, null);
        Compare(null, (1, 2));
        Compare((2, 3), null);
        Compare((4, 4), (4, 4));
        Compare((5, 5), (10, 10));
    }
    private static void Compare((int, int)? nt1, (int, int)? nt2)
    {
        System.Console.Write($""{nt1 == nt2} "");
    }
}
";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            var verifier = CompileAndVerify(comp, expectedOutput: "True False False True False");
 
            var tree = comp.SyntaxTrees.First();
            var model = comp.GetSemanticModel(tree);
 
            var comparison = tree.GetCompilationUnitRoot().DescendantNodes().OfType<BinaryExpressionSyntax>().Single();
            var nt1 = comparison.Left;
            var nt1Type = model.GetTypeInfo(nt1);
            Assert.Equal("nt1", nt1.ToString());
            Assert.Equal("(System.Int32, System.Int32)?", nt1Type.Type.ToTestDisplayString());
            Assert.Equal("(System.Int32, System.Int32)?", nt1Type.ConvertedType.ToTestDisplayString());
 
            var nt2 = comparison.Right;
            var nt2Type = model.GetTypeInfo(nt2);
            Assert.Equal("nt2", nt2.ToString());
            Assert.Equal("(System.Int32, System.Int32)?", nt2Type.Type.ToTestDisplayString());
            Assert.Equal("(System.Int32, System.Int32)?", nt2Type.ConvertedType.ToTestDisplayString());
 
            verifier.VerifyIL("C.Compare", @"{
  // Code size      104 (0x68)
  .maxstack  3
  .locals init (System.ValueTuple<int, int>? V_0,
                System.ValueTuple<int, int>? V_1,
                bool V_2,
                System.ValueTuple<int, int> V_3,
                System.ValueTuple<int, int> V_4)
  IL_0000:  nop
  IL_0001:  ldstr      ""{0} ""
  IL_0006:  ldarg.0
  IL_0007:  stloc.0
  IL_0008:  ldarg.1
  IL_0009:  stloc.1
  IL_000a:  ldloca.s   V_0
  IL_000c:  call       ""bool System.ValueTuple<int, int>?.HasValue.get""
  IL_0011:  stloc.2
  IL_0012:  ldloc.2
  IL_0013:  ldloca.s   V_1
  IL_0015:  call       ""bool System.ValueTuple<int, int>?.HasValue.get""
  IL_001a:  beq.s      IL_001f
  IL_001c:  ldc.i4.0
  IL_001d:  br.s       IL_0057
  IL_001f:  ldloc.2
  IL_0020:  brtrue.s   IL_0025
  IL_0022:  ldc.i4.1
  IL_0023:  br.s       IL_0057
  IL_0025:  ldloca.s   V_0
  IL_0027:  call       ""System.ValueTuple<int, int> System.ValueTuple<int, int>?.GetValueOrDefault()""
  IL_002c:  stloc.3
  IL_002d:  ldloca.s   V_1
  IL_002f:  call       ""System.ValueTuple<int, int> System.ValueTuple<int, int>?.GetValueOrDefault()""
  IL_0034:  stloc.s    V_4
  IL_0036:  ldloc.3
  IL_0037:  ldfld      ""int System.ValueTuple<int, int>.Item1""
  IL_003c:  ldloc.s    V_4
  IL_003e:  ldfld      ""int System.ValueTuple<int, int>.Item1""
  IL_0043:  bne.un.s   IL_0056
  IL_0045:  ldloc.3
  IL_0046:  ldfld      ""int System.ValueTuple<int, int>.Item2""
  IL_004b:  ldloc.s    V_4
  IL_004d:  ldfld      ""int System.ValueTuple<int, int>.Item2""
  IL_0052:  ceq
  IL_0054:  br.s       IL_0057
  IL_0056:  ldc.i4.0
  IL_0057:  box        ""bool""
  IL_005c:  call       ""string string.Format(string, object)""
  IL_0061:  call       ""void System.Console.Write(string)""
  IL_0066:  nop
  IL_0067:  ret
}");
        }
 
        [Fact]
        public void TestEqualOnNullableVsNullableTuples_NeverNull()
        {
            var source = @"
class C
{
    public static void Main()
    {
        System.Console.Write(((int, int)?) (1, 2) == ((int, int)?) (1, 2));
    }
}
";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            var verifier = CompileAndVerify(comp, expectedOutput: "True");
            verifier.VerifyIL("C.Main", @"
{
    // Code size       19 (0x13)
    .maxstack  2
    IL_0000:  nop
    IL_0001:  ldc.i4.1
    IL_0002:  ldc.i4.1
    IL_0003:  bne.un.s   IL_000b
    IL_0005:  ldc.i4.2
    IL_0006:  ldc.i4.2
    IL_0007:  ceq
    IL_0009:  br.s       IL_000c
    IL_000b:  ldc.i4.0
    IL_000c:  call       ""void System.Console.Write(bool)""
    IL_0011:  nop
    IL_0012:  ret
}
");
        }
 
        [Fact]
        public void TestEqualOnNullableVsNullableTuples_OneSideNeverNull()
        {
            var source = @"
class C
{
    public static void Main()
    {
        System.Console.Write(((int, int)?) (1, 2) == (1, 2));
    }
}
";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            var verifier = CompileAndVerify(comp, expectedOutput: "True");
            verifier.VerifyIL("C.Main", @"
{
    // Code size       19 (0x13)
    .maxstack  2
    IL_0000:  nop
    IL_0001:  ldc.i4.1
    IL_0002:  ldc.i4.1
    IL_0003:  bne.un.s   IL_000b
    IL_0005:  ldc.i4.2
    IL_0006:  ldc.i4.2
    IL_0007:  ceq
    IL_0009:  br.s       IL_000c
    IL_000b:  ldc.i4.0
    IL_000c:  call       ""void System.Console.Write(bool)""
    IL_0011:  nop
    IL_0012:  ret
}
");
        }
 
        [Fact]
        public void TestEqualOnNullableVsNullableTuples_Tuple_AlwaysNull_AlwaysNull()
        {
            var source = @"
class C
{
    public static void Main()
    {
        System.Console.Write(((int, int)?)null == ((int, int)?)null);
    }
}
";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            var verifier = CompileAndVerify(comp, expectedOutput: "True");
            verifier.VerifyIL("C.Main", @"{
  // Code size        9 (0x9)
  .maxstack  1
  IL_0000:  nop
  IL_0001:  ldc.i4.1
  IL_0002:  call       ""void System.Console.Write(bool)""
  IL_0007:  nop
  IL_0008:  ret
}
");
 
            CompileAndVerify(source, options: TestOptions.ReleaseExe).VerifyIL("C.Main", @"{
  // Code size        7 (0x7)
  .maxstack  1
  IL_0000:  ldc.i4.1
  IL_0001:  call       ""void System.Console.Write(bool)""
  IL_0006:  ret
}
");
        }
 
        [Fact]
        public void TestEqualOnNullableVsNullableTuples_Tuple_MaybeNull_AlwaysNull()
        {
            var source = @"
class C
{
    public static void Main()
    {
        M(null);
        M((1, 2));
    }
    public static void M((int, int)? t)
    {
        System.Console.Write(t == ((int, int)?)null);
    }
}
";
 
            CompileAndVerify(source, expectedOutput: "TrueFalse", options: TestOptions.ReleaseExe).VerifyIL("C.M", @"{
  // Code size       18 (0x12)
  .maxstack  2
  .locals init (System.ValueTuple<int, int>? V_0)
  IL_0000:  ldarg.0
  IL_0001:  stloc.0
  IL_0002:  ldloca.s   V_0
  IL_0004:  call       ""bool System.ValueTuple<int, int>?.HasValue.get""
  IL_0009:  ldc.i4.0
  IL_000a:  ceq
  IL_000c:  call       ""void System.Console.Write(bool)""
  IL_0011:  ret
}
");
        }
 
        [Fact]
        public void TestNotEqualOnNullableVsNullableTuples_Tuple_MaybeNull_AlwaysNull()
        {
            var source = @"
class C
{
    public static void Main()
    {
        M(null);
        M((1, 2));
    }
    public static void M((int, int)? t)
    {
        System.Console.Write(t != ((int, int)?)null);
    }
}
";
 
            CompileAndVerify(source, expectedOutput: "FalseTrue", options: TestOptions.ReleaseExe).VerifyIL("C.M", @"{
  // Code size       15 (0xf)
  .maxstack  1
  .locals init (System.ValueTuple<int, int>? V_0)
  IL_0000:  ldarg.0
  IL_0001:  stloc.0
  IL_0002:  ldloca.s   V_0
  IL_0004:  call       ""bool System.ValueTuple<int, int>?.HasValue.get""
  IL_0009:  call       ""void System.Console.Write(bool)""
  IL_000e:  ret
}
");
        }
 
        [Fact]
        public void TestNotEqualOnNullableVsNullableTuples_Tuple_AlwaysNull_MaybeNull()
        {
            var source = @"
class C
{
    public static void Main()
    {
        M(null);
        M((1, 2));
    }
    public static void M((int, int)? t)
    {
        System.Console.Write(((int, int)?)null == t);
    }
}
";
 
            CompileAndVerify(source, expectedOutput: "TrueFalse", options: TestOptions.ReleaseExe).VerifyIL("C.M", @"{
  // Code size       18 (0x12)
  .maxstack  2
  .locals init (System.ValueTuple<int, int>? V_0)
  IL_0000:  ldarg.0
  IL_0001:  stloc.0
  IL_0002:  ldloca.s   V_0
  IL_0004:  call       ""bool System.ValueTuple<int, int>?.HasValue.get""
  IL_0009:  ldc.i4.0
  IL_000a:  ceq
  IL_000c:  call       ""void System.Console.Write(bool)""
  IL_0011:  ret
}
");
        }
 
        [Fact]
        public void TestEqualOnNullableVsNullableTuples_Tuple_NeverNull_AlwaysNull()
        {
            var source = @"
class C
{
    public static void Main()
    {
        System.Console.Write((1, 2) == ((int, int)?)null);
    }
}
";
 
            CompileAndVerify(source, expectedOutput: "False", options: TestOptions.ReleaseExe).VerifyIL("C.Main", @"{
  // Code size        7 (0x7)
  .maxstack  1
  IL_0000:  ldc.i4.0
  IL_0001:  call       ""void System.Console.Write(bool)""
  IL_0006:  ret
}
");
        }
 
        [Fact]
        public void TestEqualOnNullableVsNullableTuples_Tuple_AlwaysNull_MaybeNull()
        {
            var source = @"
class C
{
    public static void Main()
    {
        M(null);
        M((1, 2));
    }
    public static void M((int, int)? t)
    {
        System.Console.Write(((int, int)?)null == t);
    }
}
";
 
            CompileAndVerify(source, expectedOutput: "TrueFalse", options: TestOptions.ReleaseExe).VerifyIL("C.M", @"{
  // Code size       18 (0x12)
  .maxstack  2
  .locals init (System.ValueTuple<int, int>? V_0)
  IL_0000:  ldarg.0
  IL_0001:  stloc.0
  IL_0002:  ldloca.s   V_0
  IL_0004:  call       ""bool System.ValueTuple<int, int>?.HasValue.get""
  IL_0009:  ldc.i4.0
  IL_000a:  ceq
  IL_000c:  call       ""void System.Console.Write(bool)""
  IL_0011:  ret
}
");
        }
 
        [Fact]
        public void TestEqualOnNullableVsNullableTuples_Tuple_AlwaysNull_NeverNull()
        {
            var source = @"
class C
{
    public static void Main()
    {
        System.Console.Write(((int, int)?)null == (1, 2));
    }
}
";
 
            CompileAndVerify(source, expectedOutput: "False", options: TestOptions.ReleaseExe).VerifyIL("C.Main", @"{
  // Code size        7 (0x7)
  .maxstack  1
  IL_0000:  ldc.i4.0
  IL_0001:  call       ""void System.Console.Write(bool)""
  IL_0006:  ret
}
");
        }
 
        [Fact]
        public void TestEqualOnNullableVsNullableTuples_ElementAlwaysNull()
        {
            var source = @"
class C
{
    public static void Main()
    {
        System.Console.Write((null, null) == (new int?(), new int?()));
    }
}
";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            var verifier = CompileAndVerify(comp, expectedOutput: "True");
            verifier.VerifyIL("C.Main", @"{
  // Code size        9 (0x9)
  .maxstack  1
  IL_0000:  nop
  IL_0001:  ldc.i4.1
  IL_0002:  call       ""void System.Console.Write(bool)""
  IL_0007:  nop
  IL_0008:  ret
}
");
        }
 
        [Fact]
        public void TestNotEqualOnNullableVsNullableTuples()
        {
            var source = @"
class C
{
    public static void Main()
    {
        Compare(null, null);
        Compare(null, (1, 2));
        Compare((2, 3), null);
        Compare((4, 4), (4, 4));
        Compare((5, 5), (10, 10));
    }
    private static void Compare((int, int)? nt1, (int, int)? nt2)
    {
        System.Console.Write($""{nt1 != nt2} "");
    }
}
";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            var verifier = CompileAndVerify(comp, expectedOutput: "False True True False True");
 
            var tree = comp.SyntaxTrees.First();
            var model = comp.GetSemanticModel(tree);
 
            var comparison = tree.GetCompilationUnitRoot().DescendantNodes().OfType<BinaryExpressionSyntax>().Single();
            var nt1 = comparison.Left;
            var nt1Type = model.GetTypeInfo(nt1);
            Assert.Equal("nt1", nt1.ToString());
            Assert.Equal("(System.Int32, System.Int32)?", nt1Type.Type.ToTestDisplayString());
            Assert.Equal("(System.Int32, System.Int32)?", nt1Type.ConvertedType.ToTestDisplayString());
 
            var nt2 = comparison.Right;
            var nt2Type = model.GetTypeInfo(nt2);
            Assert.Equal("nt2", nt2.ToString());
            Assert.Equal("(System.Int32, System.Int32)?", nt2Type.Type.ToTestDisplayString());
            Assert.Equal("(System.Int32, System.Int32)?", nt2Type.ConvertedType.ToTestDisplayString());
 
            verifier.VerifyIL("C.Compare", @"{
  // Code size      107 (0x6b)
  .maxstack  3
  .locals init (System.ValueTuple<int, int>? V_0,
                System.ValueTuple<int, int>? V_1,
                bool V_2,
                System.ValueTuple<int, int> V_3,
                System.ValueTuple<int, int> V_4)
  IL_0000:  nop
  IL_0001:  ldstr      ""{0} ""
  IL_0006:  ldarg.0
  IL_0007:  stloc.0
  IL_0008:  ldarg.1
  IL_0009:  stloc.1
  IL_000a:  ldloca.s   V_0
  IL_000c:  call       ""bool System.ValueTuple<int, int>?.HasValue.get""
  IL_0011:  stloc.2
  IL_0012:  ldloc.2
  IL_0013:  ldloca.s   V_1
  IL_0015:  call       ""bool System.ValueTuple<int, int>?.HasValue.get""
  IL_001a:  beq.s      IL_001f
  IL_001c:  ldc.i4.1
  IL_001d:  br.s       IL_005a
  IL_001f:  ldloc.2
  IL_0020:  brtrue.s   IL_0025
  IL_0022:  ldc.i4.0
  IL_0023:  br.s       IL_005a
  IL_0025:  ldloca.s   V_0
  IL_0027:  call       ""System.ValueTuple<int, int> System.ValueTuple<int, int>?.GetValueOrDefault()""
  IL_002c:  stloc.3
  IL_002d:  ldloca.s   V_1
  IL_002f:  call       ""System.ValueTuple<int, int> System.ValueTuple<int, int>?.GetValueOrDefault()""
  IL_0034:  stloc.s    V_4
  IL_0036:  ldloc.3
  IL_0037:  ldfld      ""int System.ValueTuple<int, int>.Item1""
  IL_003c:  ldloc.s    V_4
  IL_003e:  ldfld      ""int System.ValueTuple<int, int>.Item1""
  IL_0043:  bne.un.s   IL_0059
  IL_0045:  ldloc.3
  IL_0046:  ldfld      ""int System.ValueTuple<int, int>.Item2""
  IL_004b:  ldloc.s    V_4
  IL_004d:  ldfld      ""int System.ValueTuple<int, int>.Item2""
  IL_0052:  ceq
  IL_0054:  ldc.i4.0
  IL_0055:  ceq
  IL_0057:  br.s       IL_005a
  IL_0059:  ldc.i4.1
  IL_005a:  box        ""bool""
  IL_005f:  call       ""string string.Format(string, object)""
  IL_0064:  call       ""void System.Console.Write(string)""
  IL_0069:  nop
  IL_006a:  ret
}");
        }
 
        [Fact]
        public void TestNotEqualOnNullableVsNullableNestedTuples()
        {
            var source = @"
class C
{
    public static void Main()
    {
        Compare((1, null), (1, null), true);
        Compare(null, (1, (2, 3)), false);
        Compare((1, (2, 3)), (1, null), false);
        Compare((1, (4, 4)), (1, (4, 4)), true);
        Compare((1, (5, 5)), (1, (10, 10)), false);
        System.Console.Write(""Success"");
    }
    private static void Compare((int, (int, int)?)? nt1, (int, (int, int)?)? nt2, bool expectMatch)
    {
        if (expectMatch != (nt1 == nt2) || expectMatch == (nt1 != nt2))
        {
            throw null;
        }
     }
}
";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "Success");
        }
 
        [Fact]
        public void TestEqualOnNullableVsNullableTuples_WithImplicitConversion()
        {
            var source = @"
class C
{
    public static void Main()
    {
        Compare(null, null);
        Compare(null, (1, 2));
        Compare((2, 3), null);
        Compare((4, 4), (4, 4));
        Compare((5, 5), (10, 10));
    }
    private static void Compare((int, int)? nt1, (byte, long)? nt2)
    {
        System.Console.Write($""{nt1 == nt2} "");
    }
}
";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            var verifier = CompileAndVerify(comp, expectedOutput: "True False False True False");
 
            var tree = comp.SyntaxTrees.First();
            var model = comp.GetSemanticModel(tree);
 
            var comparison = tree.GetCompilationUnitRoot().DescendantNodes().OfType<BinaryExpressionSyntax>().Single();
            var nt1 = comparison.Left;
            var nt1Type = model.GetTypeInfo(nt1);
            Assert.Equal("nt1", nt1.ToString());
            Assert.Equal("(System.Int32, System.Int32)?", nt1Type.Type.ToTestDisplayString());
            Assert.Equal("(System.Int32, System.Int64)?", nt1Type.ConvertedType.ToTestDisplayString());
 
            var nt2 = comparison.Right;
            var nt2Type = model.GetTypeInfo(nt2);
            Assert.Equal("nt2", nt2.ToString());
            Assert.Equal("(System.Byte, System.Int64)?", nt2Type.Type.ToTestDisplayString());
            Assert.Equal("(System.Int32, System.Int64)?", nt2Type.ConvertedType.ToTestDisplayString());
 
            verifier.VerifyIL("C.Compare", @"{
  // Code size      105 (0x69)
  .maxstack  3
  .locals init (System.ValueTuple<int, int>? V_0,
                System.ValueTuple<byte, long>? V_1,
                bool V_2,
                System.ValueTuple<int, int> V_3,
                System.ValueTuple<byte, long> V_4)
  IL_0000:  nop
  IL_0001:  ldstr      ""{0} ""
  IL_0006:  ldarg.0
  IL_0007:  stloc.0
  IL_0008:  ldarg.1
  IL_0009:  stloc.1
  IL_000a:  ldloca.s   V_0
  IL_000c:  call       ""bool System.ValueTuple<int, int>?.HasValue.get""
  IL_0011:  stloc.2
  IL_0012:  ldloc.2
  IL_0013:  ldloca.s   V_1
  IL_0015:  call       ""bool System.ValueTuple<byte, long>?.HasValue.get""
  IL_001a:  beq.s      IL_001f
  IL_001c:  ldc.i4.0
  IL_001d:  br.s       IL_0058
  IL_001f:  ldloc.2
  IL_0020:  brtrue.s   IL_0025
  IL_0022:  ldc.i4.1
  IL_0023:  br.s       IL_0058
  IL_0025:  ldloca.s   V_0
  IL_0027:  call       ""System.ValueTuple<int, int> System.ValueTuple<int, int>?.GetValueOrDefault()""
  IL_002c:  stloc.3
  IL_002d:  ldloca.s   V_1
  IL_002f:  call       ""System.ValueTuple<byte, long> System.ValueTuple<byte, long>?.GetValueOrDefault()""
  IL_0034:  stloc.s    V_4
  IL_0036:  ldloc.3
  IL_0037:  ldfld      ""int System.ValueTuple<int, int>.Item1""
  IL_003c:  ldloc.s    V_4
  IL_003e:  ldfld      ""byte System.ValueTuple<byte, long>.Item1""
  IL_0043:  bne.un.s   IL_0057
  IL_0045:  ldloc.3
  IL_0046:  ldfld      ""int System.ValueTuple<int, int>.Item2""
  IL_004b:  conv.i8
  IL_004c:  ldloc.s    V_4
  IL_004e:  ldfld      ""long System.ValueTuple<byte, long>.Item2""
  IL_0053:  ceq
  IL_0055:  br.s       IL_0058
  IL_0057:  ldc.i4.0
  IL_0058:  box        ""bool""
  IL_005d:  call       ""string string.Format(string, object)""
  IL_0062:  call       ""void System.Console.Write(string)""
  IL_0067:  nop
  IL_0068:  ret
}");
        }
 
        [Fact]
        public void TestOnNullableVsNullableTuples_WithImplicitCustomConversion()
        {
            var source = @"
class C
{
    int _value;
    public C(int v) { _value = v; }
 
    public static void Main()
    {
        Compare(null, null);
        Compare(null, (1, new C(20)));
        Compare((new C(30), 3), null);
        Compare((new C(4), 4), (4, new C(4)));
        Compare((new C(5), 5), (10, new C(10)));
        Compare((new C(6), 6), (6, new C(20)));
    }
    private static void Compare((C, int)? nt1, (int, C)? nt2)
    {
        System.Console.Write($""{nt1 == nt2} "");
    }
    public static implicit operator int(C c)
    {
        System.Console.Write($""Convert{c._value} "");
        return c._value;
    }
}
";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            var verifier = CompileAndVerify(comp, expectedOutput: "True False False Convert4 Convert4 True Convert5 False Convert6 Convert20 False ");
 
            var tree = comp.SyntaxTrees.First();
            var model = comp.GetSemanticModel(tree);
 
            var comparison = tree.GetCompilationUnitRoot().DescendantNodes().OfType<BinaryExpressionSyntax>().Single();
            var nt1 = comparison.Left;
            var nt1Type = model.GetTypeInfo(nt1);
            Assert.Equal("nt1", nt1.ToString());
            Assert.Equal("(C, System.Int32)?", nt1Type.Type.ToTestDisplayString());
            Assert.Equal("(System.Int32, System.Int32)?", nt1Type.ConvertedType.ToTestDisplayString());
 
            var nt2 = comparison.Right;
            var nt2Type = model.GetTypeInfo(nt2);
            Assert.Equal("nt2", nt2.ToString());
            Assert.Equal("(System.Int32, C)?", nt2Type.Type.ToTestDisplayString());
            Assert.Equal("(System.Int32, System.Int32)?", nt2Type.ConvertedType.ToTestDisplayString());
 
            verifier.VerifyIL("C.Compare", @"{
  // Code size      114 (0x72)
  .maxstack  3
  .locals init (System.ValueTuple<C, int>? V_0,
                System.ValueTuple<int, C>? V_1,
                bool V_2,
                System.ValueTuple<C, int> V_3,
                System.ValueTuple<int, C> V_4)
  IL_0000:  nop
  IL_0001:  ldstr      ""{0} ""
  IL_0006:  ldarg.0
  IL_0007:  stloc.0
  IL_0008:  ldarg.1
  IL_0009:  stloc.1
  IL_000a:  ldloca.s   V_0
  IL_000c:  call       ""bool System.ValueTuple<C, int>?.HasValue.get""
  IL_0011:  stloc.2
  IL_0012:  ldloc.2
  IL_0013:  ldloca.s   V_1
  IL_0015:  call       ""bool System.ValueTuple<int, C>?.HasValue.get""
  IL_001a:  beq.s      IL_001f
  IL_001c:  ldc.i4.0
  IL_001d:  br.s       IL_0061
  IL_001f:  ldloc.2
  IL_0020:  brtrue.s   IL_0025
  IL_0022:  ldc.i4.1
  IL_0023:  br.s       IL_0061
  IL_0025:  ldloca.s   V_0
  IL_0027:  call       ""System.ValueTuple<C, int> System.ValueTuple<C, int>?.GetValueOrDefault()""
  IL_002c:  stloc.3
  IL_002d:  ldloca.s   V_1
  IL_002f:  call       ""System.ValueTuple<int, C> System.ValueTuple<int, C>?.GetValueOrDefault()""
  IL_0034:  stloc.s    V_4
  IL_0036:  ldloc.3
  IL_0037:  ldfld      ""C System.ValueTuple<C, int>.Item1""
  IL_003c:  call       ""int C.op_Implicit(C)""
  IL_0041:  ldloc.s    V_4
  IL_0043:  ldfld      ""int System.ValueTuple<int, C>.Item1""
  IL_0048:  bne.un.s   IL_0060
  IL_004a:  ldloc.3
  IL_004b:  ldfld      ""int System.ValueTuple<C, int>.Item2""
  IL_0050:  ldloc.s    V_4
  IL_0052:  ldfld      ""C System.ValueTuple<int, C>.Item2""
  IL_0057:  call       ""int C.op_Implicit(C)""
  IL_005c:  ceq
  IL_005e:  br.s       IL_0061
  IL_0060:  ldc.i4.0
  IL_0061:  box        ""bool""
      IL_0066:  call       ""string string.Format(string, object)""
      IL_006b:  call       ""void System.Console.Write(string)""
      IL_0070:  nop
      IL_0071:  ret
}");
        }
 
        [Fact]
        public void TestOnNullableVsNonNullableTuples()
        {
            var source = @"
class C
{
    public static void Main()
    {
        M(null);
        M((1, 2));
        M((10, 20));
    }
    private static void M((byte, int)? nt)
    {
        System.Console.Write((1, 2) == nt);
    }
}
";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
 
            var verifier = CompileAndVerify(comp, expectedOutput: "FalseTrueFalse");
            verifier.VerifyIL("C.M", @"{
  // Code size       53 (0x35)
  .maxstack  2
  .locals init (System.ValueTuple<byte, int>? V_0,
                System.ValueTuple<byte, int> V_1)
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  stloc.0
  IL_0003:  ldloca.s   V_0
  IL_0005:  call       ""bool System.ValueTuple<byte, int>?.HasValue.get""
  IL_000a:  brtrue.s   IL_000f
  IL_000c:  ldc.i4.0
  IL_000d:  br.s       IL_002e
  IL_000f:  br.s       IL_0011
  IL_0011:  ldloca.s   V_0
  IL_0013:  call       ""System.ValueTuple<byte, int> System.ValueTuple<byte, int>?.GetValueOrDefault()""
  IL_0018:  stloc.1
  IL_0019:  ldc.i4.1
  IL_001a:  ldloc.1
  IL_001b:  ldfld      ""byte System.ValueTuple<byte, int>.Item1""
  IL_0020:  bne.un.s   IL_002d
  IL_0022:  ldc.i4.2
  IL_0023:  ldloc.1
  IL_0024:  ldfld      ""int System.ValueTuple<byte, int>.Item2""
  IL_0029:  ceq
  IL_002b:  br.s       IL_002e
  IL_002d:  ldc.i4.0
  IL_002e:  call       ""void System.Console.Write(bool)""
  IL_0033:  nop
  IL_0034:  ret
}");
 
            var tree = comp.SyntaxTrees.First();
            var model = comp.GetSemanticModel(tree);
 
            var comparison = tree.GetCompilationUnitRoot().DescendantNodes().OfType<BinaryExpressionSyntax>().Single();
            var tuple = comparison.Left;
            var tupleType = model.GetTypeInfo(tuple);
            Assert.Equal("(1, 2)", tuple.ToString());
            Assert.Equal("(System.Int32, System.Int32)", tupleType.Type.ToTestDisplayString());
            Assert.Equal("(System.Int32, System.Int32)?", tupleType.ConvertedType.ToTestDisplayString());
 
            var nt = comparison.Right;
            var ntType = model.GetTypeInfo(nt);
            Assert.Equal("nt", nt.ToString());
            Assert.Equal("(System.Byte, System.Int32)?", ntType.Type.ToTestDisplayString());
            Assert.Equal("(System.Int32, System.Int32)?", ntType.ConvertedType.ToTestDisplayString());
        }
 
        [Fact]
        public void TestOnNullableVsNonNullableTuples_WithCustomConversion()
        {
            var source = @"
class C
{
    int _value;
    public C(int v) { _value = v; }
    public static void Main()
    {
        M(null);
        M((new C(1), 2));
        M((new C(10), 20));
    }
    private static void M((C, int)? nt)
    {
        System.Console.Write($""{(1, 2) == nt} "");
        System.Console.Write($""{nt == (1, 2)} "");
    }
    public static implicit operator int(C c)
    {
        System.Console.Write($""Convert{c._value} "");
        return c._value;
    }
}";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
 
            var verifier = CompileAndVerify(comp, expectedOutput: "False False Convert1 True Convert1 True Convert10 False Convert10 False");
 
            var tree = comp.SyntaxTrees.First();
            var model = comp.GetSemanticModel(tree);
 
            var comparison = tree.GetCompilationUnitRoot().DescendantNodes().OfType<BinaryExpressionSyntax>().First();
            Assert.Equal("(1, 2) == nt", comparison.ToString());
            var tuple = comparison.Left;
            var tupleType = model.GetTypeInfo(tuple);
            Assert.Equal("(1, 2)", tuple.ToString());
            Assert.Equal("(System.Int32, System.Int32)", tupleType.Type.ToTestDisplayString());
            Assert.Equal("(System.Int32, System.Int32)?", tupleType.ConvertedType.ToTestDisplayString());
 
            var nt = comparison.Right;
            var ntType = model.GetTypeInfo(nt);
            Assert.Equal("nt", nt.ToString());
            Assert.Equal("(C, System.Int32)?", ntType.Type.ToTestDisplayString());
            Assert.Equal("(System.Int32, System.Int32)?", ntType.ConvertedType.ToTestDisplayString());
        }
 
        [Fact]
        public void TestOnNullableVsNonNullableTuples3()
        {
            var source = @"
class C
{
    public static void Main()
    {
        M(null);
        M((1, 2));
        M((10, 20));
    }
    private static void M((int, int)? nt)
    {
        System.Console.Write(nt == (1, 2));
    }
}
";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
 
            var verifier = CompileAndVerify(comp, expectedOutput: "FalseTrueFalse");
            verifier.VerifyIL("C.M", @"{
  // Code size       59 (0x3b)
  .maxstack  2
  .locals init (System.ValueTuple<int, int>? V_0,
                bool V_1,
                System.ValueTuple<int, int> V_2)
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  stloc.0
  IL_0003:  ldloca.s   V_0
  IL_0005:  call       ""bool System.ValueTuple<int, int>?.HasValue.get""
  IL_000a:  stloc.1
  IL_000b:  ldloc.1
  IL_000c:  brtrue.s   IL_0011
  IL_000e:  ldc.i4.0
  IL_000f:  br.s       IL_0034
  IL_0011:  ldloc.1
  IL_0012:  brtrue.s   IL_0017
  IL_0014:  ldc.i4.1
  IL_0015:  br.s       IL_0034
  IL_0017:  ldloca.s   V_0
  IL_0019:  call       ""System.ValueTuple<int, int> System.ValueTuple<int, int>?.GetValueOrDefault()""
  IL_001e:  stloc.2
  IL_001f:  ldloc.2
  IL_0020:  ldfld      ""int System.ValueTuple<int, int>.Item1""
  IL_0025:  ldc.i4.1
  IL_0026:  bne.un.s   IL_0033
  IL_0028:  ldloc.2
  IL_0029:  ldfld      ""int System.ValueTuple<int, int>.Item2""
  IL_002e:  ldc.i4.2
  IL_002f:  ceq
  IL_0031:  br.s       IL_0034
  IL_0033:  ldc.i4.0
  IL_0034:  call       ""void System.Console.Write(bool)""
  IL_0039:  nop
  IL_003a:  ret
}");
        }
 
        [Fact]
        public void TestOnNullableVsLiteralTuples()
        {
            var source = @"
class C
{
    public static void Main()
    {
        CheckNull(null);
        CheckNull((1, 2));
    }
    private static void CheckNull((int, int)? nt)
    {
        System.Console.Write($""{nt == null} "");
        System.Console.Write($""{nt != null} "");
    }
}
";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "True False False True");
 
            var tree = comp.SyntaxTrees[0];
            var model = comp.GetSemanticModel(tree);
 
            var lastNull = tree.GetCompilationUnitRoot().DescendantNodes().OfType<LiteralExpressionSyntax>().Last();
            Assert.Equal("null", lastNull.ToString());
            var nullType = model.GetTypeInfo(lastNull);
            Assert.Null(nullType.Type);
            Assert.Null(nullType.ConvertedType); // In nullable-null comparison, the null literal remains typeless
        }
 
        [Fact]
        public void TestOnLongTuple()
        {
            var source = @"
class C
{
    public static void Main()
    {
        Assert(MakeLongTuple(1) == MakeLongTuple(1));
        Assert(!(MakeLongTuple(1) != MakeLongTuple(1)));
 
        Assert(MakeLongTuple(1) == (1, 1, 1, 1, 1, 1, 1, 1, 1));
        Assert(!(MakeLongTuple(1) != (1, 1, 1, 1, 1, 1, 1, 1, 1)));
 
        Assert(MakeLongTuple(1) == MakeLongTuple(1, 1));
        Assert(!(MakeLongTuple(1) != MakeLongTuple(1, 1)));
 
        Assert(!(MakeLongTuple(1) == MakeLongTuple(1, 2)));
        Assert(!(MakeLongTuple(1) == MakeLongTuple(2, 1)));
        Assert(MakeLongTuple(1) != MakeLongTuple(1, 2));
        Assert(MakeLongTuple(1) != MakeLongTuple(2, 1));
 
        System.Console.Write(""Success"");
    }
    private static (int, int, int, int, int, int, int, int, int) MakeLongTuple(int x)
        => (x, x, x, x, x, x, x, x, x);
 
    private static (int?, int, int?, int, int?, int, int?, int, int?)? MakeLongTuple(int? x, int y)
        => (x, y, x, y, x, y, x, y, x);
 
    private static void Assert(bool test)
    {
        if (!test)
        {
            throw null;
        }
    }
}
";
 
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "Success");
        }
 
        [Fact]
        public void TestOn1Tuple_FromRest()
        {
            var source = @"
class C
{
    public static bool M()
    {
        var x1 = MakeLongTuple(1).Rest;
 
        bool b1 = x1 == x1;
        bool b2 = x1 != x1;
 
        return b1 && b2;
    }
    private static (int, int, int, int, int, int, int, int?) MakeLongTuple(int? x)
        => throw null;
 
    public bool Unused((int, int, int, int, int, int, int, int?) t)
    {
        return t.Rest == t.Rest;
    }
}
";
 
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (8,19): error CS0019: Operator '==' cannot be applied to operands of type 'ValueTuple<int?>' and 'ValueTuple<int?>'
                //         bool b1 = x1 == x1;
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "x1 == x1").WithArguments("==", "System.ValueTuple<int?>", "System.ValueTuple<int?>").WithLocation(8, 19),
                // (9,19): error CS0019: Operator '!=' cannot be applied to operands of type 'ValueTuple<int?>' and 'ValueTuple<int?>'
                //         bool b2 = x1 != x1;
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "x1 != x1").WithArguments("!=", "System.ValueTuple<int?>", "System.ValueTuple<int?>").WithLocation(9, 19),
                // (18,16): error CS0019: Operator '==' cannot be applied to operands of type 'ValueTuple<int?>' and 'ValueTuple<int?>'
                //         return t.Rest == t.Rest;
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "t.Rest == t.Rest").WithArguments("==", "System.ValueTuple<int?>", "System.ValueTuple<int?>").WithLocation(18, 16)
                );
 
            var tree = comp.SyntaxTrees.First();
            var model = comp.GetSemanticModel(tree);
            var comparison = tree.GetRoot().DescendantNodes().OfType<BinaryExpressionSyntax>().Last();
            Assert.Equal("t.Rest == t.Rest", comparison.ToString());
 
            var left = model.GetTypeInfo(comparison.Left);
            Assert.Equal("System.ValueTuple<System.Int32?>", left.Type.ToTestDisplayString());
            Assert.Equal("System.ValueTuple<System.Int32?>", left.ConvertedType.ToTestDisplayString());
            Assert.True(left.Type.IsTupleType);
        }
 
        [Fact]
        public void TestOn1Tuple_FromValueTuple()
        {
            var source = @"
using System;
class C
{
    public static bool M()
    {
        var x1 = ValueTuple.Create((int?)1);
 
        bool b1 = x1 == x1;
        bool b2 = x1 != x1;
 
        return b1 && b2;
    }
}
";
 
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (9,19): error CS0019: Operator '==' cannot be applied to operands of type 'ValueTuple<int?>' and 'ValueTuple<int?>'
                //         bool b1 = x1 == x1;
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "x1 == x1").WithArguments("==", "System.ValueTuple<int?>", "System.ValueTuple<int?>").WithLocation(9, 19),
                // (10,19): error CS0019: Operator '!=' cannot be applied to operands of type 'ValueTuple<int?>' and 'ValueTuple<int?>'
                //         bool b2 = x1 != x1;
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "x1 != x1").WithArguments("!=", "System.ValueTuple<int?>", "System.ValueTuple<int?>").WithLocation(10, 19)
                );
        }
 
        [Fact]
        public void TestOnTupleOfDecimals()
        {
            var source = @"
class C
{
    public static void Main()
    {
        System.Console.Write(Compare((1, 2), (1, 2)));
        System.Console.Write(Compare((1, 2), (10, 20)));
    }
    public static bool Compare((decimal, decimal) t1, (decimal, decimal) t2)
    {
        return t1 == t2;
    }
}
";
 
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            var verifier = CompileAndVerify(comp, expectedOutput: "TrueFalse");
            verifier.VerifyIL("C.Compare", @"{
  // Code size       49 (0x31)
  .maxstack  2
  .locals init (System.ValueTuple<decimal, decimal> V_0,
                System.ValueTuple<decimal, decimal> V_1,
                bool V_2)
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  stloc.0
  IL_0003:  ldarg.1
  IL_0004:  stloc.1
  IL_0005:  ldloc.0
  IL_0006:  ldfld      ""decimal System.ValueTuple<decimal, decimal>.Item1""
  IL_000b:  ldloc.1
  IL_000c:  ldfld      ""decimal System.ValueTuple<decimal, decimal>.Item1""
  IL_0011:  call       ""bool decimal.op_Equality(decimal, decimal)""
  IL_0016:  brfalse.s  IL_002b
  IL_0018:  ldloc.0
  IL_0019:  ldfld      ""decimal System.ValueTuple<decimal, decimal>.Item2""
  IL_001e:  ldloc.1
  IL_001f:  ldfld      ""decimal System.ValueTuple<decimal, decimal>.Item2""
  IL_0024:  call       ""bool decimal.op_Equality(decimal, decimal)""
  IL_0029:  br.s       IL_002c
  IL_002b:  ldc.i4.0
  IL_002c:  stloc.2
  IL_002d:  br.s       IL_002f
  IL_002f:  ldloc.2
  IL_0030:  ret
}");
        }
 
        [Fact]
        public void TestSideEffectsAreSavedToTemps()
        {
            var source = @"
class C
{
    public static void Main()
    {
        int i = 0;
        System.Console.Write((i++, i++, i++) == (0, 1, 2));
    }
}
";
 
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            var verifier = CompileAndVerify(comp, expectedOutput: "True");
        }
 
        [Fact]
        public void TestNonBoolComparisonResult_WithTrueFalseOperators()
        {
            var source = @"
using static System.Console;
public class C
{
    public static void Main()
    {
        Write($""{REPLACE}"");
    }
}
public class Base
{
    public int I;
    public Base(int i) { I = i; }
}
public class A : Base
{
    public A(int i) : base(i)
    {
        Write($""A:{i}, "");
    }
    public static NotBool operator ==(A a, Y y)
    {
        Write(""A == Y, "");
        return new NotBool(a.I == y.I);
    }
    public static NotBool operator !=(A a, Y y)
    {
        Write(""A != Y, "");
        return new NotBool(a.I != y.I);
    }
    public override bool Equals(object o)
        => throw null;
    public override int GetHashCode()
        => throw null;
}
public class X : Base
{
    public X(int i) : base(i)
    {
        Write($""X:{i}, "");
    }
}
public class Y : Base
{
    public Y(int i) : base(i)
    {
        Write($""Y:{i}, "");
    }
    public static implicit operator Y(X x)
    {
        Write(""X -> "");
        return new Y(x.I);
    }
}
public class NotBool
{
    public bool B;
    public NotBool(bool value)
    {
        B = value;
    }
    public static bool operator true(NotBool b)
    {
        Write($""NotBool.true -> {b.B}, "");
        return b.B;
    }
    public static bool operator false(NotBool b)
    {
        Write($""NotBool.false -> {!b.B}, "");
        return !b.B;
    }
}
";
 
            validate("(new A(1), new A(2)) == (new X(1), new Y(2))", "A:1, A:2, X:1, Y:2, X -> Y:1, A == Y, NotBool.false -> False, A == Y, NotBool.false -> False, True");
            validate("(new A(1), new A(2)) == (new X(1), new Y(20))", "A:1, A:2, X:1, Y:20, X -> Y:1, A == Y, NotBool.false -> False, A == Y, NotBool.false -> True, False");
 
            validate("(new A(1), new A(2)) != (new X(1), new Y(2))", "A:1, A:2, X:1, Y:2, X -> Y:1, A != Y, NotBool.true -> False, A != Y, NotBool.true -> False, False");
            validate("(new A(1), new A(2)) != (new X(1), new Y(20))", "A:1, A:2, X:1, Y:20, X -> Y:1, A != Y, NotBool.true -> False, A != Y, NotBool.true -> True, True");
 
            validate("((dynamic)new A(1), new A(2)) == (new X(1), (dynamic)new Y(2))", "A:1, A:2, X:1, Y:2, X -> Y:1, A == Y, NotBool.false -> False, A == Y, NotBool.false -> False, True");
            validate("((dynamic)new A(1), new A(2)) == (new X(1), (dynamic)new Y(20))", "A:1, A:2, X:1, Y:20, X -> Y:1, A == Y, NotBool.false -> False, A == Y, NotBool.false -> True, False");
 
            validate("((dynamic)new A(1), new A(2)) != (new X(1), (dynamic)new Y(2))", "A:1, A:2, X:1, Y:2, X -> Y:1, A != Y, NotBool.true -> False, A != Y, NotBool.true -> False, False");
            validate("((dynamic)new A(1), new A(2)) != (new X(1), (dynamic)new Y(20))", "A:1, A:2, X:1, Y:20, X -> Y:1, A != Y, NotBool.true -> False, A != Y, NotBool.true -> True, True");
 
            void validate(string expression, string expected)
            {
                var comp = CreateCompilation(source.Replace("REPLACE", expression),
                    targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.DebugExe);
 
                comp.VerifyDiagnostics();
                CompileAndVerify(comp, expectedOutput: expected);
            }
        }
 
        [Fact]
        public void TestNonBoolComparisonResult_WithTrueFalseOperatorsOnBase()
        {
            var source = @"
using static System.Console;
public class C
{
    public static void Main()
    {
        Write($""{REPLACE}"");
    }
}
public class Base
{
    public int I;
    public Base(int i) { I = i; }
}
public class A : Base
{
    public A(int i) : base(i)
    {
        Write($""A:{i}, "");
    }
    public static NotBool operator ==(A a, Y y)
    {
        Write(""A == Y, "");
        return new NotBool(a.I == y.I);
    }
    public static NotBool operator !=(A a, Y y)
    {
        Write(""A != Y, "");
        return new NotBool(a.I != y.I);
    }
    public override bool Equals(object o)
        => throw null;
    public override int GetHashCode()
        => throw null;
}
public class X : Base
{
    public X(int i) : base(i)
    {
        Write($""X:{i}, "");
    }
}
public class Y : Base
{
    public Y(int i) : base(i)
    {
        Write($""Y:{i}, "");
    }
    public static implicit operator Y(X x)
    {
        Write(""X -> "");
        return new Y(x.I);
    }
}
public class NotBoolBase
{
    public bool B;
    public NotBoolBase(bool value)
    {
        B = value;
    }
    public static bool operator true(NotBoolBase b)
    {
        Write($""NotBoolBase.true -> {b.B}, "");
        return b.B;
    }
    public static bool operator false(NotBoolBase b)
    {
        Write($""NotBoolBase.false -> {!b.B}, "");
        return !b.B;
    }
}
public class NotBool : NotBoolBase
{
    public NotBool(bool value) : base(value) { }
}
";
 
            // This tests the case where the custom operators false/true need an input conversion that's not just an identity conversion (in this case, it's an implicit reference conversion)
            validate("(new A(1), new A(2)) == (new X(1), new Y(2))", "A:1, A:2, X:1, Y:2, X -> Y:1, A == Y, NotBoolBase.false -> False, A == Y, NotBoolBase.false -> False, True");
            validate("(new A(1), new A(2)) == (new X(1), new Y(20))", "A:1, A:2, X:1, Y:20, X -> Y:1, A == Y, NotBoolBase.false -> False, A == Y, NotBoolBase.false -> True, False");
 
            validate("(new A(1), new A(2)) != (new X(1), new Y(2))", "A:1, A:2, X:1, Y:2, X -> Y:1, A != Y, NotBoolBase.true -> False, A != Y, NotBoolBase.true -> False, False");
            validate("(new A(1), new A(2)) != (new X(1), new Y(20))", "A:1, A:2, X:1, Y:20, X -> Y:1, A != Y, NotBoolBase.true -> False, A != Y, NotBoolBase.true -> True, True");
 
            validate("((dynamic)new A(1), new A(2)) == (new X(1), (dynamic)new Y(2))", "A:1, A:2, X:1, Y:2, X -> Y:1, A == Y, NotBoolBase.false -> False, A == Y, NotBoolBase.false -> False, True");
            validate("((dynamic)new A(1), new A(2)) == (new X(1), (dynamic)new Y(20))", "A:1, A:2, X:1, Y:20, X -> Y:1, A == Y, NotBoolBase.false -> False, A == Y, NotBoolBase.false -> True, False");
 
            validate("((dynamic)new A(1), new A(2)) != (new X(1), (dynamic)new Y(2))", "A:1, A:2, X:1, Y:2, X -> Y:1, A != Y, NotBoolBase.true -> False, A != Y, NotBoolBase.true -> False, False");
            validate("((dynamic)new A(1), new A(2)) != (new X(1), (dynamic)new Y(20))", "A:1, A:2, X:1, Y:20, X -> Y:1, A != Y, NotBoolBase.true -> False, A != Y, NotBoolBase.true -> True, True");
 
            void validate(string expression, string expected)
            {
                var comp = CreateCompilation(source.Replace("REPLACE", expression),
                    targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.DebugExe);
 
                comp.VerifyDiagnostics();
                CompileAndVerify(comp, expectedOutput: expected);
            }
        }
 
        [Fact]
        public void TestNonBoolComparisonResult_WithImplicitBoolConversion()
        {
            var source = @"
using static System.Console;
public class C
{
    public static void Main()
    {
        Write($""{REPLACE}"");
    }
}
public class Base
{
    public int I;
    public Base(int i) { I = i; }
}
public class A : Base
{
    public A(int i) : base(i)
    {
        Write($""A:{i}, "");
    }
    public static NotBool operator ==(A a, Y y)
    {
        Write(""A == Y, "");
        return new NotBool(a.I == y.I);
    }
    public static NotBool operator !=(A a, Y y)
    {
        Write(""A != Y, "");
        return new NotBool(a.I != y.I);
    }
    public override bool Equals(object o)
        => throw null;
    public override int GetHashCode()
        => throw null;
}
public class X : Base
{
    public X(int i) : base(i)
    {
        Write($""X:{i}, "");
    }
}
public class Y : Base
{
    public Y(int i) : base(i)
    {
        Write($""Y:{i}, "");
    }
    public static implicit operator Y(X x)
    {
        Write(""X -> "");
        return new Y(x.I);
    }
}
public class NotBool
{
    public bool B;
    public NotBool(bool value)
    {
        B = value;
    }
    public static implicit operator bool(NotBool b)
    {
        Write($""NotBool -> bool:{b.B}, "");
        return b.B;
    }
}
";
 
            validate("(new A(1), new A(2)) == (new X(1), new Y(2))", "A:1, A:2, X:1, Y:2, X -> Y:1, A == Y, NotBool -> bool:True, A == Y, NotBool -> bool:True, True");
            validate("(new A(1), new A(2)) == (new X(10), new Y(2))", "A:1, A:2, X:10, Y:2, X -> Y:10, A == Y, NotBool -> bool:False, False");
            validate("(new A(1), new A(2)) == (new X(1), new Y(20))", "A:1, A:2, X:1, Y:20, X -> Y:1, A == Y, NotBool -> bool:True, A == Y, NotBool -> bool:False, False");
 
            validate("(new A(1), new A(2)) != (new X(1), new Y(2))", "A:1, A:2, X:1, Y:2, X -> Y:1, A != Y, NotBool -> bool:False, A != Y, NotBool -> bool:False, False");
            validate("(new A(1), new A(2)) != (new X(10), new Y(2))", "A:1, A:2, X:10, Y:2, X -> Y:10, A != Y, NotBool -> bool:True, True");
            validate("(new A(1), new A(2)) != (new X(1), new Y(20))", "A:1, A:2, X:1, Y:20, X -> Y:1, A != Y, NotBool -> bool:False, A != Y, NotBool -> bool:True, True");
 
            void validate(string expression, string expected)
            {
                var comp = CreateCompilation(source.Replace("REPLACE", expression), options: TestOptions.DebugExe);
 
                comp.VerifyDiagnostics();
                CompileAndVerify(comp, expectedOutput: expected);
            }
        }
 
        [Fact]
        public void TestNonBoolComparisonResult_WithoutImplicitBoolConversion()
        {
            var source = @"
using static System.Console;
public class C
{
    public static void Main()
    {
        Write($""{REPLACE}"");
    }
}
public class Base
{
    public Base(int i) { }
}
public class A : Base
{
    public A(int i) : base(i)
        => throw null;
    public static NotBool operator ==(A a, Y y)
        => throw null;
    public static NotBool operator !=(A a, Y y)
        => throw null;
    public override bool Equals(object o)
        => throw null;
    public override int GetHashCode()
        => throw null;
}
public class X : Base
{
    public X(int i) : base(i)
        => throw null;
}
public class Y : Base
{
    public Y(int i) : base(i)
        => throw null;
    public static implicit operator Y(X x)
        => throw null;
}
public class NotBool
{
}
";
 
            validate("(new A(1), new A(2)) == (new X(1), new Y(2))",
                // (7,18): error CS0029: Cannot implicitly convert type 'NotBool' to 'bool'
                //         Write($"{(new A(1), new A(2)) == (new X(1), new Y(2))}");
                Diagnostic(ErrorCode.ERR_NoImplicitConv, "(new A(1), new A(2)) == (new X(1), new Y(2))").WithArguments("NotBool", "bool").WithLocation(7, 18),
                // (7,18): error CS0029: Cannot implicitly convert type 'NotBool' to 'bool'
                //         Write($"{(new A(1), new A(2)) == (new X(1), new Y(2))}");
                Diagnostic(ErrorCode.ERR_NoImplicitConv, "(new A(1), new A(2)) == (new X(1), new Y(2))").WithArguments("NotBool", "bool").WithLocation(7, 18)
                );
 
            validate("(new A(1), 2) != (new X(1), 2)",
                // (7,18): error CS0029: Cannot implicitly convert type 'NotBool' to 'bool'
                //         Write($"{(new A(1), 2) != (new X(1), 2)}");
                Diagnostic(ErrorCode.ERR_NoImplicitConv, "(new A(1), 2) != (new X(1), 2)").WithArguments("NotBool", "bool").WithLocation(7, 18)
                );
 
            void validate(string expression, params DiagnosticDescription[] expected)
            {
                var comp = CreateCompilation(source.Replace("REPLACE", expression));
                comp.VerifyDiagnostics(expected);
            }
        }
 
        [Fact]
        public void TestNonBoolComparisonResult_WithExplicitBoolConversion()
        {
            var source = @"
using static System.Console;
public class C
{
    public static void Main()
    {
        Write($""{(new A(1), new A(2)) == (new X(1), new Y(2))}"");
    }
}
public class Base
{
    public int I;
    public Base(int i) { I = i; }
}
public class A : Base
{
    public A(int i) : base(i)
        => throw null;
    public static NotBool operator ==(A a, Y y)
        => throw null;
    public static NotBool operator !=(A a, Y y)
        => throw null;
    public override bool Equals(object o)
        => throw null;
    public override int GetHashCode()
        => throw null;
}
public class X : Base
{
    public X(int i) : base(i)
        => throw null;
}
public class Y : Base
{
    public Y(int i) : base(i)
        => throw null;
    public static implicit operator Y(X x)
        => throw null;
}
public class NotBool
{
    public NotBool(bool value)
        => throw null;
    public static explicit operator bool(NotBool b)
        => throw null;
}
";
 
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (7,18): error CS0029: Cannot implicitly convert type 'NotBool' to 'bool'
                //         Write($"{(new A(1), new A(2)) == (new X(1), new Y(2))}");
                Diagnostic(ErrorCode.ERR_NoImplicitConv, "(new A(1), new A(2)) == (new X(1), new Y(2))").WithArguments("NotBool", "bool").WithLocation(7, 18),
                // (7,18): error CS0029: Cannot implicitly convert type 'NotBool' to 'bool'
                //         Write($"{(new A(1), new A(2)) == (new X(1), new Y(2))}");
                Diagnostic(ErrorCode.ERR_NoImplicitConv, "(new A(1), new A(2)) == (new X(1), new Y(2))").WithArguments("NotBool", "bool").WithLocation(7, 18)
                );
        }
 
        [Fact]
        public void TestNullableBoolComparisonResult_WithTrueFalseOperators()
        {
            var source = @"
public class C
{
    public static void M(A a)
    {
        _ = (a, a) == (a, a);
        if (a == a) { }
    }
}
public class A
{
    public A(int i)
        => throw null;
    public static bool? operator ==(A a1, A a2)
        => throw null;
    public static bool? operator !=(A a1, A a2)
        => throw null;
    public override bool Equals(object o)
        => throw null;
    public override int GetHashCode()
        => throw null;
}
";
 
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (6,13): error CS0029: Cannot implicitly convert type 'bool?' to 'bool'
                //         _ = (a, a) == (a, a);
                Diagnostic(ErrorCode.ERR_NoImplicitConv, "(a, a) == (a, a)").WithArguments("bool?", "bool").WithLocation(6, 13),
                // (6,13): error CS0029: Cannot implicitly convert type 'bool?' to 'bool'
                //         _ = (a, a) == (a, a);
                Diagnostic(ErrorCode.ERR_NoImplicitConv, "(a, a) == (a, a)").WithArguments("bool?", "bool").WithLocation(6, 13),
                // (7,13): error CS0266: Cannot implicitly convert type 'bool?' to 'bool'. An explicit conversion exists (are you missing a cast?)
                //         if (a == a) { }
                Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "a == a").WithArguments("bool?", "bool").WithLocation(7, 13),
                // (7,13): warning CS1718: Comparison made to same variable; did you mean to compare something else?
                //         if (a == a) { }
                Diagnostic(ErrorCode.WRN_ComparisonToSelf, "a == a").WithLocation(7, 13)
                );
        }
 
        [Fact]
        public void TestElementNames()
        {
            var source = @"
#pragma warning disable CS0219
using static System.Console;
public class C
{
    public static void Main()
    {
        int a = 1;
        int b = 2;
        int c = 3;
        int d = 4;
        int x = 5;
        int y = 6;
        (int x, int y) t1 = (1, 2);
        (int, int) t2 = (1, 2);
        Write($""{REPLACE}"");
    }
}
";
 
            // tuple expression vs tuple expression
            validate("t1 == t2");
 
            // tuple expression vs tuple literal
            validate("t1 == (x: 1, y: 2)");
            validate("t1 == (1, 2)");
            validate("(1, 2) == t1");
            validate("(x: 1, d) == t1");
 
            validate("t2 == (x: 1, y: 2)",
                // (16,25): warning CS8375: The tuple element name 'x' is ignored because a different name or no name is specified on the other side of the tuple == or != operator.
                //         Write($"{t2 == (x: 1, y: 2)}");
                Diagnostic(ErrorCode.WRN_TupleBinopLiteralNameMismatch, "x: 1").WithArguments("x").WithLocation(16, 25),
                // (16,31): warning CS8375: The tuple element name 'y' is ignored because a different name or no name is specified on the other side of the tuple == or != operator.
                //         Write($"{t2 == (x: 1, y: 2)}");
                Diagnostic(ErrorCode.WRN_TupleBinopLiteralNameMismatch, "y: 2").WithArguments("y").WithLocation(16, 31)
                );
 
            // tuple literal vs tuple literal
            // - warnings reported on the right when both sides could complain
            // - no warnings on inferred names
 
            validate("((a, b), c: 3) == ((1, x: 2), 3)",
                // (16,27): warning CS8375: The tuple element name 'c' is ignored because a different name or no name is specified on the other side of the tuple == or != operator.
                //         Write($"{((a, b), c: 3) == ((1, x: 2), 3)}");
                Diagnostic(ErrorCode.WRN_TupleBinopLiteralNameMismatch, "c: 3").WithArguments("c").WithLocation(16, 27),
                // (16,41): warning CS8375: The tuple element name 'x' is ignored because a different name or no name is specified on the other side of the tuple == or != operator.
                //         Write($"{((a, b), c: 3) == ((1, x: 2), 3)}");
                Diagnostic(ErrorCode.WRN_TupleBinopLiteralNameMismatch, "x: 2").WithArguments("x").WithLocation(16, 41)
                );
 
            validate("(a, b) == (a: 1, b: 2)");
            validate("(a, b) == (c: 1, d)",
                // (16,29): warning CS8375: The tuple element name 'c' is ignored because a different name or no name is specified on the other side of the tuple == or != operator.
                //         Write($"{(a, b) == (c: 1, d)}");
                Diagnostic(ErrorCode.WRN_TupleBinopLiteralNameMismatch, "c: 1").WithArguments("c").WithLocation(16, 29)
                );
 
            validate("(a: 1, b: 2) == (c: 1, d)",
                // (16,35): warning CS8375: The tuple element name 'c' is ignored because a different name or no name is specified on the other side of the tuple == or != operator.
                //         Write($"{(a: 1, b: 2) == (c: 1, d)}");
                Diagnostic(ErrorCode.WRN_TupleBinopLiteralNameMismatch, "c: 1").WithArguments("c").WithLocation(16, 35),
                // (16,25): warning CS8375: The tuple element name 'b' is ignored because a different name or no name is specified on the other side of the tuple == or != operator.
                //         Write($"{(a: 1, b: 2) == (c: 1, d)}");
                Diagnostic(ErrorCode.WRN_TupleBinopLiteralNameMismatch, "b: 2").WithArguments("b").WithLocation(16, 25)
                );
 
            validate("(null, b) == (c: null, d: 2)",
                // (16,32): warning CS8375: The tuple element name 'c' is ignored because a different name or no name is specified on the other side of the tuple == or != operator.
                //         Write($"{(null, b) == (c: null, d: 2)}");
                Diagnostic(ErrorCode.WRN_TupleBinopLiteralNameMismatch, "c: null").WithArguments("c").WithLocation(16, 32),
                // (16,41): warning CS8375: The tuple element name 'd' is ignored because a different name or no name is specified on the other side of the tuple == or != operator.
                //         Write($"{(null, b) == (c: null, d: 2)}");
                Diagnostic(ErrorCode.WRN_TupleBinopLiteralNameMismatch, "d: 2").WithArguments("d").WithLocation(16, 41)
                );
 
            void validate(string expression, params DiagnosticDescription[] diagnostics)
            {
                var comp = CreateCompilation(source.Replace("REPLACE", expression));
                comp.VerifyDiagnostics(diagnostics);
            }
        }
 
        [Fact]
        public void TestValueTupleWithObsoleteEqualityOperator()
        {
            string source = @"
public class C
{
    public static void Main()
    {
        System.Console.Write((1, 2) == (3, 4));
    }
}
namespace System
{
    public struct ValueTuple<T1, T2>
    {
        public T1 Item1;
        public T2 Item2;
 
        public ValueTuple(T1 item1, T2 item2)
        {
            this.Item1 = item1;
            this.Item2 = item2;
        }
 
        [System.Obsolete]
        public static bool operator==(ValueTuple<T1, T2> t1, ValueTuple<T1, T2> t2)
            => throw null;
 
        public static bool operator!=(ValueTuple<T1, T2> t1, ValueTuple<T1, T2> t2)
            => throw null;
 
        public override bool Equals(object other)
            => throw null;
 
        public override int GetHashCode()
            => throw null;
    }
}
";
 
            var comp = CreateCompilationWithMscorlib40(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
 
            // Note: tuple equality picked ahead of custom operator==
            CompileAndVerify(comp, expectedOutput: "False");
        }
 
        [Fact]
        public void TestInExpressionTree()
        {
            var source = @"
using System;
using System.Linq.Expressions;
public class C
{
    public static void Main()
    {
        Expression<Func<int, bool>> expr = i => (i, i) == (i, i);
        Expression<Func<(int, int), bool>> expr2 = t => t != t;
    }
}";
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (8,49): error CS8374: An expression tree may not contain a tuple == or != operator
                //         Expression<Func<int, bool>> expr = i => (i, i) == (i, i);
                Diagnostic(ErrorCode.ERR_ExpressionTreeContainsTupleBinOp, "(i, i) == (i, i)").WithLocation(8, 49),
                // (8,49): error CS8143: An expression tree may not contain a tuple literal.
                //         Expression<Func<int, bool>> expr = i => (i, i) == (i, i);
                Diagnostic(ErrorCode.ERR_ExpressionTreeContainsTupleLiteral, "(i, i)").WithLocation(8, 49),
                // (8,59): error CS8143: An expression tree may not contain a tuple literal.
                //         Expression<Func<int, bool>> expr = i => (i, i) == (i, i);
                Diagnostic(ErrorCode.ERR_ExpressionTreeContainsTupleLiteral, "(i, i)").WithLocation(8, 59),
                // (9,57): error CS8374: An expression tree may not contain a tuple == or != operator
                //         Expression<Func<(int, int), bool>> expr2 = t => t != t;
                Diagnostic(ErrorCode.ERR_ExpressionTreeContainsTupleBinOp, "t != t").WithLocation(9, 57)
                );
        }
 
        [Fact]
        public void TestComparisonOfDynamicAndTuple()
        {
            var source = @"
public class C
{
    (long, string) _t;
    public C(long l, string s) { _t = (l, s); }
 
    public static void Main()
    {
        dynamic d = new C(1, ""hello"");
        (long, string) tuple1 = (1, ""hello"");
        (long, string) tuple2 = (2, ""world"");
        System.Console.Write($""{d == tuple1} {d != tuple1} {d == tuple2} {d != tuple2}"");
    }
    public static bool operator==(C c, (long, string) t)
    {
        return c._t.Item1 == t.Item1 && c._t.Item2 == t.Item2;
    }
    public static bool operator!=(C c, (long, string) t)
    {
        return !(c == t);
    }
    public override bool Equals(object o) => throw null;
    public override int GetHashCode() => throw null;
}
";
 
            var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "True False False True");
        }
 
        [Fact]
        public void TestComparisonOfDynamicTuple()
        {
            var source = @"
public class C
{
    public static void Main()
    {
        dynamic d1 = (1, ""hello"");
        dynamic d2 = (1, ""hello"");
        dynamic d3 = ((byte)1, 2);
        PrintException(() => d1 == d2);
        PrintException(() => d1 == d3);
    }
    public static void PrintException(System.Func<bool> action)
    {
        System.Globalization.CultureInfo saveUICulture = System.Threading.Thread.CurrentThread.CurrentUICulture;
        System.Threading.Thread.CurrentThread.CurrentUICulture = System.Globalization.CultureInfo.InvariantCulture;
 
        try
        {
            action();
        }
        catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException e)
        {
            System.Console.WriteLine(e.Message);
        }
        finally
        {
            System.Threading.Thread.CurrentThread.CurrentUICulture = saveUICulture;
        }
    }
}
";
 
            var comp = CreateCompilation(source, targetFramework: TargetFramework.StandardAndCSharp, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
 
            CompileAndVerify(comp, expectedOutput:
@"Operator '==' cannot be applied to operands of type 'System.ValueTuple<int,string>' and 'System.ValueTuple<int,string>'
Operator '==' cannot be applied to operands of type 'System.ValueTuple<int,string>' and 'System.ValueTuple<byte,int>'");
        }
 
        [Fact]
        public void TestComparisonWithTupleElementNames()
        {
            var source = @"
public class C
{
    public static void Main()
    {
        int Bob = 1;
        System.Console.Write((Alice: 0, (Bob, 2)) == (Bob: 0, (1, Other: 2)));
    }
}
";
 
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics(
                // (7,55): warning CS8375: The tuple element name 'Bob' is ignored because a different name or no name is specified on the other side of the tuple == or != operator.
                //         System.Console.Write((Alice: 0, (Bob, 2)) == (Bob: 0, (1, Other: 2)));
                Diagnostic(ErrorCode.WRN_TupleBinopLiteralNameMismatch, "Bob: 0").WithArguments("Bob").WithLocation(7, 55),
                // (7,67): warning CS8375: The tuple element name 'Other' is ignored because a different name or no name is specified on the other side of the tuple == or != operator.
                //         System.Console.Write((Alice: 0, (Bob, 2)) == (Bob: 0, (1, Other: 2)));
                Diagnostic(ErrorCode.WRN_TupleBinopLiteralNameMismatch, "Other: 2").WithArguments("Other").WithLocation(7, 67)
                );
 
            CompileAndVerify(comp, expectedOutput: "True");
 
            var tree = comp.SyntaxTrees.First();
            var model = comp.GetSemanticModel(tree);
 
            var equals = tree.GetCompilationUnitRoot().DescendantNodes().OfType<BinaryExpressionSyntax>().First();
 
            // check left tuple
            var leftTuple = equals.Left;
            var leftInfo = model.GetTypeInfo(leftTuple);
            Assert.Equal("(System.Int32 Alice, (System.Int32 Bob, System.Int32))", leftInfo.Type.ToTestDisplayString());
            Assert.Equal("(System.Int32 Alice, (System.Int32 Bob, System.Int32))", leftInfo.ConvertedType.ToTestDisplayString());
 
            // check right tuple
            var rightTuple = equals.Right;
            var rightInfo = model.GetTypeInfo(rightTuple);
            Assert.Equal("(System.Int32 Bob, (System.Int32, System.Int32 Other))", rightInfo.Type.ToTestDisplayString());
            Assert.Equal("(System.Int32 Bob, (System.Int32, System.Int32 Other))", rightInfo.ConvertedType.ToTestDisplayString());
        }
 
        [Fact]
        public void TestComparisonWithCastedTuples()
        {
            var source = @"
public class C
{
    public static void Main()
    {
        System.Console.Write( ((string, (byte, long))) (null, (1, 2L)) == ((string, (long, byte))) (null, (1L, 2)) );
    }
}
";
 
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
 
            CompileAndVerify(comp, expectedOutput: "True");
 
            var tree = comp.SyntaxTrees.First();
            var model = comp.GetSemanticModel(tree);
 
            var equals = tree.GetCompilationUnitRoot().DescendantNodes().OfType<BinaryExpressionSyntax>().First();
 
            // check left cast ...
            var leftCast = (CastExpressionSyntax)equals.Left;
            Assert.Equal("((string, (byte, long))) (null, (1, 2L))", leftCast.ToString());
            var leftCastInfo = model.GetTypeInfo(leftCast);
            Assert.Equal("(System.String, (System.Byte, System.Int64))", leftCastInfo.Type.ToTestDisplayString());
            Assert.Equal("(System.String, (System.Int64, System.Int64))", leftCastInfo.ConvertedType.ToTestDisplayString());
            Assert.Equal(ConversionKind.ImplicitTuple, model.GetConversion(leftCast).Kind);
 
            // ... its tuple ...
            var leftTuple = (TupleExpressionSyntax)leftCast.Expression;
            Assert.Equal("(null, (1, 2L))", leftTuple.ToString());
            var leftTupleInfo = model.GetTypeInfo(leftTuple);
            Assert.Null(leftTupleInfo.Type);
            Assert.Equal("(System.String, (System.Byte, System.Int64))", leftTupleInfo.ConvertedType.ToTestDisplayString());
            Assert.Equal(ConversionKind.ExplicitTupleLiteral, model.GetConversion(leftTuple).Kind);
 
            // ... its null ...
            var leftNull = leftTuple.Arguments[0].Expression;
            Assert.Equal("null", leftNull.ToString());
            var leftNullInfo = model.GetTypeInfo(leftNull);
            Assert.Null(leftNullInfo.Type);
            Assert.Equal("System.String", leftNullInfo.ConvertedType.ToTestDisplayString());
            Assert.Equal(ConversionKind.ImplicitReference, model.GetConversion(leftNull).Kind);
 
            // ... its nested tuple
            var leftNestedTuple = leftTuple.Arguments[1].Expression;
            Assert.Equal("(1, 2L)", leftNestedTuple.ToString());
            var leftNestedTupleInfo = model.GetTypeInfo(leftNestedTuple);
            Assert.Equal("(System.Int32, System.Int64)", leftNestedTupleInfo.Type.ToTestDisplayString());
            Assert.Equal("(System.Byte, System.Int64)", leftNestedTupleInfo.ConvertedType.ToTestDisplayString());
            Assert.Equal(ConversionKind.Identity, model.GetConversion(leftNestedTuple).Kind);
 
            // check right cast ...
            var rightCast = (CastExpressionSyntax)equals.Right;
            var rightCastInfo = model.GetTypeInfo(rightCast);
            Assert.Equal("(System.String, (System.Int64, System.Byte))", rightCastInfo.Type.ToTestDisplayString());
            Assert.Equal("(System.String, (System.Int64, System.Int64))", rightCastInfo.ConvertedType.ToTestDisplayString());
            Assert.Equal(ConversionKind.ImplicitTuple, model.GetConversion(rightCast).Kind);
 
            // ... its tuple
            var rightTuple = rightCast.Expression;
            var rightTupleInfo = model.GetTypeInfo(rightTuple);
            Assert.Null(rightTupleInfo.Type);
            Assert.Equal("(System.String, (System.Int64, System.Byte))", rightTupleInfo.ConvertedType.ToTestDisplayString());
            Assert.Equal(ConversionKind.ExplicitTupleLiteral, model.GetConversion(rightTuple).Kind);
        }
 
        [Fact]
        public void TestGenericElement()
        {
            var source = @"
public class C
{
    public void M<T>(T t)
    {
        _ = (t, t) == (t, t);
    }
}
";
 
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (6,13): error CS0019: Operator '==' cannot be applied to operands of type 'T' and 'T'
                //         _ = (t, t) == (t, t);
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "(t, t) == (t, t)").WithArguments("==", "T", "T").WithLocation(6, 13),
                // (6,13): error CS0019: Operator '==' cannot be applied to operands of type 'T' and 'T'
                //         _ = (t, t) == (t, t);
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "(t, t) == (t, t)").WithArguments("==", "T", "T").WithLocation(6, 13)
                );
        }
 
        [Fact]
        public void TestNameofEquality()
        {
            var source = @"
public class C
{
    public void M()
    {
        _ = nameof((1, 2) == (3, 4));
    }
}
";
 
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (6,20): error CS8081: Expression does not have a name.
                //         _ = nameof((1, 2) == (3, 4));
                Diagnostic(ErrorCode.ERR_ExpressionHasNoName, "(1, 2) == (3, 4)").WithLocation(6, 20)
                );
        }
 
        [Fact]
        public void TestAsRefOrOutArgument()
        {
            var source = @"
public class C
{
    public void M(ref bool x, out bool y)
    {
        x = true;
        y = true;
        M(ref (1, 2) == (3, 4), out (1, 2) == (3, 4));
    }
}
";
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (8,15): error CS1510: A ref or out value must be an assignable variable
                //         M(ref (1, 2) == (3, 4), out (1, 2) == (3, 4));
                Diagnostic(ErrorCode.ERR_RefLvalueExpected, "(1, 2) == (3, 4)").WithLocation(8, 15),
                // (8,37): error CS1510: A ref or out value must be an assignable variable
                //         M(ref (1, 2) == (3, 4), out (1, 2) == (3, 4));
                Diagnostic(ErrorCode.ERR_RefLvalueExpected, "(1, 2) == (3, 4)").WithLocation(8, 37)
                );
        }
 
        [Fact]
        public void TestWithAnonymousTypes()
        {
            var source = @"
public class C
{
    public static void Main()
    {
        var a = new { A = 1 };
        var b = new { B = 2 };
        var c = new { A = 1 };
        var d = new { B = 2 };
 
        System.Console.Write((a, b) == (a, b));
        System.Console.Write((a, b) == (c, d));
        System.Console.Write((a, b) != (c, d));
    }
}
";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "TrueFalseTrue");
        }
 
        [Fact]
        public void TestWithAnonymousTypes2()
        {
            var source = @"
public class C
{
    public static void Main()
    {
        var a = new { A = 1 };
        var b = new { B = 2 };
 
        System.Console.Write((a, b) == (b, a));
    }
}
";
            var comp = CreateCompilation(source);
            comp.VerifyDiagnostics(
                // (9,30): error CS0019: Operator '==' cannot be applied to operands of type '<anonymous type: int A>' and '<anonymous type: int B>'
                //         System.Console.Write((a, b) == (b, a));
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "(a, b) == (b, a)").WithArguments("==", "<anonymous type: int A>", "<anonymous type: int B>").WithLocation(9, 30),
                // (9,30): error CS0019: Operator '==' cannot be applied to operands of type '<anonymous type: int B>' and '<anonymous type: int A>'
                //         System.Console.Write((a, b) == (b, a));
                Diagnostic(ErrorCode.ERR_BadBinaryOps, "(a, b) == (b, a)").WithArguments("==", "<anonymous type: int B>", "<anonymous type: int A>").WithLocation(9, 30)
                );
        }
 
        [Fact]
        public void TestRefReturningElements()
        {
            var source = @"
public class C
{
    public static void Main()
    {
        System.Console.Write((P, S()) == (1, ""hello""));
    }
    public static int p = 1;
    public static ref int P => ref p;
    public static string s = ""hello"";
    public static ref string S() => ref s;
}
";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "True");
        }
 
        [Fact]
        public void TestChecked()
        {
            var source = @"
public class C
{
    public static void Main()
    {
        try
        {
            checked
            {
                int ten = 10;
                System.Console.Write((2147483647 + ten, 1) == (0, 1));
            }
        }
        catch (System.OverflowException)
        {
            System.Console.Write(""overflow"");
        }
    }
}
";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "overflow");
        }
 
        [Fact]
        public void TestUnchecked()
        {
            var source = @"
public class C
{
    public static void Main()
    {
        unchecked
        {
            int ten = 10;
            System.Console.Write((2147483647 + ten, 1) == (-2147483639, 1));
        }
    }
}
";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "True");
        }
 
        [Fact]
        public void TestInQuery()
        {
            var source = @"
using System.Linq;
public class C
{
    public static void Main()
    {
        var query =
            from a in new int[] { 1, 2, 2}
            where (a, 2) == (2, a)
            select a;
 
        foreach (var i in query)
        {
            System.Console.Write(i);
        }
    }
}
";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: "22");
        }
 
        [Fact]
        public void TestWithPointer()
        {
            var source = @"
public class C
{
    public unsafe static void M()
    {
        int x = 234;
        int y = 236;
        int* p1 = &x;
        int* p2 = &y;
        _ = (p1, p2) == (p1, p2);
    }
}
";
            var comp = CreateCompilation(source, options: TestOptions.UnsafeDebugDll);
            comp.VerifyDiagnostics(
                // (10,14): error CS0306: The type 'int*' may not be used as a type argument
                //         _ = (p1, p2) == (p1, p2);
                Diagnostic(ErrorCode.ERR_BadTypeArgument, "p1").WithArguments("int*").WithLocation(10, 14),
                // (10,18): error CS0306: The type 'int*' may not be used as a type argument
                //         _ = (p1, p2) == (p1, p2);
                Diagnostic(ErrorCode.ERR_BadTypeArgument, "p2").WithArguments("int*").WithLocation(10, 18),
                // (10,26): error CS0306: The type 'int*' may not be used as a type argument
                //         _ = (p1, p2) == (p1, p2);
                Diagnostic(ErrorCode.ERR_BadTypeArgument, "p1").WithArguments("int*").WithLocation(10, 26),
                // (10,30): error CS0306: The type 'int*' may not be used as a type argument
                //         _ = (p1, p2) == (p1, p2);
                Diagnostic(ErrorCode.ERR_BadTypeArgument, "p2").WithArguments("int*").WithLocation(10, 30),
                // (10,14): error CS0306: The type 'void*' may not be used as a type argument
                //         _ = (p1, p2) == (p1, p2);
                Diagnostic(ErrorCode.ERR_BadTypeArgument, "p1").WithArguments("void*").WithLocation(10, 14),
                // (10,18): error CS0306: The type 'void*' may not be used as a type argument
                //         _ = (p1, p2) == (p1, p2);
                Diagnostic(ErrorCode.ERR_BadTypeArgument, "p2").WithArguments("void*").WithLocation(10, 18),
                // (10,26): error CS0306: The type 'void*' may not be used as a type argument
                //         _ = (p1, p2) == (p1, p2);
                Diagnostic(ErrorCode.ERR_BadTypeArgument, "p1").WithArguments("void*").WithLocation(10, 26),
                // (10,30): error CS0306: The type 'void*' may not be used as a type argument
                //         _ = (p1, p2) == (p1, p2);
                Diagnostic(ErrorCode.ERR_BadTypeArgument, "p2").WithArguments("void*").WithLocation(10, 30)
                );
        }
 
        [Fact]
        public void TestOrder01()
        {
            var source = @"
using System;
 
public class C
{
    public static void Main()
    {
        X x = new X();
        Y y = new Y();
        Console.WriteLine((1, ((int, int))x) == (y, (1, 1)));
     }
}
 
class X
{
    public static implicit operator (short, short)(X x)
    {
        Console.WriteLine(""X-> (short, short)"");
        return (1, 1);
    }
}
 
class Y
{
    public static implicit operator int(Y x)
    {
        Console.WriteLine(""Y -> int"");
        return 1;
    }
}
";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics();
            CompileAndVerify(comp, expectedOutput: @"X-> (short, short)
Y -> int
True");
        }
 
        [Fact]
        public void TestOrder02()
        {
            var source = @"
using System;
 
public class C {
    public static void Main() {
        var result = (new B(1), new Nullable<B>(new A(2))) == (new A(3), new B(4));
        Console.WriteLine();
        Console.WriteLine(result);
    }
}
 
struct A
{
    public readonly int N;
    public A(int n)
    {
        this.N = n;
        Console.Write($""new A({ n }); "");
    }
}
 
struct B
{
    public readonly int N;
    public B(int n)
    {
        this.N = n;
        Console.Write($""new B({n}); "");
    }
    public static implicit operator B(A a)
    {
        Console.Write($""A({a.N})->"");
        return new B(a.N);
    }
    public static bool operator ==(B b1, B b2)
    {
        Console.Write($""B({b1.N})==B({b2.N}); "");
        return b1.N == b2.N;
    }
    public static bool operator !=(B b1, B b2)
    {
        Console.Write($""B({b1.N})!=B({b2.N}); "");
        return b1.N != b2.N;
    }
}
";
            var comp = CreateCompilation(source, options: TestOptions.DebugExe);
            comp.VerifyDiagnostics(
                    // (22,8): warning CS0660: 'B' defines operator == or operator != but does not override Object.Equals(object o)
                    // struct B
                    Diagnostic(ErrorCode.WRN_EqualityOpWithoutEquals, "B").WithArguments("B").WithLocation(22, 8),
                    // (22,8): warning CS0661: 'B' defines operator == or operator != but does not override Object.GetHashCode()
                    // struct B
                    Diagnostic(ErrorCode.WRN_EqualityOpWithoutGetHashCode, "B").WithArguments("B").WithLocation(22, 8)
                );
            CompileAndVerify(comp, expectedOutput: @"new B(1); new A(2); A(2)->new B(2); new A(3); new B(4); A(3)->new B(3); B(1)==B(3); 
False
");
        }
 
        [Fact, WorkItem(35958, "https://github.com/dotnet/roslyn/issues/35958")]
        public void TestMethodGroupConversionInTupleEquality_01()
        {
            var source = @"
using System;
 
public class C {
    public static void Main()
    {
        Action a = M;
        Console.WriteLine((a, M) == (M, a));
    }
    
    static void M() {}
}";
            var comp = CompileAndVerify(source, expectedOutput: "True");
            comp.VerifyDiagnostics();
        }
 
        [Fact, WorkItem(35958, "https://github.com/dotnet/roslyn/issues/35958")]
        public void TestMethodGroupConversionInTupleEquality_02()
        {
            var source = @"
using System;
 
public class C {
    public static void Main()
    {
        Action a = () => {};
        Console.WriteLine((a, M) == (M, a));
    }
    
    static void M() {}
}";
            var comp = CompileAndVerify(source, expectedOutput: "False");
            comp.VerifyDiagnostics();
        }
 
        [Fact, WorkItem(35958, "https://github.com/dotnet/roslyn/issues/35958")]
        public void TestMethodGroupConversionInTupleEquality_03()
        {
            var source = @"
using System;
 
public class C {
    public static void Main()
    {
        K k = null;
        Console.WriteLine((k, 1) == (M, 1));
    }
    
    static void M() {}
}
 
class K
{
    public static bool operator ==(K k, System.Action a) => true;
    public static bool operator !=(K k, System.Action a) => false;
    public override bool Equals(object other) => false;
    public override int GetHashCode() => 1;
}
";
            var comp = CompileAndVerify(source, expectedOutput: "True");
            comp.VerifyDiagnostics();
        }
 
        [Fact, WorkItem(35958, "https://github.com/dotnet/roslyn/issues/35958")]
        public void TestInterpolatedStringConversionInTupleEquality_01()
        {
            var source = @"
using System;
 
public class C {
    public static void Main()
    {
        K k = null;
        Console.WriteLine((k, 1) == ($""frog"", 1));
    }
    
    static void M() {}
}
 
class K
{
    public static bool operator ==(K k, IFormattable a) => a.ToString() == ""frog"";
    public static bool operator !=(K k, IFormattable a) => false;
    public override bool Equals(object other) => false;
    public override int GetHashCode() => 1;
}
";
            var comp = CompileAndVerify(source, expectedOutput: "True");
            comp.VerifyDiagnostics();
        }
 
        [Fact]
        [WorkItem("https://github.com/dotnet/roslyn/issues/66265")]
        public void Issue66265_01()
        {
            var source = @"
class C
{
    static void Main()
    {
        (byte, byte) a = (1, 2);
        var eq = ((int, int))a == a;
        System.Console.Write(eq ? 1 : 0);
    }
}
";
 
            var comp = CompileAndVerify(source, expectedOutput: "1").VerifyDiagnostics();
            comp.VerifyIL("C.Main", @"
{
  // Code size       70 (0x46)
  .maxstack  3
  .locals init (System.ValueTuple<int, int> V_0,
                byte V_1,
                byte V_2,
                System.ValueTuple<byte, byte> V_3)
  IL_0000:  ldc.i4.1
  IL_0001:  ldc.i4.2
  IL_0002:  newobj     ""System.ValueTuple<byte, byte>..ctor(byte, byte)""
  IL_0007:  dup
  IL_0008:  stloc.3
  IL_0009:  ldloc.3
  IL_000a:  ldfld      ""byte System.ValueTuple<byte, byte>.Item1""
  IL_000f:  ldloc.3
  IL_0010:  ldfld      ""byte System.ValueTuple<byte, byte>.Item2""
  IL_0015:  newobj     ""System.ValueTuple<int, int>..ctor(int, int)""
  IL_001a:  stloc.0
  IL_001b:  dup
  IL_001c:  ldfld      ""byte System.ValueTuple<byte, byte>.Item1""
  IL_0021:  stloc.1
  IL_0022:  ldfld      ""byte System.ValueTuple<byte, byte>.Item2""
  IL_0027:  stloc.2
  IL_0028:  ldloc.0
  IL_0029:  ldfld      ""int System.ValueTuple<int, int>.Item1""
  IL_002e:  ldloc.1
  IL_002f:  bne.un.s   IL_003c
  IL_0031:  ldloc.0
  IL_0032:  ldfld      ""int System.ValueTuple<int, int>.Item2""
  IL_0037:  ldloc.2
  IL_0038:  ceq
  IL_003a:  br.s       IL_003d
  IL_003c:  ldc.i4.0
  IL_003d:  ldc.i4.0
  IL_003e:  cgt.un
  IL_0040:  call       ""void System.Console.Write(int)""
  IL_0045:  ret
}
");
        }
 
        [Fact]
        [WorkItem("https://github.com/dotnet/roslyn/issues/66265")]
        public void Issue66265_02()
        {
            var source = @"
class C
{
    static void Main()
    {
        (int, int) a = (1, 2);
        var eq = M1() == a;
        System.Console.Write(eq ? 1 : 0);
    }
 
    static (byte, byte) M1()
    {
        System.Console.Write(""M1"");
        return (1, 2);
    }
}
";
 
            var comp = CompileAndVerify(source, expectedOutput: "M11").VerifyDiagnostics();
            comp.VerifyIL("C.Main", @"
{
  // Code size       56 (0x38)
  .maxstack  3
  .locals init (byte V_0,
            byte V_1,
            System.ValueTuple<int, int> V_2)
  IL_0000:  ldc.i4.1
  IL_0001:  ldc.i4.2
  IL_0002:  newobj     ""System.ValueTuple<int, int>..ctor(int, int)""
  IL_0007:  call       ""System.ValueTuple<byte, byte> C.M1()""
  IL_000c:  dup
  IL_000d:  ldfld      ""byte System.ValueTuple<byte, byte>.Item1""
  IL_0012:  stloc.0
  IL_0013:  ldfld      ""byte System.ValueTuple<byte, byte>.Item2""
  IL_0018:  stloc.1
  IL_0019:  stloc.2
  IL_001a:  ldloc.0
  IL_001b:  ldloc.2
  IL_001c:  ldfld      ""int System.ValueTuple<int, int>.Item1""
  IL_0021:  bne.un.s   IL_002e
  IL_0023:  ldloc.1
  IL_0024:  ldloc.2
  IL_0025:  ldfld      ""int System.ValueTuple<int, int>.Item2""
  IL_002a:  ceq
  IL_002c:  br.s       IL_002f
  IL_002e:  ldc.i4.0
  IL_002f:  ldc.i4.0
  IL_0030:  cgt.un
  IL_0032:  call       ""void System.Console.Write(int)""
  IL_0037:  ret
}
");
        }
 
        [Fact]
        [WorkItem("https://github.com/dotnet/roslyn/issues/66265")]
        public void Issue66265_03()
        {
            var source = @"
class C
{
    static void Main()
    {
        ((byte, byte), byte) a = ((1, 2), 3);
        var eq = (((int, int), int))a == a;
        System.Console.Write(eq ? 1 : 0);
    }
}
";
 
            var comp = CompileAndVerify(source, expectedOutput: "1").VerifyDiagnostics();
            comp.VerifyIL("C.Main", @"
{
  // Code size      131 (0x83)
  .maxstack  3
  .locals init (System.ValueTuple<System.ValueTuple<int, int>, int> V_0,
            byte V_1,
            byte V_2,
            byte V_3,
            System.ValueTuple<System.ValueTuple<byte, byte>, byte> V_4,
            System.ValueTuple<byte, byte> V_5)
  IL_0000:  ldc.i4.1
  IL_0001:  ldc.i4.2
  IL_0002:  newobj     ""System.ValueTuple<byte, byte>..ctor(byte, byte)""
  IL_0007:  ldc.i4.3
  IL_0008:  newobj     ""System.ValueTuple<System.ValueTuple<byte, byte>, byte>..ctor(System.ValueTuple<byte, byte>, byte)""
  IL_000d:  dup
  IL_000e:  stloc.s    V_4
  IL_0010:  ldloc.s    V_4
  IL_0012:  ldfld      ""System.ValueTuple<byte, byte> System.ValueTuple<System.ValueTuple<byte, byte>, byte>.Item1""
  IL_0017:  stloc.s    V_5
  IL_0019:  ldloc.s    V_5
  IL_001b:  ldfld      ""byte System.ValueTuple<byte, byte>.Item1""
  IL_0020:  ldloc.s    V_5
  IL_0022:  ldfld      ""byte System.ValueTuple<byte, byte>.Item2""
  IL_0027:  newobj     ""System.ValueTuple<int, int>..ctor(int, int)""
  IL_002c:  ldloc.s    V_4
  IL_002e:  ldfld      ""byte System.ValueTuple<System.ValueTuple<byte, byte>, byte>.Item2""
  IL_0033:  newobj     ""System.ValueTuple<System.ValueTuple<int, int>, int>..ctor(System.ValueTuple<int, int>, int)""
  IL_0038:  stloc.0
  IL_0039:  dup
  IL_003a:  ldfld      ""System.ValueTuple<byte, byte> System.ValueTuple<System.ValueTuple<byte, byte>, byte>.Item1""
  IL_003f:  dup
  IL_0040:  ldfld      ""byte System.ValueTuple<byte, byte>.Item1""
  IL_0045:  stloc.1
  IL_0046:  ldfld      ""byte System.ValueTuple<byte, byte>.Item2""
  IL_004b:  stloc.2
  IL_004c:  ldfld      ""byte System.ValueTuple<System.ValueTuple<byte, byte>, byte>.Item2""
  IL_0051:  stloc.3
  IL_0052:  ldloc.0
  IL_0053:  ldfld      ""System.ValueTuple<int, int> System.ValueTuple<System.ValueTuple<int, int>, int>.Item1""
  IL_0058:  ldfld      ""int System.ValueTuple<int, int>.Item1""
  IL_005d:  ldloc.1
  IL_005e:  bne.un.s   IL_0079
  IL_0060:  ldloc.0
  IL_0061:  ldfld      ""System.ValueTuple<int, int> System.ValueTuple<System.ValueTuple<int, int>, int>.Item1""
  IL_0066:  ldfld      ""int System.ValueTuple<int, int>.Item2""
  IL_006b:  ldloc.2
  IL_006c:  bne.un.s   IL_0079
  IL_006e:  ldloc.0
  IL_006f:  ldfld      ""int System.ValueTuple<System.ValueTuple<int, int>, int>.Item2""
  IL_0074:  ldloc.3
  IL_0075:  ceq
  IL_0077:  br.s       IL_007a
  IL_0079:  ldc.i4.0
  IL_007a:  ldc.i4.0
  IL_007b:  cgt.un
  IL_007d:  call       ""void System.Console.Write(int)""
  IL_0082:  ret
}
");
        }
 
        [Fact]
        [WorkItem("https://github.com/dotnet/roslyn/issues/66265")]
        public void Issue66265_04()
        {
            var source = @"
class C
{
    static void Main()
    {
        (byte, byte) a = (1, 2);
        (int, int) b = (1, 2);
        var eq = a == b;
        System.Console.Write(eq ? 1 : 0);
    }
}
";
 
            var comp = CompileAndVerify(source, expectedOutput: "1").VerifyDiagnostics();
            comp.VerifyIL("C.Main", @"
{
  // Code size       61 (0x3d)
  .maxstack  4
  .locals init (System.ValueTuple<int, int> V_0, //b
                byte V_1,
                byte V_2,
                System.ValueTuple<int, int> V_3)
  IL_0000:  ldc.i4.1
  IL_0001:  ldc.i4.2
  IL_0002:  newobj     ""System.ValueTuple<byte, byte>..ctor(byte, byte)""
  IL_0007:  ldloca.s   V_0
  IL_0009:  ldc.i4.1
  IL_000a:  ldc.i4.2
  IL_000b:  call       ""System.ValueTuple<int, int>..ctor(int, int)""
  IL_0010:  dup
  IL_0011:  ldfld      ""byte System.ValueTuple<byte, byte>.Item1""
  IL_0016:  stloc.1
  IL_0017:  ldfld      ""byte System.ValueTuple<byte, byte>.Item2""
  IL_001c:  stloc.2
  IL_001d:  ldloc.0
  IL_001e:  stloc.3
  IL_001f:  ldloc.1
  IL_0020:  ldloc.3
  IL_0021:  ldfld      ""int System.ValueTuple<int, int>.Item1""
  IL_0026:  bne.un.s   IL_0033
  IL_0028:  ldloc.2
  IL_0029:  ldloc.3
  IL_002a:  ldfld      ""int System.ValueTuple<int, int>.Item2""
  IL_002f:  ceq
  IL_0031:  br.s       IL_0034
  IL_0033:  ldc.i4.0
  IL_0034:  ldc.i4.0
  IL_0035:  cgt.un
  IL_0037:  call       ""void System.Console.Write(int)""
  IL_003c:  ret
}
");
        }
    }
}