﻿// 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.Collections.Generic;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.EditAndContinue.UnitTests;
using Roslyn.Test.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests
{
    public class StatementMatchingTests : EditingTestBase
    {
        #region Known Matches

        [Fact]
        public void KnownMatches()
        {
            var src1 = @"
Console.WriteLine(1)/*1*/;
Console.WriteLine(1)/*2*/;
";

            var src2 = @"
Console.WriteLine(1)/*3*/;
Console.WriteLine(1)/*4*/;
";

            var m1 = MakeMethodBody(src1);
            var m2 = MakeMethodBody(src2);

            var knownMatches = new KeyValuePair<SyntaxNode, SyntaxNode>[]
            {
                new KeyValuePair<SyntaxNode, SyntaxNode>(((BlockSyntax)m1.RootNodes.First()).Statements[1], ((BlockSyntax)m2.RootNodes.First()).Statements[0])
            };

            // pre-matched:

            var match = m1.ComputeMatch(m2, knownMatches);

            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "Console.WriteLine(1)/*1*/;", "Console.WriteLine(1)/*4*/;" },
                { "Console.WriteLine(1)/*2*/;", "Console.WriteLine(1)/*3*/;" }
            };

            expected.AssertEqual(actual);

            // not pre-matched:

            match = m1.ComputeMatch(m2, knownMatches: null);

            actual = ToMatchingPairs(match);

            expected = new MatchingPairs
            {
                { "Console.WriteLine(1)/*1*/;", "Console.WriteLine(1)/*3*/;" },
                { "Console.WriteLine(1)/*2*/;", "Console.WriteLine(1)/*4*/;" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void KnownMatches_Root()
        {
            var src1 = @"
Console.WriteLine(1);
";

            var src2 = @"
Console.WriteLine(2);
";

            var m1 = MakeMethodBody(src1);
            var m2 = MakeMethodBody(src2);

            var knownMatches = new[] { new KeyValuePair<SyntaxNode, SyntaxNode>(m1.RootNodes.First(), m2.RootNodes.First()) };
            var match = m1.ComputeMatch(m2, knownMatches);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "Console.WriteLine(1);", "Console.WriteLine(2);" }
            };

            expected.AssertEqual(actual);
        }

        #endregion

        #region Statements

        [Fact]
        public void MiscStatements()
        {
            var src1 = @"
int x = 1; 
Console.WriteLine(1);
x++/*1A*/;
Console.WriteLine(2);

while (true)
{
    x++/*2A*/;
}

Console.WriteLine(1);
";
            var src2 = @"
int x = 1;
x++/*1B*/;
for (int i = 0; i < 10; i++) {}
y++;
if (x > 1)
{
    while (true)
    {
        x++/*2B*/;
    }

    Console.WriteLine(1);
}";
            var match = GetMethodMatch(src1, src2);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "int x = 1;", "int x = 1;" },
                { "int x = 1", "int x = 1" },
                { "x = 1", "x = 1" },
                { "Console.WriteLine(1);", "Console.WriteLine(1);" },
                { "x++/*1A*/;", "x++/*1B*/;" },
                { "Console.WriteLine(2);", "y++;" },
                { "while (true) {     x++/*2A*/; }", "while (true)     {         x++/*2B*/;     }" },
                { "{     x++/*2A*/; }", "{         x++/*2B*/;     }" },
                { "x++/*2A*/;", "x++/*2B*/;" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void ThrowException_UpdateInsert()
        {
            var src1 = @"
return a > 3 ? a : throw new Exception();
return c > 7 ? c : 7;
";

            var src2 = @"
return a > 3 ? a : throw new ArgumentException();
return c > 7 ? c : throw new IndexOutOfRangeException();
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Regular);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "return a > 3 ? a : throw new Exception();", "return a > 3 ? a : throw new ArgumentException();" },
                { "return c > 7 ? c : 7;", "return c > 7 ? c : throw new IndexOutOfRangeException();" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void ThrowException_UpdateDelete()
        {
            var src1 = @"
return a > 3 ? a : throw new Exception();
return b > 5 ? b : throw new OperationCanceledException();
";

            var src2 = @"
return a > 3 ? a : throw new ArgumentException();
return b > 5 ? b : 5;
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Regular);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "return a > 3 ? a : throw new Exception();", "return a > 3 ? a : throw new ArgumentException();" },
                { "return b > 5 ? b : throw new OperationCanceledException();", "return b > 5 ? b : 5;" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void Tuple()
        {
            var src1 = @"
return (1, 2);
return (d, 6);
return (10, e, 22);
return (2, () => { 
    int a = 6;
    return 1;
});";

            var src2 = @"
return (1, 2, 3);
return (d, 5);
return (10, e);
return (2, () => {
    int a = 6;
    return 5;
});";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Regular);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "return (1, 2);", "return (1, 2, 3);" },
                { "return (d, 6);", "return (d, 5);" },
                { "return (10, e, 22);", "return (10, e);" },
                { "return (2, () => {      int a = 6;     return 1; });", "return (2, () => {     int a = 6;     return 5; });" },
                { "() => {      int a = 6;     return 1; }", "() => {     int a = 6;     return 5; }" },
                { "()", "()" },
                { "{      int a = 6;     return 1; }", "{     int a = 6;     return 5; }" },
                { "int a = 6;", "int a = 6;" },
                { "int a = 6", "int a = 6" },
                { "a = 6", "a = 6" },
                { "return 1;", "return 5;" }
            };

            expected.AssertEqual(actual);
        }

        #endregion

        #region Local Variables

        [Fact]
        public void Locals_Rename()
        {
            var src1 = @"
int x = 1;
";
            var src2 = @"
int y = 1;
";
            var match = GetMethodMatch(src1, src2);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "int x = 1;", "int y = 1;" },
                { "int x = 1", "int y = 1" },
                { "x = 1", "y = 1" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void Locals_TypeChange()
        {
            var src1 = @"
int x = 1;
";
            var src2 = @"
byte x = 1;
";
            var match = GetMethodMatch(src1, src2);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "int x = 1;", "byte x = 1;" },
                { "int x = 1", "byte x = 1" },
                { "x = 1", "x = 1" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void BlocksWithLocals1()
        {
            var src1 = @"
{
    int a = 1;
}
{
    int b = 2;
}
";
            var src2 = @"
{
    int a = 3;
    int b = 4;
}
{
    int b = 5;
}
";
            var match = GetMethodMatch(src1, src2);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "{     int a = 1; }", "{     int a = 3;     int b = 4; }" },
                { "int a = 1;", "int a = 3;" },
                { "int a = 1", "int a = 3" },
                { "a = 1", "a = 3" },
                { "{     int b = 2; }", "{     int b = 5; }" },
                { "int b = 2;", "int b = 5;" },
                { "int b = 2", "int b = 5" },
                { "b = 2", "b = 5" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void IfBlocksWithLocals1()
        {
            var src1 = @"
if (X)
{
    int a = 1;
}
if (Y)
{
    int b = 2;
}
";
            var src2 = @"
if (Y)
{
    int a = 3;
    int b = 4;
}
if (X)
{
    int b = 5;
}
";
            var match = GetMethodMatch(src1, src2);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "if (X) {     int a = 1; }", "if (Y) {     int a = 3;     int b = 4; }" },
                { "{     int a = 1; }", "{     int a = 3;     int b = 4; }" },
                { "int a = 1;", "int a = 3;" },
                { "int a = 1", "int a = 3" },
                { "a = 1", "a = 3" },
                { "if (Y) {     int b = 2; }", "if (X) {     int b = 5; }" },
                { "{     int b = 2; }", "{     int b = 5; }" },
                { "int b = 2;", "int b = 5;" },
                { "int b = 2", "int b = 5" },
                { "b = 2", "b = 5" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void BlocksWithLocals2()
        {
            var src1 = @"
{
    int a = 1;
}
{
    {
        int b = 2;
    }
}
";
            var src2 = @"
{
    int b = 1;
}
{
    {
        int a = 2;
    }
}
";
            var match = GetMethodMatch(src1, src2);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "{     int a = 1; }", "{         int a = 2;     }" },
                { "int a = 1;", "int a = 2;" },
                { "int a = 1", "int a = 2" },
                { "a = 1", "a = 2" },
                { "{     {         int b = 2;     } }", "{     {         int a = 2;     } }" },
                { "{         int b = 2;     }", "{     int b = 1; }" },
                { "int b = 2;", "int b = 1;" },
                { "int b = 2", "int b = 1" },
                { "b = 2", "b = 1" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void BlocksWithLocals3()
        {
            var src1 = @"
{
    int a = 1, b = 2, c = 3;
    Console.WriteLine(a + b + c);
}
{
    int c = 4, b = 5, a = 6;
    Console.WriteLine(a + b + c);
}
{
    int a = 7, b = 8;
    Console.WriteLine(a + b);
}
";
            var src2 = @"
{
    int a = 9, b = 10;
    Console.WriteLine(a + b);
}
{
    int c = 11, b = 12, a = 13;
    Console.WriteLine(a + b + c);
}
{
    int a = 14, b = 15, c = 16;
    Console.WriteLine(a + b + c);
}
";
            var match = GetMethodMatch(src1, src2);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "{     int a = 1, b = 2, c = 3;     Console.WriteLine(a + b + c); }", "{     int a = 14, b = 15, c = 16;     Console.WriteLine(a + b + c); }" },
                { "int a = 1, b = 2, c = 3;", "int a = 14, b = 15, c = 16;" },
                { "int a = 1, b = 2, c = 3", "int a = 14, b = 15, c = 16" },
                { "a = 1", "a = 14" },
                { "b = 2", "b = 15" },
                { "c = 3", "c = 16" },
                { "Console.WriteLine(a + b + c);", "Console.WriteLine(a + b + c);" },
                { "{     int c = 4, b = 5, a = 6;     Console.WriteLine(a + b + c); }", "{     int c = 11, b = 12, a = 13;     Console.WriteLine(a + b + c); }" },
                { "int c = 4, b = 5, a = 6;", "int c = 11, b = 12, a = 13;" },
                { "int c = 4, b = 5, a = 6", "int c = 11, b = 12, a = 13" },
                { "c = 4", "c = 11" },
                { "b = 5", "b = 12" },
                { "a = 6", "a = 13" },
                { "Console.WriteLine(a + b + c);", "Console.WriteLine(a + b + c);" },
                { "{     int a = 7, b = 8;     Console.WriteLine(a + b); }", "{     int a = 9, b = 10;     Console.WriteLine(a + b); }" },
                { "int a = 7, b = 8;", "int a = 9, b = 10;" },
                { "int a = 7, b = 8", "int a = 9, b = 10" },
                { "a = 7", "a = 9" },
                { "b = 8", "b = 10" },
                { "Console.WriteLine(a + b);", "Console.WriteLine(a + b);" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void VariableDesignations()
        {
            var src1 = @"
M(out int z);
N(out var a);
";

            var src2 = @"
M(out var z);
N(out var b);
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Regular);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "M(out int z);", "M(out var z);" },
                { "z", "z" },
                { "N(out var a);", "N(out var b);" },
                { "a", "b" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void ParenthesizedVariable_Update()
        {
            var src1 = @"
var (x1, (x2, x3, _)) = (1, (2, true, 3));
var (a1, a2) = (1, () => { return 7; });
";

            var src2 = @"
var (x1, (x3, x4)) = (1, (2, true));
var (a1, a3) = (1, () => { return 8; });
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Regular);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "var (x1, (x2, x3, _)) = (1, (2, true, 3));", "var (x1, (x3, x4)) = (1, (2, true));" },
                { "x1", "x1" },
                { "x2", "x4" },
                { "x3", "x3" },
                { "var (a1, a2) = (1, () => { return 7; });", "var (a1, a3) = (1, () => { return 8; });" },
                { "a1", "a1" },
                { "a2", "a3" },
                { "() => { return 7; }", "() => { return 8; }" },
                { "()", "()" },
                { "{ return 7; }", "{ return 8; }" },
                { "return 7;", "return 8;" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void ParenthesizedVariable_Insert()
        {
            var src1 = @"var (z1, z2) = (1, 2);";
            var src2 = @"var (z1, z2, z3) = (1, 2, 5);";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Regular);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "var (z1, z2) = (1, 2);", "var (z1, z2, z3) = (1, 2, 5);" },
                { "z1", "z1" },
                { "z2", "z2" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void ParenthesizedVariable_Delete()
        {
            var src1 = @"var (y1, y2, y3) = (1, 2, 7);";
            var src2 = @"var (y1, y2) = (1, 4);";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Regular);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "var (y1, y2, y3) = (1, 2, 7);", "var (y1, y2) = (1, 4);" },
                { "y1", "y1" },
                { "y2", "y2" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void RefVariable()
        {
            var src1 = @"
ref int a = ref G(new int[] { 1, 2 });
    ref int G(int[] p)
    {
        return ref p[1];
    }
";

            var src2 = @"
ref int32 a = ref G1(new int[] { 1, 2 });
    ref int G1(int[] p)
    {
        return ref p[2];
    }
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Regular);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "ref int a = ref G(new int[] { 1, 2 });", "ref int32 a = ref G1(new int[] { 1, 2 });" },
                { "ref int a = ref G(new int[] { 1, 2 })", "ref int32 a = ref G1(new int[] { 1, 2 })" },
                { "a = ref G(new int[] { 1, 2 })", "a = ref G1(new int[] { 1, 2 })" },
                { "ref int G(int[] p)     {         return ref p[1];     }", "ref int G1(int[] p)     {         return ref p[2];     }" },
                { "(int[] p)", "(int[] p)" },
                { "int[] p", "int[] p" },
                { "{         return ref p[1];     }", "{         return ref p[2];     }" },
                { "return ref p[1];", "return ref p[2];" }
            };

            expected.AssertEqual(actual);
        }

        #endregion

        #region Lambdas

        [Fact]
        public void Lambdas1()
        {
            var src1 = "Action x = a => a;";
            var src2 = "Action x = (a) => a;";

            var match = GetMethodMatch(src1, src2);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "Action x = a => a;", "Action x = (a) => a;" },
                { "Action x = a => a", "Action x = (a) => a" },
                { "x = a => a", "x = (a) => a" },
                { "a => a", "(a) => a" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void Lambdas2a()
        {
            var src1 = @"
F(x => x + 1, 1, y => y + 1, delegate(int x) { return x; }, async u => u);
";
            var src2 = @"
F(y => y + 1, G(), x => x + 1, (int x) => x, u => u, async (u, v) => u + v);
";

            var match = GetMethodMatch(src1, src2);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "F(x => x + 1, 1, y => y + 1, delegate(int x) { return x; }, async u => u);", "F(y => y + 1, G(), x => x + 1, (int x) => x, u => u, async (u, v) => u + v);" },
                { "x => x + 1", "x => x + 1" },
                { "x", "x" },
                { "y => y + 1", "y => y + 1" },
                { "y", "y" },
                { "delegate(int x) { return x; }", "(int x) => x" },
                { "(int x)", "(int x)" },
                { "int x", "int x" },
                { "async u => u", "async (u, v) => u + v" }
            };

            expected.AssertEqual(actual);
        }

        [Fact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/830419")]
        public void Lambdas2b()
        {
            var src1 = @"
F(delegate { return x; });
";
            var src2 = @"
F((a) => x, () => x);
";

            var match = GetMethodMatch(src1, src2);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "F(delegate { return x; });", "F((a) => x, () => x);" },
                { "delegate { return x; }", "() => x" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void Lambdas3()
        {
            var src1 = @"
a += async u => u;
";
            var src2 = @"
a += u => u;
";

            var match = GetMethodMatch(src1, src2);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "a += async u => u;", "a += u => u;" },
                { "async u => u", "u => u" },
                { "u", "u" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void Lambdas4()
        {
            var src1 = @"
foreach (var a in z)
{
    var e = from q in a.Where(l => l > 10) select q + 1;
}
";
            var src2 = @"
foreach (var a in z)
{
    var e = from q in a.Where(l => l < 0) select q + 1;
}
";

            var match = GetMethodMatch(src1, src2);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "foreach (var a in z) {     var e = from q in a.Where(l => l > 10) select q + 1; }", "foreach (var a in z) {     var e = from q in a.Where(l => l < 0) select q + 1; }" },
                { "{     var e = from q in a.Where(l => l > 10) select q + 1; }", "{     var e = from q in a.Where(l => l < 0) select q + 1; }" },
                { "var e = from q in a.Where(l => l > 10) select q + 1;", "var e = from q in a.Where(l => l < 0) select q + 1;" },
                { "var e = from q in a.Where(l => l > 10) select q + 1", "var e = from q in a.Where(l => l < 0) select q + 1" },
                { "e = from q in a.Where(l => l > 10) select q + 1", "e = from q in a.Where(l => l < 0) select q + 1" },
                { "from q in a.Where(l => l > 10)", "from q in a.Where(l => l < 0)" },
                { "l => l > 10", "l => l < 0" },
                { "l", "l" },
                { "select q + 1", "select q + 1" },  // select clause
                { "select q + 1", "select q + 1" }   // query body
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void Lambdas5()
        {
            var src1 = @"
F(a => b => c => d);
";
            var src2 = @"
F(a => b => c => d);
";

            var matches = GetMethodMatches(src1, src2);
            var actual = ToMatchingPairs(matches);

            var expected = new MatchingPairs
            {
                { "F(a => b => c => d);", "F(a => b => c => d);" },
                { "a => b => c => d", "a => b => c => d" },
                { "a", "a" },
                { "b => c => d", "b => c => d" },
                { "b", "b" },
                { "c => d", "c => d" },
                { "c", "c" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void Lambdas6()
        {
            var src1 = @"
F(a => b => c => d);
";
            var src2 = @"
F(a => G(b => H(c => I(d))));
";

            var matches = GetMethodMatches(src1, src2);
            var actual = ToMatchingPairs(matches);

            var expected = new MatchingPairs
            {
                { "F(a => b => c => d);", "F(a => G(b => H(c => I(d))));" },
                { "a => b => c => d", "a => G(b => H(c => I(d)))" },
                { "a", "a" },
                { "b => c => d", "b => H(c => I(d))" },
                { "b", "b" },
                { "c => d", "c => I(d)" },
                { "c", "c" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void Lambdas7()
        {
            var src1 = @"
F(a => 
{ 
    F(c => /*1*/d);
    F((u, v) => 
    {
        F((w) => c => /*2*/d);
        F(p => p);
    });
});
";
            var src2 = @"
F(a => 
{ 
    F(c => /*1*/d + 1);
    F((u, v) => 
    {
        F((w) => c => /*2*/d + 1);
        F(p => p*2);
    });
});
";

            var matches = GetMethodMatches(src1, src2);
            var actual = ToMatchingPairs(matches);

            var expected = new MatchingPairs
            {
                { "F(a =>  {      F(c => /*1*/d);     F((u, v) =>      {         F((w) => c => /*2*/d);         F(p => p);     }); });",
                  "F(a =>  {      F(c => /*1*/d + 1);     F((u, v) =>      {         F((w) => c => /*2*/d + 1);         F(p => p*2);     }); });" },
                { "a =>  {      F(c => /*1*/d);     F((u, v) =>      {         F((w) => c => /*2*/d);         F(p => p);     }); }",
                  "a =>  {      F(c => /*1*/d + 1);     F((u, v) =>      {         F((w) => c => /*2*/d + 1);         F(p => p*2);     }); }" },
                { "a", "a" },
                { "{      F(c => /*1*/d);     F((u, v) =>      {         F((w) => c => /*2*/d);         F(p => p);     }); }",
                  "{      F(c => /*1*/d + 1);     F((u, v) =>      {         F((w) => c => /*2*/d + 1);         F(p => p*2);     }); }" },
                { "F(c => /*1*/d);", "F(c => /*1*/d + 1);" },
                { "c => /*1*/d", "c => /*1*/d + 1" },
                { "c", "c" },
                { "F((u, v) =>      {         F((w) => c => /*2*/d);         F(p => p);     });", "F((u, v) =>      {         F((w) => c => /*2*/d + 1);         F(p => p*2);     });" },
                { "(u, v) =>      {         F((w) => c => /*2*/d);         F(p => p);     }", "(u, v) =>      {         F((w) => c => /*2*/d + 1);         F(p => p*2);     }" },
                { "(u, v)", "(u, v)" },
                { "u", "u" },
                { "v", "v" },
                { "{         F((w) => c => /*2*/d);         F(p => p);     }", "{         F((w) => c => /*2*/d + 1);         F(p => p*2);     }" },
                { "F((w) => c => /*2*/d);", "F((w) => c => /*2*/d + 1);" },
                { "(w) => c => /*2*/d", "(w) => c => /*2*/d + 1" },
                { "(w)", "(w)" },
                { "w", "w" },
                { "c => /*2*/d", "c => /*2*/d + 1" },
                { "c", "c" },
                { "F(p => p);", "F(p => p*2);" },
                { "p => p", "p => p*2" },
                { "p", "p" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void LambdasInArrayType()
        {
            var src1 = "var x = new int[F(a => 1)];";
            var src2 = "var x = new int[F(a => 2)];";

            var match = GetMethodMatch(src1, src2);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "var x = new int[F(a => 1)];", "var x = new int[F(a => 2)];" },
                { "var x = new int[F(a => 1)]", "var x = new int[F(a => 2)]" },
                { "x = new int[F(a => 1)]", "x = new int[F(a => 2)]" },
                { "a => 1", "a => 2" },
                { "a", "a" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void LambdasInArrayInitializer()
        {
            var src1 = "var x = new int[] { F(a => 1) };";
            var src2 = "var x = new int[] { F(a => 2) };";

            var match = GetMethodMatch(src1, src2);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "var x = new int[] { F(a => 1) };", "var x = new int[] { F(a => 2) };" },
                { "var x = new int[] { F(a => 1) }", "var x = new int[] { F(a => 2) }" },
                { "x = new int[] { F(a => 1) }", "x = new int[] { F(a => 2) }" },
                { "a => 1", "a => 2" },
                { "a", "a" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void LambdasInStackalloc()
        {
            var src1 = "var x = stackalloc int[F(a => 1)];";
            var src2 = "var x = stackalloc int[F(a => 2)];";

            var match = GetMethodMatch(src1, src2);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "var x = stackalloc int[F(a => 1)];", "var x = stackalloc int[F(a => 2)];" },
                { "var x = stackalloc int[F(a => 1)]", "var x = stackalloc int[F(a => 2)]" },
                { "x = stackalloc int[F(a => 1)]", "x = stackalloc int[F(a => 2)]" },
                { "a => 1", "a => 2" },
                { "a", "a" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void LambdasInStackalloc_Initializer()
        {
            var src1 = "var x = stackalloc[] { F(a => 1) };";
            var src2 = "var x = stackalloc[] { F(a => 2) };";

            var match = GetMethodMatch(src1, src2);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "var x = stackalloc[] { F(a => 1) };", "var x = stackalloc[] { F(a => 2) };" },
                { "var x = stackalloc[] { F(a => 1) }", "var x = stackalloc[] { F(a => 2) }" },
                { "x = stackalloc[] { F(a => 1) }", "x = stackalloc[] { F(a => 2) }" },
                { "a => 1", "a => 2" },
                { "a", "a" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void Lambdas_ParameterToDiscard()
        {
            var src1 = "var x = F((a, b) => 1);";
            var src2 = "var x = F((_, _) => 2);";

            var match = GetMethodMatch(src1, src2);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "var x = F((a, b) => 1);", "var x = F((_, _) => 2);" },
                { "var x = F((a, b) => 1)", "var x = F((_, _) => 2)" },
                { "x = F((a, b) => 1)", "x = F((_, _) => 2)" },
                { "(a, b) => 1", "(_, _) => 2" },
                { "(a, b)", "(_, _)" },
                { "a", "_" },
                { "b", "_" }
            };

            expected.AssertEqual(actual);
        }

        #endregion

        #region Local Functions

        [Fact]
        public void LocalFunctionDefinitions()
        {
            var src1 = @"
(int a, string c) F1(int i) { return null; }
(int a, int b) F2(int i) { return null; }
(int a, int b, int c) F3(int i) { return null; }
";

            var src2 = @"
(int a, int b) F1(int i) { return null; }
(int a, int b, string c) F2(int i) { return null; }
(int a, int b) F3(int i) { return null; }
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Regular);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "(int a, string c) F1(int i) { return null; }", "(int a, int b) F1(int i) { return null; }" },
                { "(int i)", "(int i)" },
                { "int i", "int i" },
                { "{ return null; }", "{ return null; }" },
                { "return null;", "return null;" },
                { "(int a, int b) F2(int i) { return null; }", "(int a, int b, string c) F2(int i) { return null; }" },
                { "(int i)", "(int i)" },
                { "int i", "int i" },
                { "{ return null; }", "{ return null; }" },
                { "return null;", "return null;" },
                { "(int a, int b, int c) F3(int i) { return null; }", "(int a, int b) F3(int i) { return null; }" },
                { "(int i)", "(int i)" },
                { "int i", "int i" },
                { "{ return null; }", "{ return null; }" },
                { "return null;", "return null;" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void LocalFunctions1()
        {
            var src1 = "object x(object a) => a; F(x);";
            var src2 = "F(a => a);";

            var match = GetMethodMatch(src1, src2);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "object x(object a) => a;", "a => a" },
                { "F(x);", "F(a => a);" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void LocalFunctions2a()
        {
            var src1 = @"
F(x => x + 1, 1, y => y + 1, delegate(int x) { return x; }, async u => u);
";
            var src2 = @"
int localF1(int y) => y + 1;
int localF2(int x) => x + 1;
int localF3(int x) => x;
int localF4(int u) => u;
async int localF5(int u, int v) =>  u + v;
F(localF1, localF2, G(), localF2, localF3, localF4, localF5);
";

            var match = GetMethodMatch(src1, src2);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "F(x => x + 1, 1, y => y + 1, delegate(int x) { return x; }, async u => u);", "F(localF1, localF2, G(), localF2, localF3, localF4, localF5);" },
                { "x => x + 1", "int localF2(int x) => x + 1;" },
                { "y => y + 1", "int localF1(int y) => y + 1;" },
                { "delegate(int x) { return x; }", "int localF3(int x) => x;" },
                { "(int x)", "(int x)" },
                { "int x", "int x" },
                { "async u => u", "int localF4(int u) => u;" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void LocalFunctions2b()
        {
            var src1 = @"
F(delegate { return x; });
";
            var src2 = @"
int localF() => x;
F(localF);
";

            var match = GetMethodMatch(src1, src2);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "F(delegate { return x; });", "F(localF);" },
                { "delegate { return x; }", "int localF() => x;" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void LocalFunctions3()
        {
            var src1 = @"
a += async u => u;
";
            var src2 = @"
object localF(object u) => u;
a += localF;
";

            var match = GetMethodMatch(src1, src2);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "a += async u => u;", "a += localF;" },
                { "async u => u", "object localF(object u) => u;" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void LocalFunctions4()
        {
            var src1 = @"int a() { int b() { int c() { int d() { return 0; } } return c(); } return b(); }";
            var src2 = @"int a() { int b() { int c() { int d() { return 0; } } return c(); } return b(); }";

            var matches = GetMethodMatches(src1, src2);
            var actual = ToMatchingPairs(matches);

            var expected = new MatchingPairs
            {
                { "int a() { int b() { int c() { int d() { return 0; } } return c(); } return b(); }",
                    "int a() { int b() { int c() { int d() { return 0; } } return c(); } return b(); }" },
                { "()", "()" },
                { "{ int b() { int c() { int d() { return 0; } } return c(); } return b(); }",
                    "{ int b() { int c() { int d() { return 0; } } return c(); } return b(); }" },
                { "int b() { int c() { int d() { return 0; } } return c(); }",
                    "int b() { int c() { int d() { return 0; } } return c(); }" },
                { "()", "()" },
                { "{ int c() { int d() { return 0; } } return c(); }",
                    "{ int c() { int d() { return 0; } } return c(); }" },
                { "int c() { int d() { return 0; } }", "int c() { int d() { return 0; } }" },
                { "()", "()" },
                { "{ int d() { return 0; } }", "{ int d() { return 0; } }" },
                { "int d() { return 0; }", "int d() { return 0; }" },
                { "()", "()" },
                { "{ return 0; }", "{ return 0; }" },
                { "return 0;", "return 0;" },
                { "return c();", "return c();" },
                { "return b();", "return b();" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void LocalFunctions5()
        {
            var src1 = @"
void G6(int a)
{ 
    int G5(int c) => /*1*/d;
    F(G5);

    void G4()
    {
        void G1(int x) => x;
        int G3(int w)
        { 
            int G2(int c) => /*2*/d;
            return G2(w);
        }
        F(G3);
        F(G1);
    };
    F(G4);
}
";

            var src2 = @"
void G6(int a)
{ 
    int G5(int c) => /*1*/d + 1;F(G5);

    void G4()
    {
        int G3(int w)
        { 
            int G2(int c) => /*2*/d + 1; return G2(w);
        }
        F(G3); F(G1); int G6(int p) => p *2; F(G6);
    }
    F(G4);
}
";

            var matches = GetMethodMatches(src1, src2);
            var actual = ToMatchingPairs(matches);

            var expected = new MatchingPairs
            {
                { "void G6(int a) {      int G5(int c) => /*1*/d;     F(G5);      void G4()     {         void G1(int x) => x;         int G3(int w)         {              int G2(int c) => /*2*/d;             return G2(w);         }         F(G3);         F(G1);     };     F(G4); }",
                    "void G6(int a) {      int G5(int c) => /*1*/d + 1;F(G5);      void G4()     {         int G3(int w)         {              int G2(int c) => /*2*/d + 1; return G2(w);         }         F(G3); F(G1); int G6(int p) => p *2; F(G6);     }     F(G4); }" },
                { "(int a)", "(int a)" },
                { "int a", "int a" },
                { "{      int G5(int c) => /*1*/d;     F(G5);      void G4()     {         void G1(int x) => x;         int G3(int w)         {              int G2(int c) => /*2*/d;             return G2(w);         }         F(G3);         F(G1);     };     F(G4); }",
                    "{      int G5(int c) => /*1*/d + 1;F(G5);      void G4()     {         int G3(int w)         {              int G2(int c) => /*2*/d + 1; return G2(w);         }         F(G3); F(G1); int G6(int p) => p *2; F(G6);     }     F(G4); }" },
                { "int G5(int c) => /*1*/d;", "int G5(int c) => /*1*/d + 1;" },
                { "(int c)", "(int c)" },
                { "int c", "int c" },
                { "F(G5);", "F(G5);" },
                { "void G4()     {         void G1(int x) => x;         int G3(int w)         {              int G2(int c) => /*2*/d;             return G2(w);         }         F(G3);         F(G1);     }",
                    "void G4()     {         int G3(int w)         {              int G2(int c) => /*2*/d + 1; return G2(w);         }         F(G3); F(G1); int G6(int p) => p *2; F(G6);     }" },
                { "()", "()" },
                { "{         void G1(int x) => x;         int G3(int w)         {              int G2(int c) => /*2*/d;             return G2(w);         }         F(G3);         F(G1);     }",
                    "{         int G3(int w)         {              int G2(int c) => /*2*/d + 1; return G2(w);         }         F(G3); F(G1); int G6(int p) => p *2; F(G6);     }" },
                { "void G1(int x) => x;", "int G6(int p) => p *2;" },
                { "(int x)", "(int p)" },
                { "int x", "int p" },
                { "int G3(int w)         {              int G2(int c) => /*2*/d;             return G2(w);         }",
                    "int G3(int w)         {              int G2(int c) => /*2*/d + 1; return G2(w);         }" },
                { "(int w)", "(int w)" },
                { "int w", "int w" },
                { "{              int G2(int c) => /*2*/d;             return G2(w);         }", "{              int G2(int c) => /*2*/d + 1; return G2(w);         }" },
                { "int G2(int c) => /*2*/d;", "int G2(int c) => /*2*/d + 1;" },
                { "(int c)", "(int c)" },
                { "int c", "int c" },
                { "return G2(w);", "return G2(w);" },
                { "F(G3);", "F(G3);" },
                { "F(G1);", "F(G1);" },
                { "F(G4);", "F(G4);" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void LocalFunctions6()
        {
            var src1 = @"int f() { return local(); int local() { return 1; }}";
            var src2 = @"int f() { return local(); int local() => 2; }";

            var matches = GetMethodMatches(src1, src2);
            var actual = ToMatchingPairs(matches);

            var expected = new MatchingPairs
            {
                { "int f() { return local(); int local() { return 1; }}", "int f() { return local(); int local() => 2; }" },
                { "()", "()" },
                { "{ return local(); int local() { return 1; }}", "{ return local(); int local() => 2; }" },
                { "return local();", "return local();" },
                { "int local() { return 1; }", "int local() => 2;" },
                { "()", "()" },
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void LocalFunctions6Reverse()
        {
            var src1 = @"int f() { return local(); int local() => 2; }";
            var src2 = @"int f() { return local(); int local() { return 1; }}";

            var matches = GetMethodMatches(src1, src2);
            var actual = ToMatchingPairs(matches);

            var expected = new MatchingPairs
            {
                { "int f() { return local(); int local() => 2; }", "int f() { return local(); int local() { return 1; }}" },
                { "()", "()" },
                { "{ return local(); int local() => 2; }", "{ return local(); int local() { return 1; }}" },
                { "return local();", "return local();" },
                { "int local() => 2;", "int local() { return 1; }" },
                { "()", "()" },
            };

            expected.AssertEqual(actual);
        }

        #endregion

        #region LINQ

        [Fact]
        public void Queries1()
        {
            var src1 = @"
var q = from c in cars
        from ud in users_details
        from bd in bids
        select 1;
";
            var src2 = @"
var q = from c in cars
        from bd in bids
        from ud in users_details
        select 2;
";

            var match = GetMethodMatch(src1, src2);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "var q = from c in cars         from ud in users_details         from bd in bids         select 1;", "var q = from c in cars         from bd in bids         from ud in users_details         select 2;" },
                { "var q = from c in cars         from ud in users_details         from bd in bids         select 1", "var q = from c in cars         from bd in bids         from ud in users_details         select 2" },
                { "q = from c in cars         from ud in users_details         from bd in bids         select 1", "q = from c in cars         from bd in bids         from ud in users_details         select 2" },
                { "from c in cars", "from c in cars" },
                { "from ud in users_details         from bd in bids         select 1", "from bd in bids         from ud in users_details         select 2" },
                { "from ud in users_details", "from ud in users_details" },
                { "from bd in bids", "from bd in bids" },
                { "select 1", "select 2" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void Queries2()
        {
            var src1 = @"
var q = from c in cars
        from ud in users_details
        from bd in bids
        orderby c.listingOption descending
        where a.userID == ud.userid
        let images = from ai in auction_images
                     where ai.belongs_to == c.id
                     select ai
        let bid = (from b in bids
                    orderby b.id descending
                    where b.carID == c.id
                    select b.bidamount).FirstOrDefault()
        select bid;
";
            var src2 = @"
var q = from c in cars
        from ud in users_details
        from bd in bids
        orderby c.listingOption descending
        where a.userID == ud.userid
        let images = from ai in auction_images
                     where ai.belongs_to == c.id2
                     select ai + 1
        let bid = (from b in bids
                    orderby b.id ascending
                    where b.carID == c.id2
                    select b.bidamount).FirstOrDefault()
        select bid;
";

            var match = GetMethodMatches(src1, src2);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "var q = from c in cars         from ud in users_details         from bd in bids         orderby c.listingOption descending         where a.userID == ud.userid         let images = from ai in auction_images                      where ai.belongs_to == c.id                      select ai         let bid = (from b in bids                     orderby b.id descending                     where b.carID == c.id                     select b.bidamount).FirstOrDefault()         select bid;",
                  "var q = from c in cars         from ud in users_details         from bd in bids         orderby c.listingOption descending         where a.userID == ud.userid         let images = from ai in auction_images                      where ai.belongs_to == c.id2                      select ai + 1         let bid = (from b in bids                     orderby b.id ascending                     where b.carID == c.id2                     select b.bidamount).FirstOrDefault()         select bid;" },
                { "var q = from c in cars         from ud in users_details         from bd in bids         orderby c.listingOption descending         where a.userID == ud.userid         let images = from ai in auction_images                      where ai.belongs_to == c.id                      select ai         let bid = (from b in bids                     orderby b.id descending                     where b.carID == c.id                     select b.bidamount).FirstOrDefault()         select bid",
                  "var q = from c in cars         from ud in users_details         from bd in bids         orderby c.listingOption descending         where a.userID == ud.userid         let images = from ai in auction_images                      where ai.belongs_to == c.id2                      select ai + 1         let bid = (from b in bids                     orderby b.id ascending                     where b.carID == c.id2                     select b.bidamount).FirstOrDefault()         select bid" },
                { "q = from c in cars         from ud in users_details         from bd in bids         orderby c.listingOption descending         where a.userID == ud.userid         let images = from ai in auction_images                      where ai.belongs_to == c.id                      select ai         let bid = (from b in bids                     orderby b.id descending                     where b.carID == c.id                     select b.bidamount).FirstOrDefault()         select bid",
                  "q = from c in cars         from ud in users_details         from bd in bids         orderby c.listingOption descending         where a.userID == ud.userid         let images = from ai in auction_images                      where ai.belongs_to == c.id2                      select ai + 1         let bid = (from b in bids                     orderby b.id ascending                     where b.carID == c.id2                     select b.bidamount).FirstOrDefault()         select bid" },
                { "from c in cars", "from c in cars" },
                { "from ud in users_details         from bd in bids         orderby c.listingOption descending         where a.userID == ud.userid         let images = from ai in auction_images                      where ai.belongs_to == c.id                      select ai         let bid = (from b in bids                     orderby b.id descending                     where b.carID == c.id                     select b.bidamount).FirstOrDefault()         select bid", "from ud in users_details         from bd in bids         orderby c.listingOption descending         where a.userID == ud.userid         let images = from ai in auction_images                      where ai.belongs_to == c.id2                      select ai + 1         let bid = (from b in bids                     orderby b.id ascending                     where b.carID == c.id2                     select b.bidamount).FirstOrDefault()         select bid" },
                { "from ud in users_details", "from ud in users_details" },
                { "from bd in bids", "from bd in bids" },
                { "orderby c.listingOption descending", "orderby c.listingOption descending" },
                { "c.listingOption descending", "c.listingOption descending" },
                { "where a.userID == ud.userid", "where a.userID == ud.userid" },
                { "let images = from ai in auction_images                      where ai.belongs_to == c.id                      select ai",
                  "let images = from ai in auction_images                      where ai.belongs_to == c.id2                      select ai + 1" },
                { "from ai in auction_images", "from ai in auction_images" },
                { "where ai.belongs_to == c.id                      select ai", "where ai.belongs_to == c.id2                      select ai + 1" },
                { "where ai.belongs_to == c.id", "where ai.belongs_to == c.id2" },
                { "select ai", "select ai + 1" },
                { "let bid = (from b in bids                     orderby b.id descending                     where b.carID == c.id                     select b.bidamount).FirstOrDefault()",
                  "let bid = (from b in bids                     orderby b.id ascending                     where b.carID == c.id2                     select b.bidamount).FirstOrDefault()" },
                { "from b in bids", "from b in bids" },
                { "orderby b.id descending                     where b.carID == c.id                     select b.bidamount", "orderby b.id ascending                     where b.carID == c.id2                     select b.bidamount" },
                { "orderby b.id descending", "orderby b.id ascending" },
                { "b.id descending", "b.id ascending" },
                { "where b.carID == c.id", "where b.carID == c.id2" },
                { "select b.bidamount", "select b.bidamount" },
                { "select bid", "select bid" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void Queries3()
        {
            var src1 = @"
var q = from a in await seq1
        join c in await seq2 on F(u => u) equals G(s => s) into g1
        join l in await seq3 on F(v => v) equals G(t => t) into g2
        select a;

";
            var src2 = @"
var q = from a in await seq1
        join c in await seq2 on F(u => u + 1) equals G(s => s + 3) into g1
        join c in await seq3 on F(vv => vv + 2) equals G(tt => tt + 4) into g2
        select a + 1;
";

            var match = GetMethodMatches(src1, src2, MethodKind.Async);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "var q = from a in await seq1         join c in await seq2 on F(u => u) equals G(s => s) into g1         join l in await seq3 on F(v => v) equals G(t => t) into g2         select a;", "var q = from a in await seq1         join c in await seq2 on F(u => u + 1) equals G(s => s + 3) into g1         join c in await seq3 on F(vv => vv + 2) equals G(tt => tt + 4) into g2         select a + 1;" },
                { "var q = from a in await seq1         join c in await seq2 on F(u => u) equals G(s => s) into g1         join l in await seq3 on F(v => v) equals G(t => t) into g2         select a", "var q = from a in await seq1         join c in await seq2 on F(u => u + 1) equals G(s => s + 3) into g1         join c in await seq3 on F(vv => vv + 2) equals G(tt => tt + 4) into g2         select a + 1" },
                { "q = from a in await seq1         join c in await seq2 on F(u => u) equals G(s => s) into g1         join l in await seq3 on F(v => v) equals G(t => t) into g2         select a", "q = from a in await seq1         join c in await seq2 on F(u => u + 1) equals G(s => s + 3) into g1         join c in await seq3 on F(vv => vv + 2) equals G(tt => tt + 4) into g2         select a + 1" },
                { "from a in await seq1", "from a in await seq1" },
                { "await seq1", "await seq1" },
                { "join c in await seq2 on F(u => u) equals G(s => s) into g1         join l in await seq3 on F(v => v) equals G(t => t) into g2         select a", "join c in await seq2 on F(u => u + 1) equals G(s => s + 3) into g1         join c in await seq3 on F(vv => vv + 2) equals G(tt => tt + 4) into g2         select a + 1" },
                { "join c in await seq2 on F(u => u) equals G(s => s) into g1", "join c in await seq2 on F(u => u + 1) equals G(s => s + 3) into g1" },
                { "await seq2", "await seq2" },
                { "u => u", "u => u + 1" },
                { "u", "u" },
                { "s => s", "s => s + 3" },
                { "s", "s" },
                { "into g1", "into g1" },
                { "join l in await seq3 on F(v => v) equals G(t => t) into g2", "join c in await seq3 on F(vv => vv + 2) equals G(tt => tt + 4) into g2" },
                { "await seq3", "await seq3" },
                { "v => v", "vv => vv + 2" },
                { "v", "vv" },
                { "t => t", "tt => tt + 4" },
                { "t", "tt" },
                { "into g2", "into g2" },
                { "select a", "select a + 1" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void Queries4()
        {
            var src1 = "F(from a in await b from x in y select c);";
            var src2 = "F(from a in await c from x in y select c);";

            var match = GetMethodMatches(src1, src2, MethodKind.Async);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "F(from a in await b from x in y select c);", "F(from a in await c from x in y select c);" },
                { "from a in await b", "from a in await c" },
                { "await b", "await c" },
                { "from x in y select c", "from x in y select c" },
                { "from x in y", "from x in y" },
                { "select c", "select c" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void Queries5()
        {
            var src1 = "F(from a in b  group a by a.x into g  select g);";
            var src2 = "F(from a in b  group z by z.y into h  select h);";

            var match = GetMethodMatches(src1, src2);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "F(from a in b  group a by a.x into g  select g);", "F(from a in b  group z by z.y into h  select h);" },
                { "from a in b", "from a in b" },
                { "group a by a.x into g  select g", "group z by z.y into h  select h" },
                { "group a by a.x", "group z by z.y" },
                { "into g  select g", "into h  select h" },
                { "select g", "select h" },
                { "select g", "select h" }
            };

            expected.AssertEqual(actual);
        }

        #endregion

        #region Iterators

        [Fact]
        public void Yields()
        {
            var src1 = @"
yield return 0;
yield return 1;
";
            var src2 = @"
yield break;
yield return 1;
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Iterator);
            var actual = ToMatchingPairs(match);

            // yield return should not match yield break
            var expected = new MatchingPairs()
            {
                { "yield return 1;", "yield return 1;" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void YieldReturn_Add()
        {
            var src1 = @"
yield return /*1*/ 1;
yield return /*2*/ 2;
";
            var src2 = @"
yield return /*3*/ 3;
yield return /*1*/ 1;
yield return /*2*/ 2;
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Iterator);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "yield return /*1*/ 1;", "yield return /*1*/ 1;" },
                { "yield return /*2*/ 2;", "yield return /*2*/ 2;" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void YieldReturn_Swap1()
        {
            var src1 = @"
A();
yield return /*1*/ 1;
B();
yield return /*2*/ 2;
C();
";
            var src2 = @"
B();
yield return /*2*/ 2;
A();
yield return /*1*/ 1;
C();
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Iterator);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "A();", "A();" },
                { "yield return /*1*/ 1;", "yield return /*1*/ 1;" },
                { "B();", "B();" },
                { "yield return /*2*/ 2;", "yield return /*2*/ 2;" },
                { "C();", "C();" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void YieldReturn_Swap2()
        {
            var src1 = @"
yield return /*1*/ 1;

{
    yield return /*2*/ 2;
}

foreach (var x in y) { yield return /*3*/ 3; }
";
            var src2 = @"
yield return /*1*/ 1;
yield return /*2*/ 3;
foreach (var x in y) { yield return /*3*/ 2; }
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Iterator);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "yield return /*1*/ 1;", "yield return /*1*/ 1;" },
                { "{     yield return /*2*/ 2; }", "{ yield return /*3*/ 2; }" },
                { "yield return /*2*/ 2;", "yield return /*3*/ 2;" },
                { "foreach (var x in y) { yield return /*3*/ 3; }", "foreach (var x in y) { yield return /*3*/ 2; }" }
            };

            expected.AssertEqual(actual);
        }

        #endregion

        #region Async

        [Fact]
        public void AwaitExpressions()
        {
            var src1 = "F(await x, await y);";
            var src2 = "F(await y, await x);";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Async);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "F(await x, await y);", "F(await y, await x);" },
                { "await x", "await x" },
                { "await y", "await y" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void Awaits()
        {
            var src1 = @"
await x;
await using (expr) {}
await using (D y = new D()) {}
await using D y = new D();
await foreach (var z in w) {} 
await foreach (var (u, v) in w) {}
";
            var src2 = @"
await foreach (var (u, v) in w) {}
await foreach (var z in w) {} 
await using D y = new D();
await using (D y = new D()) {}
await using (expr) {}
await x;
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Async);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "await x;", "await x;" },
                { "await x", "await x" },
                { "await using (expr) {}", "await using (expr) {}" },
                { "{}", "{}" },
                { "await using (D y = new D()) {}", "await using (D y = new D()) {}" },
                { "D y = new D()", "D y = new D()" },
                { "y = new D()", "y = new D()" },
                { "{}", "{}" },
                { "await using D y = new D();", "await using D y = new D();" },
                { "D y = new D()", "D y = new D()" },
                { "y = new D()", "y = new D()" },
                { "await foreach (var z in w) {}", "await foreach (var z in w) {}" },
                { "{}", "{}" },
                { "await foreach (var (u, v) in w) {}", "await foreach (var (u, v) in w) {}" },
                { "u", "u" },
                { "v", "v" },
                { "{}", "{}" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void Await_To_AwaitUsingExpression()
        {
            var src1 = @"
await x;
";
            var src2 = @"
await using (expr) {}
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Async);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs();

            expected.AssertEqual(actual);
        }

        [Fact]
        public void Await_To_AwaitUsingDecl()
        {
            var src1 = @"
await x;
";
            var src2 = @"
await using D y = new D();
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Async);
            var actual = ToMatchingPairs(match);

            // empty - treated as different awaits as the await using awaits at the end of the scope
            var expected = new MatchingPairs();

            expected.AssertEqual(actual);
        }

        [Fact]
        public void AwaitUsingDecl_To_AwaitUsingStatement()
        {
            var src1 = @"
await using D y = new D();
";
            var src2 = @"
await using (D y = new D()) { }
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Async);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs();

            expected.AssertEqual(actual);
        }

        [Fact]
        public void AwaitUsingExpression_To_AwaitUsingStatementWithSingleVariable()
        {
            var src1 = @"
await using (y = new D()) { }
";
            var src2 = @"
await using (D y = new D()) { }
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Async);
            var actual = ToMatchingPairs(match);

            // Using with a single variable could match using with an expression because they both generate a single try-finally block,
            // but to simplify logic we do not match them currently.
            var expected = new MatchingPairs
            {
                { "{ }", "{ }" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void AwaitUsingExpression_To_AwaitUsingStatementWithMultipleVariables()
        {
            var src1 = @"
await using (y = new D()) { }
";
            var src2 = @"
await using (D y = new D(), z = new D()) { }
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Async);
            var actual = ToMatchingPairs(match);

            // Using with multiple variables should not match using with an expression because they generate different number of try-finally blocks.
            var expected = new MatchingPairs
            {
                { "{ }", "{ }" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void AwaitUsingMatchesUSing()
        {
            var src1 = "await x;foreach (T x in y) {}";
            var src2 = "await x;await foreach (T x in y) {}";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Async);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs()
            {
                { "await x;", "await x;" },
                { "await x", "await x" },
                { "foreach (T x in y) {}", "await foreach (T x in y) {}" },
                { "{}", "{}" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void Await_To_AwaitForeach()
        {
            var src1 = @"
await x;
";
            var src2 = @"
await foreach (var x in y) {}
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Async);
            var actual = ToMatchingPairs(match);

            // empty - treated as different awaits as the foreach awaits in each loop iteration
            var expected = new MatchingPairs();

            expected.AssertEqual(actual);
        }

        [Fact]
        public void Await_To_AwaitForeachVar()
        {
            var src1 = @"
await x;
";
            var src2 = @"
await foreach (var (x, y) in z) {}
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Async);
            var actual = ToMatchingPairs(match);

            // empty - treated as different awaits as the foreach awaits in each loop iteration
            var expected = new MatchingPairs();

            expected.AssertEqual(actual);
        }

        [Fact]
        public void AwaitForeach_To_AwaitForeachVar()
        {
            var src1 = @"
await foreach (var x in y) {}
";
            var src2 = @"
await foreach (var (u, v) in y) {}
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Async);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "await foreach (var x in y) {}", "await foreach (var (u, v) in y) {}" },
                { "{}", "{}" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void AwaitForeachMatchesForeach()
        {
            var src1 = "await x;foreach (T x in y) {}";
            var src2 = "await x;await foreach (T x in y) {}";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Async);
            var actual = ToMatchingPairs(match);

            // We do match await foreach to foreach, even though the latter does not represent state machine.
            // This is ok since the previous version of the method won't have state machine state associated with the 
            // foreach statement and thus matching state machine state for the await foreach won't succeed even though 
            // the syntax nodes match.
            var expected = new MatchingPairs()
            {
                { "await x;", "await x;" },
                { "await x", "await x" },
                { "foreach (T x in y) {}", "await foreach (T x in y) {}" },
                { "{}", "{}" }
            };

            expected.AssertEqual(actual);
        }

        #endregion

        #region Constructors

        [Fact]
        public void ConstructorWithInitializer1()
        {
            var src1 = @"
(int x = 1) : base(a => a + 1) { Console.WriteLine(1); }
";
            var src2 = @"
(int x = 1) : base(a => a + 1) { Console.WriteLine(1); }
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.ConstructorWithParameters);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "(int x = 1)", "(int x = 1)" },
                { "int x = 1", "int x = 1" },
                { "a => a + 1", "a => a + 1" },
                { "a", "a" },
                { "{ Console.WriteLine(1); }", "{ Console.WriteLine(1); }" },
                { "Console.WriteLine(1);", "Console.WriteLine(1);" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void ConstructorWithInitializer2()
        {
            var src1 = @"
() : base(a => a + 1) { Console.WriteLine(1); }
";
            var src2 = @"
() { Console.WriteLine(1); }
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.ConstructorWithParameters);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "()", "()" },
                { "{ Console.WriteLine(1); }", "{ Console.WriteLine(1); }" },
                { "Console.WriteLine(1);", "Console.WriteLine(1);" }
            };

            expected.AssertEqual(actual);
        }

        #endregion

        #region Exception Handlers

        [Fact]
        public void ExceptionHandlers()
        {
            var src1 = @"
try { throw new InvalidOperationException(1); }
catch (IOException e) when (filter(e)) { Console.WriteLine(2); }
catch (Exception e) when (filter(e)) { Console.WriteLine(3); }
";
            var src2 = @"
try { throw new InvalidOperationException(10); }
catch (IOException e) when (filter(e)) { Console.WriteLine(20); }
catch (Exception e) when (filter(e)) { Console.WriteLine(30); }
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Regular);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "try { throw new InvalidOperationException(1); } catch (IOException e) when (filter(e)) { Console.WriteLine(2); } catch (Exception e) when (filter(e)) { Console.WriteLine(3); }", "try { throw new InvalidOperationException(10); } catch (IOException e) when (filter(e)) { Console.WriteLine(20); } catch (Exception e) when (filter(e)) { Console.WriteLine(30); }" },
                { "{ throw new InvalidOperationException(1); }", "{ throw new InvalidOperationException(10); }" },
                { "throw new InvalidOperationException(1);", "throw new InvalidOperationException(10);" },
                { "catch (IOException e) when (filter(e)) { Console.WriteLine(2); }", "catch (IOException e) when (filter(e)) { Console.WriteLine(20); }" },
                { "(IOException e)", "(IOException e)" },
                { "when (filter(e))", "when (filter(e))" },
                { "{ Console.WriteLine(2); }", "{ Console.WriteLine(20); }" },
                { "Console.WriteLine(2);", "Console.WriteLine(20);" },
                { "catch (Exception e) when (filter(e)) { Console.WriteLine(3); }", "catch (Exception e) when (filter(e)) { Console.WriteLine(30); }" },
                { "(Exception e)", "(Exception e)" },
                { "when (filter(e))", "when (filter(e))" },
                { "{ Console.WriteLine(3); }", "{ Console.WriteLine(30); }" },
                { "Console.WriteLine(3);", "Console.WriteLine(30);" }
            };

            expected.AssertEqual(actual);
        }

        #endregion

        #region Foreach

        [Fact]
        public void ForeachVariable_Update1()
        {
            var src1 = @"
foreach (var (a1, a2) in e) { A1(); }
foreach ((var b1, var b2) in e) { A2(); }
";

            var src2 = @"
foreach (var (a1, a3) in e) { A1(); }
foreach ((var b3, int b2) in e) { A2(); }
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Regular);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "foreach (var (a1, a2) in e) { A1(); }", "foreach (var (a1, a3) in e) { A1(); }" },
                { "a1", "a1" },
                { "a2", "a3" },
                { "{ A1(); }", "{ A1(); }" },
                { "A1();", "A1();" },
                { "foreach ((var b1, var b2) in e) { A2(); }", "foreach ((var b3, int b2) in e) { A2(); }" },
                { "b1", "b3" },
                { "b2", "b2" },
                { "{ A2(); }", "{ A2(); }" },
                { "A2();", "A2();" },
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void ForeachVariable_Update2()
        {
            var src1 = @"
foreach (_ in e2) { }
foreach (_ in e3) { A(); }
";

            var src2 = @"
foreach (_ in e4) { A(); }
foreach (var b in e2) { }
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Regular);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "foreach (_ in e2) { }", "foreach (var b in e2) { }" },
                { "{ }", "{ }" },
                { "foreach (_ in e3) { A(); }", "foreach (_ in e4) { A(); }" },
                { "{ A(); }", "{ A(); }" },
                { "A();", "A();" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void ForeachVariable_Insert()
        {
            var src1 = @"
foreach (var ((a3, a4), _) in e) { }
foreach ((var b4, var b5) in e) { }
";

            var src2 = @"
foreach (var ((a3, a5, a4), _) in e) { }
foreach ((var b6, var b4, var b5) in e) { }
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Regular);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "foreach (var ((a3, a4), _) in e) { }", "foreach (var ((a3, a5, a4), _) in e) { }" },
                { "a3", "a3" },
                { "a4", "a4" },
                { "{ }", "{ }" },
                { "foreach ((var b4, var b5) in e) { }", "foreach ((var b6, var b4, var b5) in e) { }" },
                { "b4", "b4" },
                { "b5", "b5" },
                { "{ }", "{ }" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void ForeachVariable_Delete()
        {
            var src1 = @"
foreach (var (a11, a12, a13) in e) { A1(); }
foreach ((var b7, var b8, var b9) in e) { A2(); }
";

            var src2 = @"
foreach (var (a12, a13) in e1) { A1(); }
foreach ((var b7, var b9) in e) { A2(); }
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Regular);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "foreach (var (a11, a12, a13) in e) { A1(); }", "foreach (var (a12, a13) in e1) { A1(); }" },
                { "a12", "a12" },
                { "a13", "a13" },
                { "{ A1(); }", "{ A1(); }" },
                { "A1();", "A1();" },
                { "foreach ((var b7, var b8, var b9) in e) { A2(); }", "foreach ((var b7, var b9) in e) { A2(); }" },
                { "b7", "b7" },
                { "b9", "b9" },
                { "{ A2(); }", "{ A2(); }" },
                { "A2();", "A2();" }
            };

            expected.AssertEqual(actual);
        }

        #endregion

        #region Patterns

        [Fact]
        public void ConstantPattern()
        {
            var src1 = @"
if ((o is null) && (y == 7)) return 3;
if (a is 7) return 5;
";

            var src2 = @"
if ((o1 is null) && (y == 7)) return 3;
if (a is 77) return 5;
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Regular);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs {
                { "if ((o is null) && (y == 7)) return 3;", "if ((o1 is null) && (y == 7)) return 3;" },
                { "return 3;", "return 3;" },
                { "if (a is 7) return 5;", "if (a is 77) return 5;" },
                { "return 5;", "return 5;" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void DeclarationPattern()
        {
            var src1 = @"
if (!(o is int i) && (y == 7)) return;
if (!(a is string s)) return;
if (!(b is string t)) return;
if (!(c is int j)) return;
";

            var src2 = @"
if (!(b is string t1)) return;
if (!(o1 is int i) && (y == 7)) return;
if (!(c is int)) return;
if (!(a is int s)) return;
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Regular);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs {
                { "if (!(o is int i) && (y == 7)) return;", "if (!(o1 is int i) && (y == 7)) return;" },
                { "i", "i" },
                { "return;", "return;" },
                { "if (!(a is string s)) return;", "if (!(a is int s)) return;" },
                { "s", "s" },
                { "return;", "return;" },
                { "if (!(b is string t)) return;", "if (!(b is string t1)) return;" },
                { "t", "t1" },
                { "return;", "return;" },
                { "if (!(c is int j)) return;", "if (!(c is int)) return;" },
                { "return;", "return;" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void VarPattern()
        {
            var src1 = @"
if (!(o is (var x, var y))) return;
if (!(o4 is (string a, var (b, c)))) return;
if (!(o2 is var (e, f, g))) return;
if (!(o3 is var (k, l, m))) return;
";

            var src2 = @"
if (!(o is (int x, int y1)))  return;
if (!(o1 is (var a, (var b, string c1)))) return;
if (!(o7 is var (g, e, f))) return;
if (!(o3 is (string k, int l2, int m))) return;
";
            var match = GetMethodMatches(src1, src2, kind: MethodKind.Regular);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs {
                { "if (!(o is (var x, var y))) return;", "if (!(o is (int x, int y1)))  return;" },
                { "x", "x" },
                { "y", "y1" },
                { "return;", "return;" },
                { "if (!(o4 is (string a, var (b, c)))) return;", "if (!(o1 is (var a, (var b, string c1)))) return;" },
                { "a", "a" },
                { "b", "b" },
                { "c", "c1" },
                { "return;", "return;" },
                { "if (!(o2 is var (e, f, g))) return;", "if (!(o7 is var (g, e, f))) return;" },
                { "e", "e" },
                { "f", "f" },
                { "g", "g" },
                { "return;", "return;" },
                { "if (!(o3 is var (k, l, m))) return;", "if (!(o3 is (string k, int l2, int m))) return;" },
                { "k", "k" },
                { "l", "l2" },
                { "m", "m" },
                { "return;", "return;" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void PositionalPattern()
        {
            var src1 = @"var r = (x, y, z) switch {
(1, 2, 3) => 0,
(var a, 3, 4) => a,
(0, var b, int c) when c > 1 => 2,
(1, 1, Point { X: 0 } p) => 3,
_ => 4
};
";

            var src2 = @"var r = ((x, y, z)) switch {
(1, 2, 3) => 0,
(var a1, 3, 4) => a1 * 2,
(_, int b1, double c1) when c1 > 2 => c1,
(1, 1, Point { Y: 0 } p1) => 3,
_ => 4
};
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Regular);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs {
                { "var r = (x, y, z) switch { (1, 2, 3) => 0, (var a, 3, 4) => a, (0, var b, int c) when c > 1 => 2, (1, 1, Point { X: 0 } p) => 3, _ => 4 };", "var r = ((x, y, z)) switch { (1, 2, 3) => 0, (var a1, 3, 4) => a1 * 2, (_, int b1, double c1) when c1 > 2 => c1, (1, 1, Point { Y: 0 } p1) => 3, _ => 4 };" },
                { "var r = (x, y, z) switch { (1, 2, 3) => 0, (var a, 3, 4) => a, (0, var b, int c) when c > 1 => 2, (1, 1, Point { X: 0 } p) => 3, _ => 4 }", "var r = ((x, y, z)) switch { (1, 2, 3) => 0, (var a1, 3, 4) => a1 * 2, (_, int b1, double c1) when c1 > 2 => c1, (1, 1, Point { Y: 0 } p1) => 3, _ => 4 }" },
                { "r = (x, y, z) switch { (1, 2, 3) => 0, (var a, 3, 4) => a, (0, var b, int c) when c > 1 => 2, (1, 1, Point { X: 0 } p) => 3, _ => 4 }", "r = ((x, y, z)) switch { (1, 2, 3) => 0, (var a1, 3, 4) => a1 * 2, (_, int b1, double c1) when c1 > 2 => c1, (1, 1, Point { Y: 0 } p1) => 3, _ => 4 }" },
                { "(x, y, z) switch { (1, 2, 3) => 0, (var a, 3, 4) => a, (0, var b, int c) when c > 1 => 2, (1, 1, Point { X: 0 } p) => 3, _ => 4 }", "((x, y, z)) switch { (1, 2, 3) => 0, (var a1, 3, 4) => a1 * 2, (_, int b1, double c1) when c1 > 2 => c1, (1, 1, Point { Y: 0 } p1) => 3, _ => 4 }" },
                { "(1, 2, 3) => 0", "(1, 2, 3) => 0" },
                { "(var a, 3, 4) => a", "(var a1, 3, 4) => a1 * 2" },
                { "a", "a1" },
                { "(0, var b, int c) when c > 1 => 2", "(_, int b1, double c1) when c1 > 2 => c1" },
                { "b", "c1" },
                { "c", "b1" },
                { "when c > 1", "when c1 > 2" },
                { "(1, 1, Point { X: 0 } p) => 3", "(1, 1, Point { Y: 0 } p1) => 3" },
                { "p", "p1" },
                { "_ => 4", "_ => 4" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void PropertyPattern()
        {
            var src1 = @"
if (address is { State: ""WA"" }) return 1;
if (obj is { Color: Color.Purple }) return 2;
if (o is string { Length: 5 } s) return 3;
";

            var src2 = @"
if (address is { ZipCode: 98052 }) return 4;
if (obj is { Size: Size.M }) return 2;
if (o is string { Length: 7 } s7) return 5;
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Regular);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs {
                { "if (address is { State: \"WA\" }) return 1;", "if (address is { ZipCode: 98052 }) return 4;" },
                { "return 1;", "return 4;" },
                { "if (obj is { Color: Color.Purple }) return 2;", "if (obj is { Size: Size.M }) return 2;" },
                { "return 2;", "return 2;" },
                { "if (o is string { Length: 5 } s) return 3;", "if (o is string { Length: 7 } s7) return 5;" },
                { "s", "s7" },
                { "return 3;", "return 5;" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void RecursivePatterns()
        {
            var src1 = @"var r = obj switch
{
    string s when s.Length > 0 => (s, obj1) switch
    {
        (""a"", int i) => i,
        ("""", Task<int> t) => await t,
        _ => 0
    },
    int i => i * i,
    _ => -1
};
";

            var src2 = @"var r = obj switch
{
    string s when s.Length > 0 => (s, obj1) switch
    {
        (""b"", decimal i1) => i1,
        ("""", Task<object> obj2) => await obj2,
        _ => 0
    },
    double i => i * i,
    _ => -1
};
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Async);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "var r = obj switch {     string s when s.Length > 0 => (s, obj1) switch     {         (\"a\", int i) => i,         (\"\", Task<int> t) => await t,         _ => 0     },     int i => i * i,     _ => -1 };", "var r = obj switch {     string s when s.Length > 0 => (s, obj1) switch     {         (\"b\", decimal i1) => i1,         (\"\", Task<object> obj2) => await obj2,         _ => 0     },     double i => i * i,     _ => -1 };" },
                { "var r = obj switch {     string s when s.Length > 0 => (s, obj1) switch     {         (\"a\", int i) => i,         (\"\", Task<int> t) => await t,         _ => 0     },     int i => i * i,     _ => -1 }", "var r = obj switch {     string s when s.Length > 0 => (s, obj1) switch     {         (\"b\", decimal i1) => i1,         (\"\", Task<object> obj2) => await obj2,         _ => 0     },     double i => i * i,     _ => -1 }" },
                { "r = obj switch {     string s when s.Length > 0 => (s, obj1) switch     {         (\"a\", int i) => i,         (\"\", Task<int> t) => await t,         _ => 0     },     int i => i * i,     _ => -1 }", "r = obj switch {     string s when s.Length > 0 => (s, obj1) switch     {         (\"b\", decimal i1) => i1,         (\"\", Task<object> obj2) => await obj2,         _ => 0     },     double i => i * i,     _ => -1 }" },
                { "obj switch {     string s when s.Length > 0 => (s, obj1) switch     {         (\"a\", int i) => i,         (\"\", Task<int> t) => await t,         _ => 0     },     int i => i * i,     _ => -1 }", "obj switch {     string s when s.Length > 0 => (s, obj1) switch     {         (\"b\", decimal i1) => i1,         (\"\", Task<object> obj2) => await obj2,         _ => 0     },     double i => i * i,     _ => -1 }" },
                { "string s when s.Length > 0 => (s, obj1) switch     {         (\"a\", int i) => i,         (\"\", Task<int> t) => await t,         _ => 0     }", "string s when s.Length > 0 => (s, obj1) switch     {         (\"b\", decimal i1) => i1,         (\"\", Task<object> obj2) => await obj2,         _ => 0     }" },
                { "s", "s" },
                { "when s.Length > 0", "when s.Length > 0" },
                { "(s, obj1) switch     {         (\"a\", int i) => i,         (\"\", Task<int> t) => await t,         _ => 0     }", "(s, obj1) switch     {         (\"b\", decimal i1) => i1,         (\"\", Task<object> obj2) => await obj2,         _ => 0     }" },
                { "(\"a\", int i) => i", "(\"b\", decimal i1) => i1" },
                { "i", "i" },
                { "(\"\", Task<int> t) => await t", "(\"\", Task<object> obj2) => await obj2" },
                { "t", "obj2" },
                { "await t", "await obj2" },
                { "_ => 0", "_ => 0" },
                { "int i => i * i", "double i => i * i" },
                { "i", "i1" },
                { "_ => -1", "_ => -1" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void CasePattern_UpdateInsert()
        {
            var src1 = @"
switch(shape)
{
    case Circle c: return 1;
    default: return 4;
}
";

            var src2 = @"
switch(shape)
{
    case Circle c1: return 1;
    case Point p: return 0;
    default: return 4;
}
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Regular);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs {
                { "switch(shape) {     case Circle c: return 1;     default: return 4; }", "switch(shape) {     case Circle c1: return 1;     case Point p: return 0;     default: return 4; }" },
                { "case Circle c: return 1;", "case Circle c1: return 1;" },
                { "case Circle c:", "case Circle c1:" },
                { "c", "c1" },
                { "return 1;", "return 1;" },
                { "default: return 4;", "default: return 4;" },
                { "return 4;", "return 4;" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void CasePattern_UpdateDelete()
        {
            var src1 = @"
switch(shape)
{
    case Point p: return 0;
    case Circle c: return 1;
}
";

            var src2 = @"
switch(shape)
{
    case Circle circle: return 1;
}
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Regular);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs {
                { "switch(shape) {     case Point p: return 0;     case Circle c: return 1; }", "switch(shape) {     case Circle circle: return 1; }" },
                { "case Circle c: return 1;", "case Circle circle: return 1;" },
                { "case Circle c:", "case Circle circle:" },
                { "c", "circle" },
                { "return 1;", "return 1;" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void WhenCondition()
        {
            var src1 = @"
switch(shape)
{
    case Circle c when (c < 10): return 1;
    case Circle c when (c > 100): return 2;
}
";

            var src2 = @"
switch(shape)
{
    case Circle c when (c < 5): return 1;
    case Circle c2 when (c2 > 100): return 2;
}
";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Regular);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "switch(shape) {     case Circle c when (c < 10): return 1;     case Circle c when (c > 100): return 2; }", "switch(shape) {     case Circle c when (c < 5): return 1;     case Circle c2 when (c2 > 100): return 2; }" },
                { "case Circle c when (c < 10): return 1;", "case Circle c when (c < 5): return 1;" },
                { "case Circle c when (c < 10):", "case Circle c when (c < 5):" },
                { "c", "c" },
                { "when (c < 10)", "when (c < 5)" },
                { "return 1;", "return 1;" },
                { "case Circle c when (c > 100): return 2;", "case Circle c2 when (c2 > 100): return 2;" },
                { "case Circle c when (c > 100):", "case Circle c2 when (c2 > 100):" },
                { "c", "c2" },
                { "when (c > 100)", "when (c2 > 100)" },
                { "return 2;", "return 2;" }
            };

            expected.AssertEqual(actual);
        }

        #endregion

        #region Switch Expression

        [Fact]
        public void SwitchExpressionArms_Lambda()
        {
            var src1 = @"F1() switch { 1 => new Func<int>(() => 1)(), _ => 2 };";
            var src2 = @"F1() switch { 1 => new Func<int>(() => 3)(), _ => 2 };";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Regular);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs {
                { "F1() switch { 1 => new Func<int>(() => 1)(), _ => 2 };", "F1() switch { 1 => new Func<int>(() => 3)(), _ => 2 };" },
                { "F1() switch { 1 => new Func<int>(() => 1)(), _ => 2 }", "F1() switch { 1 => new Func<int>(() => 3)(), _ => 2 }" },
                { "1 => new Func<int>(() => 1)()", "1 => new Func<int>(() => 3)()" },
                { "() => 1", "() => 3" },
                { "()", "()" },
                { "_ => 2", "_ => 2" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void SwitchExpressionArms_NestedSimilar()
        {
            // The inner switch is mapped to the outer one, which is assumed to be removed.
            var src1 = @"F1() switch { 1 => 0, _ => F2() switch { 1 => 0, _ => 2 } };";
            var src2 = @"F1() switch { 1 => 0, _ => 1 };";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Regular);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs {
                { "F1() switch { 1 => 0, _ => F2() switch { 1 => 0, _ => 2 } };", "F1() switch { 1 => 0, _ => 1 };" },
                { "F2() switch { 1 => 0, _ => 2 }", "F1() switch { 1 => 0, _ => 1 }" },
                { "1 => 0", "1 => 0" },
                { "_ => 2", "_ => 1" }
            };

            expected.AssertEqual(actual);
        }

        [Fact]
        public void SwitchExpressionArms_NestedDissimilar()
        {
            // The inner switch is mapped to the outer one, which is assumed to be removed.
            var src1 = @"Method() switch { true => G(), _ => F2() switch { 1 => 0, _ => 2 } };";
            var src2 = @"Method() switch { true => G(), _ => 1 };";

            var match = GetMethodMatches(src1, src2, kind: MethodKind.Regular);
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs {
                { "Method() switch { true => G(), _ => F2() switch { 1 => 0, _ => 2 } };", "Method() switch { true => G(), _ => 1 };" },
                { "Method() switch { true => G(), _ => F2() switch { 1 => 0, _ => 2 } }", "Method() switch { true => G(), _ => 1 }" },
                { "true => G()", "true => G()" },
                { "_ => F2() switch { 1 => 0, _ => 2 }", "_ => 1" }
            };

            expected.AssertEqual(actual);
        }

        #endregion

        #region Top Level Statements

        [Fact]
        public void TopLevelStatements()
        {
            var src1 = @"
Console.WriteLine(1);
Console.WriteLine(2);

var x = 0;
while (true)
{
    x++;
}

Console.WriteLine(3);
";
            var src2 = @"
Console.WriteLine(4);
Console.WriteLine(5);

var x = 1;
while (true)
{
    x--;
}

Console.WriteLine(6);
";
            var match = GetTopEdits(src1, src2).Match;
            var actual = ToMatchingPairs(match);

            var expected = new MatchingPairs
            {
                { "Console.WriteLine(1);", "Console.WriteLine(4);" },
                { "Console.WriteLine(2);", "Console.WriteLine(5);" },
                { "var x = 0;", "var x = 1;" },
                { "while (true) {     x++; }", "while (true) {     x--; }" },
                { "Console.WriteLine(3);", "Console.WriteLine(6);" }
            };

            expected.AssertEqual(actual);
        }

        #endregion
    }
}
