File: EditAndContinue\SyntaxComparer.cs
Web Access
Project: src\src\Features\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Features.csproj (Microsoft.CodeAnalysis.CSharp.Features)
// 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.
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Differencing;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue;
 
/// <summary>
/// Creates a syntax comparer
/// </summary>
/// <param name="oldRoot">The root node to start comparisons from</param>
/// <param name="newRoot">The new root node to compare against</param>
/// <param name="oldRootChildren">Child nodes that should always be compared</param>
/// <param name="newRootChildren">New child nodes to compare against</param>
/// <param name="compareStatementSyntax">Whether this comparer is in "statement mode"</param>
internal sealed class SyntaxComparer(
    SyntaxNode? oldRoot,
    SyntaxNode? newRoot,
    IEnumerable<SyntaxNode>? oldRootChildren,
    IEnumerable<SyntaxNode>? newRootChildren,
    bool compareStatementSyntax) : AbstractSyntaxComparer(oldRoot, newRoot, oldRootChildren, newRootChildren, compareStatementSyntax)
{
    internal static readonly SyntaxComparer TopLevel = new(null, null, null, null, compareStatementSyntax: false);
    internal static readonly SyntaxComparer Statement = new(null, null, null, null, compareStatementSyntax: true);
 
    protected override bool IsLambdaBodyStatementOrExpression(SyntaxNode node)
        => LambdaUtilities.IsLambdaBodyStatementOrExpression(node);
 
    #region Labels
 
    /// <summary>
    /// Assumptions:
    /// - Each listed label corresponds to one or more syntax kinds.
    /// - Nodes with same labels might produce Update edits, nodes with different labels don't. 
    /// - If <see cref="TiedToAncestor(Label)"/> is true for a label then all its possible parent labels must precede the label.
    ///   (i.e. both MethodDeclaration and TypeDeclaration must precede TypeParameter label).
    /// - All descendants of a node whose kind is listed here will be ignored regardless of their labels
    /// </summary>
    internal enum Label
    {
        // Top level syntax kinds
        CompilationUnit,
 
        GlobalStatement,
 
        NamespaceDeclaration,
        ExternAliasDirective,              // tied to parent 
        UsingDirective,                    // tied to parent
 
        TypeDeclaration,
        EnumDeclaration,
        BaseList,                          // tied to parent
        PrimaryConstructorBase,            // tied to parent
        DelegateDeclaration,
 
        FieldDeclaration,                  // tied to parent
        FieldVariableDeclaration,          // tied to parent
        FieldVariableDeclarator,           // tied to parent
 
        MethodDeclaration,                 // tied to parent
        OperatorDeclaration,               // tied to parent
        ConversionOperatorDeclaration,     // tied to parent
        ConstructorDeclaration,            // tied to parent
        DestructorDeclaration,             // tied to parent
        PropertyDeclaration,               // tied to parent
        IndexerDeclaration,                // tied to parent
        EventDeclaration,                  // tied to parent
        EnumMemberDeclaration,             // tied to parent
        ArrowExpressionClause,             // tied to parent
 
        AccessorList,                      // tied to parent
        AccessorDeclaration,               // tied to parent
 
        // Statement syntax kinds
        Block,
        CheckedStatement,
        UnsafeStatement,
 
        TryStatement,
        CatchClause,                      // tied to parent
        CatchDeclaration,                 // tied to parent
        CatchFilterClause,                // tied to parent
        FinallyClause,                    // tied to parent
        ForStatement,
        ForStatementPart,                 // tied to parent
        ForEachStatement,
        UsingStatementWithExpression,
        UsingStatementWithDeclarations,
        FixedStatement,
        LockStatement,
        WhileStatement,
        DoStatement,
        IfStatement,
        ElseClause,                        // tied to parent 
 
        SwitchStatement,
        SwitchSection,
        CasePatternSwitchLabel,            // tied to parent
        SwitchExpression,
        SwitchExpressionArm,               // tied to parent
        WhenClause,                        // tied to parent
 
        YieldReturnStatement,              // tied to parent
        YieldBreakStatement,               // tied to parent
        GotoStatement,
        GotoCaseStatement,
        BreakContinueStatement,
        ReturnThrowStatement,
        ExpressionStatement,
 
        LabeledStatement,
 
        // TODO: 
        // Ideally we could declare LocalVariableDeclarator tied to the first enclosing node that defines local scope (block, foreach, etc.)
        // Also consider handling LocalDeclarationStatement as just a bag of variable declarators,
        // so that variable declarators contained in one can be matched with variable declarators contained in the other.
        LocalDeclarationStatement,         // tied to parent
        LocalVariableDeclaration,          // tied to parent
        LocalVariableDeclarator,           // tied to parent
 
        SingleVariableDesignation,
        AwaitExpression,
        NestedFunction,
 
        FromClause,
        QueryBody,
        FromClauseLambda,                 // tied to parent
        LetClauseLambda,                  // tied to parent
        WhereClauseLambda,                // tied to parent
        OrderByClause,                    // tied to parent
        OrderingLambda,                   // tied to parent
        SelectClauseLambda,               // tied to parent
        JoinClauseLambda,                 // tied to parent
        JoinIntoClause,                   // tied to parent
        GroupClauseLambda,                // tied to parent
        QueryContinuation,                // tied to parent
 
        // Syntax kinds that are common to both statement and top level
        TypeParameterList,                 // tied to parent
        TypeParameterConstraintClause,     // tied to parent
        TypeParameter,                     // tied to parent
        ParameterList,                     // tied to parent
        BracketedParameterList,            // tied to parent
        Parameter,                         // tied to parent
        AttributeList,                     // tied to parent
        Attribute,                         // tied to parent
 
        // helpers:
        Count,
        Ignored = IgnoredNode
    }
 
    /// <summary>
    /// Return 1 if it is desirable to report two edits (delete and insert) rather than a move edit
    /// when the node changes its parent.
    /// </summary>
    private static int TiedToAncestor(Label label)
    {
        switch (label)
        {
            // Top level syntax
            case Label.ExternAliasDirective:
            case Label.UsingDirective:
            case Label.FieldDeclaration:
            case Label.FieldVariableDeclaration:
            case Label.FieldVariableDeclarator:
            case Label.MethodDeclaration:
            case Label.OperatorDeclaration:
            case Label.ConversionOperatorDeclaration:
            case Label.ConstructorDeclaration:
            case Label.DestructorDeclaration:
            case Label.PropertyDeclaration:
            case Label.ArrowExpressionClause:
            case Label.IndexerDeclaration:
            case Label.EventDeclaration:
            case Label.EnumMemberDeclaration:
            case Label.BaseList:
            case Label.AccessorDeclaration:
            case Label.AccessorList:
            case Label.TypeParameterList:
            case Label.TypeParameter:
            case Label.TypeParameterConstraintClause:
            case Label.ParameterList:
            case Label.BracketedParameterList:
            case Label.Parameter:
            case Label.AttributeList:
            case Label.Attribute:
                return 1;
 
            // Statement syntax
            case Label.LocalDeclarationStatement:
            case Label.LocalVariableDeclaration:
            case Label.LocalVariableDeclarator:
            case Label.GotoCaseStatement:
            case Label.BreakContinueStatement:
            case Label.ElseClause:
            case Label.CatchClause:
            case Label.CatchDeclaration:
            case Label.CatchFilterClause:
            case Label.FinallyClause:
            case Label.ForStatementPart:
            case Label.YieldReturnStatement:
            case Label.YieldBreakStatement:
            case Label.FromClauseLambda:
            case Label.LetClauseLambda:
            case Label.WhereClauseLambda:
            case Label.OrderByClause:
            case Label.OrderingLambda:
            case Label.SelectClauseLambda:
            case Label.JoinClauseLambda:
            case Label.JoinIntoClause:
            case Label.GroupClauseLambda:
            case Label.QueryContinuation:
            case Label.CasePatternSwitchLabel:
            case Label.WhenClause:
            case Label.SwitchExpressionArm:
                return 1;
 
            default:
                return 0;
        }
    }
 
    internal override int Classify(int kind, SyntaxNode? node, out bool isLeaf)
        => (int)Classify((SyntaxKind)kind, node, out isLeaf);
 
    internal Label Classify(SyntaxKind kind, SyntaxNode? node, out bool isLeaf)
    {
        isLeaf = false;
 
        // If the node is a for loop Initializer, Condition, or Incrementor expression we label it as "ForStatementPart".
        // We need to capture it in the match since these expressions can be "active statements" and as such we need to map them.
        //
        // The parent is not available only when comparing nodes for value equality.
        if (node != null && node.Parent.IsKind(SyntaxKind.ForStatement) && node is ExpressionSyntax)
        {
            return Label.ForStatementPart;
        }
 
        // ************************************
        // Top and statement syntax
        // ************************************
 
        // These nodes can appear during top level and statement processing, so we put them in this first
        // switch for simplicity. Statement specific, and top level specific cases are handled below.
        switch (kind)
        {
            case SyntaxKind.CompilationUnit:
                return Label.CompilationUnit;
 
            case SyntaxKind.TypeParameterList:
                return Label.TypeParameterList;
 
            case SyntaxKind.TypeParameterConstraintClause:
                return Label.TypeParameterConstraintClause;
 
            case SyntaxKind.TypeParameter:
                // not a leaf because an attribute may be applied
                return Label.TypeParameter;
 
            case SyntaxKind.BracketedParameterList:
                return Label.BracketedParameterList;
 
            case SyntaxKind.ParameterList:
                return Label.ParameterList;
 
            case SyntaxKind.Parameter:
                return Label.Parameter;
 
            case SyntaxKind.ConstructorDeclaration:
                // Root when matching constructor bodies.
                return Label.ConstructorDeclaration;
        }
 
        if (_compareStatementSyntax)
        {
            return ClassifyStatementSyntax(kind, node, out isLeaf);
        }
 
        return ClassifyTopSyntax(kind, node, out isLeaf);
    }
 
    private static Label ClassifyStatementSyntax(SyntaxKind kind, SyntaxNode? node, out bool isLeaf)
    {
        isLeaf = false;
 
        // ************************************
        // Statement syntax
        // ************************************
 
        // These nodes could potentially be seen as top level syntax, but we only want them labelled
        // during statement syntax so they have to be kept separate.
        //
        // For example when top level sees something like this:
        //
        //      private int X => new Func(() => { return 1 })();
        //
        // It needs to go through the entire lambda to know if that property def has changed
        // but if we start labelling things, like ReturnStatement in the above example, then
        // it will stop. Given that a block bodied lambda can have any statements a user likes
        // the whole set has to be dealt with separately.
        switch (kind)
        {
            // Notes:
            // A descendant of a leaf node may be a labeled node that we don't want to visit if 
            // we are comparing its parent node (used for lambda bodies).
            // 
            // Expressions are ignored but they may contain nodes that should be matched by tree comparer.
            // (e.g. lambdas, declaration expressions). Descending to these nodes is handled in EnumerateChildren.
 
            case SyntaxKind.NamespaceDeclaration:
            case SyntaxKind.ClassDeclaration:
            case SyntaxKind.InterfaceDeclaration:
            case SyntaxKind.StructDeclaration:
            case SyntaxKind.RecordDeclaration:
            case SyntaxKind.RecordStructDeclaration:
                // These declarations can come after global statements so we want to stop statement matching
                // because no global statements can come after them
                isLeaf = true;
                return Label.Ignored;
 
            case SyntaxKind.LocalDeclarationStatement:
                return Label.LocalDeclarationStatement;
 
            case SyntaxKind.SingleVariableDesignation:
                return Label.SingleVariableDesignation;
 
            case SyntaxKind.LabeledStatement:
                return Label.LabeledStatement;
 
            case SyntaxKind.EmptyStatement:
                isLeaf = true;
                return Label.ExpressionStatement;
 
            case SyntaxKind.GotoStatement:
                isLeaf = true;
                return Label.GotoStatement;
 
            case SyntaxKind.GotoCaseStatement:
            case SyntaxKind.GotoDefaultStatement:
                isLeaf = true;
                return Label.GotoCaseStatement;
 
            case SyntaxKind.BreakStatement:
            case SyntaxKind.ContinueStatement:
                isLeaf = true;
                return Label.BreakContinueStatement;
 
            case SyntaxKind.ReturnStatement:
            case SyntaxKind.ThrowStatement:
                return Label.ReturnThrowStatement;
 
            case SyntaxKind.ExpressionStatement:
                return Label.ExpressionStatement;
 
            case SyntaxKind.YieldBreakStatement:
                // yield break is distinct from yield return as it does not suspend the state machine in a resumable state
                return Label.YieldBreakStatement;
 
            case SyntaxKind.YieldReturnStatement:
                return Label.YieldReturnStatement;
 
            case SyntaxKind.DoStatement:
                return Label.DoStatement;
 
            case SyntaxKind.WhileStatement:
                return Label.WhileStatement;
 
            case SyntaxKind.ForStatement:
                return Label.ForStatement;
 
            case SyntaxKind.ForEachVariableStatement:
            case SyntaxKind.ForEachStatement:
                return Label.ForEachStatement;
 
            case SyntaxKind.UsingStatement:
                // We need to distinguish using statements with expression or single variable declaration from ones with multiple variable declarations. 
                // The former generate a single try-finally block, the latter one for each variable. The finally blocks need to match since they
                // affect state machine state matching. For simplicity we do not match single-declaration to expression, we just treat usings
                // with declarations entirely separately from usings with expressions.
                //
                // The parent is not available only when comparing nodes for value equality.
                // In that case it doesn't matter what label the node has as long as it has some.
                return node is UsingStatementSyntax { Declaration: not null } ? Label.UsingStatementWithDeclarations : Label.UsingStatementWithExpression;
 
            case SyntaxKind.FixedStatement:
                return Label.FixedStatement;
 
            case SyntaxKind.CheckedStatement:
            case SyntaxKind.UncheckedStatement:
                return Label.CheckedStatement;
 
            case SyntaxKind.UnsafeStatement:
                return Label.UnsafeStatement;
 
            case SyntaxKind.LockStatement:
                return Label.LockStatement;
 
            case SyntaxKind.IfStatement:
                return Label.IfStatement;
 
            case SyntaxKind.ElseClause:
                return Label.ElseClause;
 
            case SyntaxKind.SwitchStatement:
                return Label.SwitchStatement;
 
            case SyntaxKind.SwitchSection:
                return Label.SwitchSection;
 
            case SyntaxKind.CaseSwitchLabel:
            case SyntaxKind.DefaultSwitchLabel:
                // Switch labels are included in the "value" of the containing switch section.
                // We don't need to analyze case expressions.
                isLeaf = true;
                return Label.Ignored;
 
            case SyntaxKind.WhenClause:
                return Label.WhenClause;
 
            case SyntaxKind.CasePatternSwitchLabel:
                return Label.CasePatternSwitchLabel;
 
            case SyntaxKind.SwitchExpression:
                return Label.SwitchExpression;
 
            case SyntaxKind.SwitchExpressionArm:
                return Label.SwitchExpressionArm;
 
            case SyntaxKind.TryStatement:
                return Label.TryStatement;
 
            case SyntaxKind.CatchClause:
                return Label.CatchClause;
 
            case SyntaxKind.CatchDeclaration:
                // the declarator of the exception variable
                return Label.CatchDeclaration;
 
            case SyntaxKind.CatchFilterClause:
                return Label.CatchFilterClause;
 
            case SyntaxKind.FinallyClause:
                return Label.FinallyClause;
 
            case SyntaxKind.FromClause:
                // The first from clause of a query is not a lambda.
                // We have to assign it a label different from "FromClauseLambda"
                // so that we won't match lambda-from to non-lambda-from.
                // 
                // Since FromClause declares range variables we need to include it in the map,
                // so that we are able to map range variable declarations.
                // Therefore we assign it a dedicated label.
                // 
                // The parent is not available only when comparing nodes for value equality.
                // In that case it doesn't matter what label the node has as long as it has some.
                if (node == null || node.Parent.IsKind(SyntaxKind.QueryExpression))
                {
                    return Label.FromClause;
                }
 
                return Label.FromClauseLambda;
 
            case SyntaxKind.QueryBody:
                return Label.QueryBody;
 
            case SyntaxKind.QueryContinuation:
                return Label.QueryContinuation;
 
            case SyntaxKind.LetClause:
                return Label.LetClauseLambda;
 
            case SyntaxKind.WhereClause:
                return Label.WhereClauseLambda;
 
            case SyntaxKind.OrderByClause:
                return Label.OrderByClause;
 
            case SyntaxKind.AscendingOrdering:
            case SyntaxKind.DescendingOrdering:
                return Label.OrderingLambda;
 
            case SyntaxKind.SelectClause:
                return Label.SelectClauseLambda;
 
            case SyntaxKind.JoinClause:
                return Label.JoinClauseLambda;
 
            case SyntaxKind.JoinIntoClause:
                return Label.JoinIntoClause;
 
            case SyntaxKind.GroupClause:
                return Label.GroupClauseLambda;
 
            case SyntaxKind.IdentifierName:
            case SyntaxKind.QualifiedName:
            case SyntaxKind.GenericName:
            case SyntaxKind.TypeArgumentList:
            case SyntaxKind.AliasQualifiedName:
            case SyntaxKind.PredefinedType:
            case SyntaxKind.PointerType:
            case SyntaxKind.NullableType:
            case SyntaxKind.TupleType:
            case SyntaxKind.RefType:
            case SyntaxKind.OmittedTypeArgument:
            case SyntaxKind.NameColon:
            case SyntaxKind.OmittedArraySizeExpression:
            case SyntaxKind.ThisExpression:
            case SyntaxKind.BaseExpression:
            case SyntaxKind.ArgListExpression:
            case SyntaxKind.NumericLiteralExpression:
            case SyntaxKind.StringLiteralExpression:
            case SyntaxKind.CharacterLiteralExpression:
            case SyntaxKind.TrueLiteralExpression:
            case SyntaxKind.FalseLiteralExpression:
            case SyntaxKind.NullLiteralExpression:
            case SyntaxKind.TypeOfExpression:
            case SyntaxKind.SizeOfExpression:
            case SyntaxKind.DefaultExpression:
            case SyntaxKind.ConstantPattern:
            case SyntaxKind.DiscardDesignation:
                // can't contain a lambda/await/anonymous type:
                isLeaf = true;
                return Label.Ignored;
 
            case SyntaxKind.AwaitExpression:
                return Label.AwaitExpression;
 
            case SyntaxKind.ParenthesizedLambdaExpression:
            case SyntaxKind.SimpleLambdaExpression:
            case SyntaxKind.AnonymousMethodExpression:
            case SyntaxKind.LocalFunctionStatement:
                return Label.NestedFunction;
 
            case SyntaxKind.VariableDeclaration:
                return Label.LocalVariableDeclaration;
 
            case SyntaxKind.VariableDeclarator:
                return Label.LocalVariableDeclarator;
 
            case SyntaxKind.Block:
                return Label.Block;
        }
 
        // If we got this far, its an unlabelled node. Since just about any node can
        // contain a lambda, isLeaf must be false for statement syntax.
        return Label.Ignored;
    }
 
    private static Label ClassifyTopSyntax(SyntaxKind kind, SyntaxNode? node, out bool isLeaf)
    {
        isLeaf = false;
 
        // ************************************
        // Top syntax
        // ************************************
 
        // More the most part these nodes will only appear in top syntax but its easier to
        // keep them separate so we can more easily discern was is shared, above.
        switch (kind)
        {
            case SyntaxKind.GlobalStatement:
                isLeaf = true;
                return Label.GlobalStatement;
 
            case SyntaxKind.ExternAliasDirective:
                isLeaf = true;
                return Label.ExternAliasDirective;
 
            case SyntaxKind.UsingDirective:
                isLeaf = true;
                return Label.UsingDirective;
 
            case SyntaxKind.NamespaceDeclaration:
            case SyntaxKind.FileScopedNamespaceDeclaration:
                return Label.NamespaceDeclaration;
 
            case SyntaxKind.ClassDeclaration:
            case SyntaxKind.StructDeclaration:
            case SyntaxKind.InterfaceDeclaration:
            case SyntaxKind.RecordDeclaration:
            case SyntaxKind.RecordStructDeclaration:
                return Label.TypeDeclaration;
 
            case SyntaxKind.BaseList:
                return Label.BaseList;
 
            case SyntaxKind.PrimaryConstructorBaseType:
                // For top syntax, primary constructor base initializer is a leaf node
                isLeaf = true;
                return Label.PrimaryConstructorBase;
 
            case SyntaxKind.MethodDeclaration:
                return Label.MethodDeclaration;
 
            case SyntaxKind.EnumDeclaration:
                return Label.EnumDeclaration;
 
            case SyntaxKind.DelegateDeclaration:
                return Label.DelegateDeclaration;
 
            case SyntaxKind.FieldDeclaration:
            case SyntaxKind.EventFieldDeclaration:
                return Label.FieldDeclaration;
 
            case SyntaxKind.ConversionOperatorDeclaration:
                return Label.ConversionOperatorDeclaration;
 
            case SyntaxKind.OperatorDeclaration:
                return Label.OperatorDeclaration;
 
            case SyntaxKind.DestructorDeclaration:
                isLeaf = true;
                return Label.DestructorDeclaration;
 
            case SyntaxKind.PropertyDeclaration:
                return Label.PropertyDeclaration;
 
            case SyntaxKind.IndexerDeclaration:
                return Label.IndexerDeclaration;
 
            case SyntaxKind.ArrowExpressionClause:
                if (node?.Parent is (kind: SyntaxKind.PropertyDeclaration or SyntaxKind.IndexerDeclaration))
                    return Label.ArrowExpressionClause;
 
                break;
 
            case SyntaxKind.EventDeclaration:
                return Label.EventDeclaration;
 
            case SyntaxKind.EnumMemberDeclaration:
                // not a leaf because an attribute may be applied
                return Label.EnumMemberDeclaration;
 
            case SyntaxKind.AccessorList:
                return Label.AccessorList;
 
            case SyntaxKind.GetAccessorDeclaration:
            case SyntaxKind.SetAccessorDeclaration:
            case SyntaxKind.InitAccessorDeclaration:
            case SyntaxKind.AddAccessorDeclaration:
            case SyntaxKind.RemoveAccessorDeclaration:
                isLeaf = true;
                return Label.AccessorDeclaration;
 
            // Note: These last two do actually appear as statement syntax, but mean something
            // different and hence have a different label
            case SyntaxKind.VariableDeclaration:
                return Label.FieldVariableDeclaration;
 
            case SyntaxKind.VariableDeclarator:
                // For top syntax, a variable declarator is a leaf node
                isLeaf = true;
                return Label.FieldVariableDeclarator;
 
            case SyntaxKind.AttributeList:
                // Only module/assembly attributes are labelled
                if (node is not null && node.IsParentKind(SyntaxKind.CompilationUnit))
                {
                    return Label.AttributeList;
                }
 
                break;
 
            case SyntaxKind.Attribute:
                // Only module/assembly attributes are labelled
                if (node is { Parent: { } parent } && parent.IsParentKind(SyntaxKind.CompilationUnit))
                {
                    isLeaf = true;
                    return Label.Attribute;
                }
 
                break;
        }
 
        // If we got this far, its an unlabelled node. For top
        // syntax, we don't need to descend into any ignored nodes
        isLeaf = true;
        return Label.Ignored;
    }
 
    // internal for testing
    internal bool HasLabel(SyntaxKind kind)
        => Classify(kind, node: null, out _) != Label.Ignored;
 
    protected internal override int LabelCount
        => (int)Label.Count;
 
    protected internal override int TiedToAncestor(int label)
        => TiedToAncestor((Label)label);
 
    #endregion
 
    #region Comparisons
 
    public override bool ValuesEqual(SyntaxNode left, SyntaxNode right)
    {
        Func<SyntaxKind, bool>? ignoreChildFunction;
        switch (left.Kind())
        {
            // all syntax kinds with a method body child:
            case SyntaxKind.MethodDeclaration:
            case SyntaxKind.ConversionOperatorDeclaration:
            case SyntaxKind.OperatorDeclaration:
            case SyntaxKind.ConstructorDeclaration:
            case SyntaxKind.DestructorDeclaration:
            case SyntaxKind.GetAccessorDeclaration:
            case SyntaxKind.SetAccessorDeclaration:
            case SyntaxKind.InitAccessorDeclaration:
            case SyntaxKind.AddAccessorDeclaration:
            case SyntaxKind.RemoveAccessorDeclaration:
                // When comparing method bodies we need to NOT ignore VariableDeclaration and VariableDeclarator children,
                // but when comparing field definitions we should ignore VariableDeclarations children.
 
                var leftBody = GetBody(left);
                var rightBody = GetBody(right);
 
                if (!SyntaxFactory.AreEquivalent(leftBody, rightBody, null))
                {
                    return false;
                }
 
                ignoreChildFunction = childKind => childKind == SyntaxKind.Block || childKind == SyntaxKind.ArrowExpressionClause || HasLabel(childKind);
                break;
 
            case SyntaxKind.SwitchSection:
                return Equal((SwitchSectionSyntax)left, (SwitchSectionSyntax)right);
 
            case SyntaxKind.ForStatement:
                // The only children of ForStatement are labeled nodes and punctuation.
                return true;
 
            default:
                if (HasChildren(left))
                {
                    ignoreChildFunction = childKind => HasLabel(childKind);
                }
                else
                {
                    ignoreChildFunction = null;
                }
 
                break;
        }
 
        return SyntaxFactory.AreEquivalent(left, right, ignoreChildFunction);
    }
 
    private bool Equal(SwitchSectionSyntax left, SwitchSectionSyntax right)
    {
        return SyntaxFactory.AreEquivalent(left.Labels, right.Labels, null)
            && SyntaxFactory.AreEquivalent(left.Statements, right.Statements, ignoreChildNode: HasLabel);
    }
 
    private static SyntaxNode? GetBody(SyntaxNode node)
    {
        switch (node)
        {
            case BaseMethodDeclarationSyntax baseMethodDeclarationSyntax: return baseMethodDeclarationSyntax.Body ?? (SyntaxNode?)baseMethodDeclarationSyntax.ExpressionBody?.Expression;
            case AccessorDeclarationSyntax accessorDeclarationSyntax: return accessorDeclarationSyntax.Body ?? (SyntaxNode?)accessorDeclarationSyntax.ExpressionBody?.Expression;
            default: throw ExceptionUtilities.UnexpectedValue(node);
        }
    }
 
    protected override bool TryComputeWeightedDistance(SyntaxNode leftNode, SyntaxNode rightNode, out double distance)
    {
        switch (leftNode.Kind())
        {
            case SyntaxKind.VariableDeclarator:
                distance = ComputeDistance(
                    ((VariableDeclaratorSyntax)leftNode).Identifier,
                    ((VariableDeclaratorSyntax)rightNode).Identifier);
                return true;
 
            case SyntaxKind.ForStatement:
                var leftFor = (ForStatementSyntax)leftNode;
                var rightFor = (ForStatementSyntax)rightNode;
                distance = ComputeWeightedDistance(leftFor, rightFor);
                return true;
 
            case SyntaxKind.ForEachStatement:
            case SyntaxKind.ForEachVariableStatement:
                {
 
                    var leftForEach = (CommonForEachStatementSyntax)leftNode;
                    var rightForEach = (CommonForEachStatementSyntax)rightNode;
                    distance = ComputeWeightedDistance(leftForEach, rightForEach);
                    return true;
                }
 
            case SyntaxKind.UsingStatement:
                {
                    var leftUsing = (UsingStatementSyntax)leftNode;
                    var rightUsing = (UsingStatementSyntax)rightNode;
 
                    if (leftUsing.Declaration != null && rightUsing.Declaration != null)
                    {
                        distance = ComputeWeightedDistance(
                            leftUsing.Declaration,
                            leftUsing.Statement,
                            rightUsing.Declaration,
                            rightUsing.Statement);
                    }
                    else
                    {
                        distance = ComputeWeightedDistance(
                            (SyntaxNode?)leftUsing.Expression ?? leftUsing.Declaration!,
                            leftUsing.Statement,
                            (SyntaxNode?)rightUsing.Expression ?? rightUsing.Declaration!,
                            rightUsing.Statement);
                    }
 
                    return true;
                }
 
            case SyntaxKind.UsingDirective:
                {
                    var leftUsing = (UsingDirectiveSyntax)leftNode;
                    var rightUsing = (UsingDirectiveSyntax)rightNode;
 
                    // For now, just compute the distances of both the alias and name and combine their weights
                    // 50/50. We could consider weighting the alias more heavily.  i.e. if you have `using X = ...`
                    // and `using X = ...` it's more likely that this is the same alias, and just the name portion
                    // changed versus thinking that some other using became this alias.
                    distance =
                        ComputeDistance(leftUsing.Alias, rightUsing.Alias) +
                        ComputeDistance(leftUsing.NamespaceOrType, rightUsing.NamespaceOrType);
 
                    // Consider two usings that only differ by presence/absence of 'global' to be a near match.
                    if (leftUsing.GlobalKeyword.IsKind(SyntaxKind.None) != rightUsing.GlobalKeyword.IsKind(SyntaxKind.None))
                        distance += EpsilonDist;
 
                    // Consider two usings that only differ by presence/absence of 'unsafe' to be a near match.
                    if (leftUsing.UnsafeKeyword.IsKind(SyntaxKind.None) != rightUsing.UnsafeKeyword.IsKind(SyntaxKind.None))
                        distance += EpsilonDist;
 
                    return true;
                }
 
            case SyntaxKind.LockStatement:
                var leftLock = (LockStatementSyntax)leftNode;
                var rightLock = (LockStatementSyntax)rightNode;
                distance = ComputeWeightedDistance(leftLock.Expression, leftLock.Statement, rightLock.Expression, rightLock.Statement);
                return true;
 
            case SyntaxKind.FixedStatement:
                var leftFixed = (FixedStatementSyntax)leftNode;
                var rightFixed = (FixedStatementSyntax)rightNode;
                distance = ComputeWeightedDistance(leftFixed.Declaration, leftFixed.Statement, rightFixed.Declaration, rightFixed.Statement);
                return true;
 
            case SyntaxKind.WhileStatement:
                var leftWhile = (WhileStatementSyntax)leftNode;
                var rightWhile = (WhileStatementSyntax)rightNode;
                distance = ComputeWeightedDistance(leftWhile.Condition, leftWhile.Statement, rightWhile.Condition, rightWhile.Statement);
                return true;
 
            case SyntaxKind.DoStatement:
                var leftDo = (DoStatementSyntax)leftNode;
                var rightDo = (DoStatementSyntax)rightNode;
                distance = ComputeWeightedDistance(leftDo.Condition, leftDo.Statement, rightDo.Condition, rightDo.Statement);
                return true;
 
            case SyntaxKind.IfStatement:
                var leftIf = (IfStatementSyntax)leftNode;
                var rightIf = (IfStatementSyntax)rightNode;
                distance = ComputeWeightedDistance(leftIf.Condition, leftIf.Statement, rightIf.Condition, rightIf.Statement);
                return true;
 
            case SyntaxKind.Block:
                var leftBlock = (BlockSyntax)leftNode;
                var rightBlock = (BlockSyntax)rightNode;
                return TryComputeWeightedDistance(leftBlock, rightBlock, out distance);
 
            case SyntaxKind.CatchClause:
                distance = ComputeWeightedDistance((CatchClauseSyntax)leftNode, (CatchClauseSyntax)rightNode);
                return true;
 
            case SyntaxKind.ParenthesizedLambdaExpression:
            case SyntaxKind.SimpleLambdaExpression:
            case SyntaxKind.AnonymousMethodExpression:
            case SyntaxKind.LocalFunctionStatement:
                distance = ComputeWeightedDistanceOfNestedFunctions(leftNode, rightNode);
                return true;
 
            case SyntaxKind.SingleVariableDesignation:
                distance = ComputeWeightedDistance((SingleVariableDesignationSyntax)leftNode, (SingleVariableDesignationSyntax)rightNode);
                return true;
 
            case SyntaxKind.TypeParameterConstraintClause:
                distance = ComputeDistance((TypeParameterConstraintClauseSyntax)leftNode, (TypeParameterConstraintClauseSyntax)rightNode);
                return true;
 
            case SyntaxKind.TypeParameter:
                distance = ComputeDistance((TypeParameterSyntax)leftNode, (TypeParameterSyntax)rightNode);
                return true;
 
            case SyntaxKind.Parameter:
                distance = ComputeDistance((ParameterSyntax)leftNode, (ParameterSyntax)rightNode);
                return true;
 
            case SyntaxKind.AttributeList:
                distance = ComputeDistance((AttributeListSyntax)leftNode, (AttributeListSyntax)rightNode);
                return true;
 
            case SyntaxKind.Attribute:
                distance = ComputeDistance((AttributeSyntax)leftNode, (AttributeSyntax)rightNode);
                return true;
 
            default:
                var leftName = TryGetName(leftNode);
                var rightName = TryGetName(rightNode);
                Contract.ThrowIfFalse(rightName.HasValue == leftName.HasValue);
 
                if (leftName.HasValue)
                {
                    distance = ComputeDistance(leftName.Value, rightName!.Value);
                    return true;
                }
                else
                {
                    distance = 0;
                    return false;
                }
        }
    }
 
    private static double ComputeWeightedDistanceOfNestedFunctions(SyntaxNode leftNode, SyntaxNode rightNode)
    {
        GetNestedFunctionsParts(leftNode, out var leftParameters, out var leftAsync, out var leftBody, out var leftModifiers, out var leftReturnType, out var leftIdentifier, out var leftTypeParameters);
        GetNestedFunctionsParts(rightNode, out var rightParameters, out var rightAsync, out var rightBody, out var rightModifiers, out var rightReturnType, out var rightIdentifier, out var rightTypeParameters);
 
        if ((leftAsync.Kind() == SyntaxKind.AsyncKeyword) != (rightAsync.Kind() == SyntaxKind.AsyncKeyword))
        {
            return 1.0;
        }
 
        var modifierDistance = ComputeDistance(leftModifiers, rightModifiers);
        var returnTypeDistance = ComputeDistance(leftReturnType, rightReturnType);
        var identifierDistance = ComputeDistance(leftIdentifier, rightIdentifier);
        var typeParameterDistance = ComputeDistance(leftTypeParameters, rightTypeParameters);
        var parameterDistance = ComputeDistance(leftParameters, rightParameters);
        var bodyDistance = ComputeDistance(leftBody, rightBody);
 
        return
            modifierDistance * 0.1 +
            returnTypeDistance * 0.1 +
            identifierDistance * 0.2 +
            typeParameterDistance * 0.2 +
            parameterDistance * 0.2 +
            bodyDistance * 0.2;
    }
 
    private static void GetNestedFunctionsParts(
        SyntaxNode nestedFunction,
        out IEnumerable<SyntaxToken> parameters,
        out SyntaxToken asyncKeyword,
        out SyntaxNode body,
        out SyntaxTokenList modifiers,
        out TypeSyntax? returnType,
        out SyntaxToken identifier,
        out TypeParameterListSyntax? typeParameters)
    {
        switch (nestedFunction.Kind())
        {
            case SyntaxKind.SimpleLambdaExpression:
                var simple = (SimpleLambdaExpressionSyntax)nestedFunction;
                parameters = simple.Parameter.DescendantTokens();
                asyncKeyword = simple.AsyncKeyword;
                body = simple.Body;
                modifiers = default;
                returnType = null;
                identifier = default;
                typeParameters = null;
                break;
 
            case SyntaxKind.ParenthesizedLambdaExpression:
                var parenthesized = (ParenthesizedLambdaExpressionSyntax)nestedFunction;
                parameters = GetDescendantTokensIgnoringSeparators(parenthesized.ParameterList.Parameters);
                asyncKeyword = parenthesized.AsyncKeyword;
                body = parenthesized.Body;
                modifiers = default;
                returnType = null;
                identifier = default;
                typeParameters = null;
                break;
 
            case SyntaxKind.AnonymousMethodExpression:
                var anonymous = (AnonymousMethodExpressionSyntax)nestedFunction;
                if (anonymous.ParameterList != null)
                {
                    parameters = GetDescendantTokensIgnoringSeparators(anonymous.ParameterList.Parameters);
                }
                else
                {
                    parameters = [];
                }
 
                asyncKeyword = anonymous.AsyncKeyword;
                body = anonymous.Block;
                modifiers = default;
                returnType = null;
                identifier = default;
                typeParameters = null;
                break;
 
            case SyntaxKind.LocalFunctionStatement:
                var localFunction = (LocalFunctionStatementSyntax)nestedFunction;
                parameters = GetDescendantTokensIgnoringSeparators(localFunction.ParameterList.Parameters);
                asyncKeyword = default;
                body = (SyntaxNode?)localFunction.Body ?? localFunction.ExpressionBody!;
                modifiers = localFunction.Modifiers;
                returnType = localFunction.ReturnType;
                identifier = localFunction.Identifier;
                typeParameters = localFunction.TypeParameterList;
                break;
 
            default:
                throw ExceptionUtilities.UnexpectedValue(nestedFunction.Kind());
        }
    }
 
    private bool TryComputeWeightedDistance(BlockSyntax leftBlock, BlockSyntax rightBlock, out double distance)
    {
        // No block can be matched with the root block.
        // Note that in constructors the root is the constructor declaration, since we need to include 
        // the constructor initializer in the match.
        if (leftBlock.Parent == null ||
            rightBlock.Parent == null ||
            leftBlock.Parent.IsKind(SyntaxKind.ConstructorDeclaration) ||
            rightBlock.Parent.IsKind(SyntaxKind.ConstructorDeclaration))
        {
            distance = 0.0;
            return true;
        }
 
        if (GetLabel(leftBlock.Parent) != GetLabel(rightBlock.Parent))
        {
            distance = 0.2 + 0.8 * ComputeWeightedBlockDistance(leftBlock, rightBlock);
            return true;
        }
 
        switch (leftBlock.Parent.Kind())
        {
            case SyntaxKind.IfStatement:
            case SyntaxKind.ForEachStatement:
            case SyntaxKind.ForEachVariableStatement:
            case SyntaxKind.ForStatement:
            case SyntaxKind.WhileStatement:
            case SyntaxKind.DoStatement:
            case SyntaxKind.FixedStatement:
            case SyntaxKind.LockStatement:
            case SyntaxKind.UsingStatement:
            case SyntaxKind.SwitchSection:
            case SyntaxKind.ParenthesizedLambdaExpression:
            case SyntaxKind.SimpleLambdaExpression:
            case SyntaxKind.AnonymousMethodExpression:
            case SyntaxKind.LocalFunctionStatement:
                // value distance of the block body is included:
                distance = GetDistance(leftBlock.Parent, rightBlock.Parent);
                return true;
 
            case SyntaxKind.CatchClause:
                var leftCatch = (CatchClauseSyntax)leftBlock.Parent;
                var rightCatch = (CatchClauseSyntax)rightBlock.Parent;
                if (leftCatch.Declaration == null && leftCatch.Filter == null &&
                    rightCatch.Declaration == null && rightCatch.Filter == null)
                {
                    var leftTry = (TryStatementSyntax)leftCatch.Parent!;
                    var rightTry = (TryStatementSyntax)rightCatch.Parent!;
 
                    distance = 0.5 * ComputeValueDistance(leftTry.Block, rightTry.Block) +
                               0.5 * ComputeValueDistance(leftBlock, rightBlock);
                }
                else
                {
                    // value distance of the block body is included:
                    distance = GetDistance(leftBlock.Parent, rightBlock.Parent);
                }
 
                return true;
 
            case SyntaxKind.Block:
            case SyntaxKind.LabeledStatement:
            case SyntaxKind.GlobalStatement:
                distance = ComputeWeightedBlockDistance(leftBlock, rightBlock);
                return true;
 
            case SyntaxKind.UnsafeStatement:
            case SyntaxKind.CheckedStatement:
            case SyntaxKind.UncheckedStatement:
            case SyntaxKind.ElseClause:
            case SyntaxKind.FinallyClause:
            case SyntaxKind.TryStatement:
                distance = 0.2 * ComputeValueDistance(leftBlock, rightBlock);
                return true;
 
            default:
                throw ExceptionUtilities.UnexpectedValue(leftBlock.Parent.Kind());
        }
    }
 
    private double ComputeWeightedDistance(SingleVariableDesignationSyntax leftNode, SingleVariableDesignationSyntax rightNode)
    {
        var distance = ComputeDistance(leftNode, rightNode);
        double parentDistance;
 
        if (leftNode.Parent != null &&
            rightNode.Parent != null &&
            GetLabel(leftNode.Parent) == GetLabel(rightNode.Parent))
        {
            parentDistance = ComputeDistance(leftNode.Parent, rightNode.Parent);
        }
        else
        {
            parentDistance = 1;
        }
 
        return 0.5 * parentDistance + 0.5 * distance;
    }
 
    private static double ComputeWeightedBlockDistance(BlockSyntax leftBlock, BlockSyntax rightBlock)
    {
        if (TryComputeLocalsDistance(leftBlock, rightBlock, out var distance))
        {
            return distance;
        }
 
        return ComputeValueDistance(leftBlock, rightBlock);
    }
 
    private static double ComputeWeightedDistance(CatchClauseSyntax left, CatchClauseSyntax right)
    {
        var blockDistance = ComputeDistance(left.Block, right.Block);
        var distance = CombineOptional(blockDistance, left.Declaration, right.Declaration, left.Filter, right.Filter);
        return AdjustForLocalsInBlock(distance, left.Block, right.Block, localsWeight: 0.3);
    }
 
    private static double ComputeWeightedDistance(
        CommonForEachStatementSyntax leftCommonForEach,
        CommonForEachStatementSyntax rightCommonForEach)
    {
        var statementDistance = ComputeDistance(leftCommonForEach.Statement, rightCommonForEach.Statement);
        var expressionDistance = ComputeDistance(leftCommonForEach.Expression, rightCommonForEach.Expression);
 
        List<SyntaxToken>? leftLocals = null;
        List<SyntaxToken>? rightLocals = null;
        GetLocalNames(leftCommonForEach, ref leftLocals);
        GetLocalNames(rightCommonForEach, ref rightLocals);
 
        var localNamesDistance = ComputeDistance(leftLocals, rightLocals);
 
        var distance = localNamesDistance * 0.6 + expressionDistance * 0.2 + statementDistance * 0.2;
        return AdjustForLocalsInBlock(distance, leftCommonForEach.Statement, rightCommonForEach.Statement, localsWeight: 0.6);
    }
 
    private static double ComputeWeightedDistance(ForStatementSyntax left, ForStatementSyntax right)
    {
        var statementDistance = ComputeDistance(left.Statement, right.Statement);
        var conditionDistance = ComputeDistance(left.Condition, right.Condition);
 
        var incDistance = ComputeDistance(
            GetDescendantTokensIgnoringSeparators(left.Incrementors), GetDescendantTokensIgnoringSeparators(right.Incrementors));
 
        var distance = conditionDistance * 0.3 + incDistance * 0.3 + statementDistance * 0.4;
        if (TryComputeLocalsDistance(left.Declaration, right.Declaration, out var localsDistance))
        {
            distance = distance * 0.4 + localsDistance * 0.6;
        }
 
        return distance;
    }
 
    private static double ComputeWeightedDistance(
        VariableDeclarationSyntax leftVariables,
        StatementSyntax leftStatement,
        VariableDeclarationSyntax rightVariables,
        StatementSyntax rightStatement)
    {
        var distance = ComputeDistance(leftStatement, rightStatement);
        // Put maximum weight behind the variables declared in the header of the statement.
        if (TryComputeLocalsDistance(leftVariables, rightVariables, out var localsDistance))
        {
            distance = distance * 0.4 + localsDistance * 0.6;
        }
 
        // If the statement is a block that declares local variables, 
        // weight them more than the rest of the statement.
        return AdjustForLocalsInBlock(distance, leftStatement, rightStatement, localsWeight: 0.2);
    }
 
    private static double ComputeWeightedDistance(
        SyntaxNode? leftHeader,
        StatementSyntax leftStatement,
        SyntaxNode? rightHeader,
        StatementSyntax rightStatement)
    {
        var headerDistance = ComputeDistance(leftHeader, rightHeader);
        var statementDistance = ComputeDistance(leftStatement, rightStatement);
        var distance = headerDistance * 0.6 + statementDistance * 0.4;
 
        return AdjustForLocalsInBlock(distance, leftStatement, rightStatement, localsWeight: 0.5);
    }
 
    private static double AdjustForLocalsInBlock(
        double distance,
        StatementSyntax leftStatement,
        StatementSyntax rightStatement,
        double localsWeight)
    {
        // If the statement is a block that declares local variables, 
        // weight them more than the rest of the statement.
        if (leftStatement.Kind() == SyntaxKind.Block && rightStatement.Kind() == SyntaxKind.Block)
        {
            if (TryComputeLocalsDistance((BlockSyntax)leftStatement, (BlockSyntax)rightStatement, out var localsDistance))
            {
                return localsDistance * localsWeight + distance * (1 - localsWeight);
            }
        }
 
        return distance;
    }
 
    private static bool TryComputeLocalsDistance(VariableDeclarationSyntax? left, VariableDeclarationSyntax? right, out double distance)
    {
        List<SyntaxToken>? leftLocals = null;
        List<SyntaxToken>? rightLocals = null;
 
        if (left != null)
        {
            GetLocalNames(left, ref leftLocals);
        }
 
        if (right != null)
        {
            GetLocalNames(right, ref rightLocals);
        }
 
        if (leftLocals == null || rightLocals == null)
        {
            distance = 0;
            return false;
        }
 
        distance = ComputeDistance(leftLocals, rightLocals);
        return true;
    }
 
    private static bool TryComputeLocalsDistance(BlockSyntax left, BlockSyntax right, out double distance)
    {
        List<SyntaxToken>? leftLocals = null;
        List<SyntaxToken>? rightLocals = null;
 
        GetLocalNames(left, ref leftLocals);
        GetLocalNames(right, ref rightLocals);
 
        if (leftLocals == null || rightLocals == null)
        {
            distance = 0;
            return false;
        }
 
        distance = ComputeDistance(leftLocals, rightLocals);
        return true;
    }
 
    // Doesn't include variables declared in declaration expressions
    // Consider including them (https://github.com/dotnet/roslyn/issues/37460).
    private static void GetLocalNames(BlockSyntax block, ref List<SyntaxToken>? result)
    {
        foreach (var child in block.ChildNodes())
        {
            if (child is LocalDeclarationStatementSyntax localDecl)
            {
                GetLocalNames(localDecl.Declaration, ref result);
            }
        }
    }
 
    // Doesn't include variables declared in declaration expressions
    // Consider including them (https://github.com/dotnet/roslyn/issues/37460).
    private static void GetLocalNames(VariableDeclarationSyntax localDeclaration, ref List<SyntaxToken>? result)
    {
        foreach (var local in localDeclaration.Variables)
        {
            GetLocalNames(local.Identifier, ref result);
        }
    }
 
    internal static void GetLocalNames(CommonForEachStatementSyntax commonForEach, ref List<SyntaxToken>? result)
    {
        switch (commonForEach.Kind())
        {
            case SyntaxKind.ForEachStatement:
                GetLocalNames(((ForEachStatementSyntax)commonForEach).Identifier, ref result);
                return;
 
            case SyntaxKind.ForEachVariableStatement:
                var forEachVariable = (ForEachVariableStatementSyntax)commonForEach;
                GetLocalNames(forEachVariable.Variable, ref result);
                return;
 
            default:
                throw ExceptionUtilities.UnexpectedValue(commonForEach.Kind());
        }
    }
 
    private static void GetLocalNames(ExpressionSyntax expression, ref List<SyntaxToken>? result)
    {
        switch (expression.Kind())
        {
            case SyntaxKind.DeclarationExpression:
                var declarationExpression = (DeclarationExpressionSyntax)expression;
                var localDeclaration = declarationExpression.Designation;
                GetLocalNames(localDeclaration, ref result);
                return;
 
            case SyntaxKind.TupleExpression:
                var tupleExpression = (TupleExpressionSyntax)expression;
                foreach (var argument in tupleExpression.Arguments)
                {
                    GetLocalNames(argument.Expression, ref result);
                }
 
                return;
 
            default:
                // Do nothing for node that cannot have variable declarations inside.
                return;
        }
    }
 
    private static void GetLocalNames(VariableDesignationSyntax designation, ref List<SyntaxToken>? result)
    {
        switch (designation.Kind())
        {
            case SyntaxKind.SingleVariableDesignation:
                GetLocalNames(((SingleVariableDesignationSyntax)designation).Identifier, ref result);
                return;
 
            case SyntaxKind.ParenthesizedVariableDesignation:
                var parenthesizedVariableDesignation = (ParenthesizedVariableDesignationSyntax)designation;
                foreach (var variableDesignation in parenthesizedVariableDesignation.Variables)
                {
                    GetLocalNames(variableDesignation, ref result);
                }
 
                return;
 
            case SyntaxKind.DiscardDesignation:
                return;
 
            default:
                throw ExceptionUtilities.UnexpectedValue(designation.Kind());
        }
    }
 
    private static void GetLocalNames(SyntaxToken syntaxToken, [NotNull] ref List<SyntaxToken>? result)
    {
        result ??= [];
        result.Add(syntaxToken);
    }
 
    private static double CombineOptional(
        double distance0,
        SyntaxNode? left1,
        SyntaxNode? right1,
        SyntaxNode? left2,
        SyntaxNode? right2,
        double weight0 = 0.8,
        double weight1 = 0.5)
    {
        var one = left1 != null || right1 != null;
        var two = left2 != null || right2 != null;
 
        if (!one && !two)
        {
            return distance0;
        }
 
        var distance1 = ComputeDistance(left1, right1);
        var distance2 = ComputeDistance(left2, right2);
 
        double d;
        if (one && two)
        {
            d = distance1 * weight1 + distance2 * (1 - weight1);
        }
        else if (one)
        {
            d = distance1;
        }
        else
        {
            d = distance2;
        }
 
        return distance0 * weight0 + d * (1 - weight0);
    }
 
    private static SyntaxNodeOrToken? TryGetName(SyntaxNode node)
    {
        switch (node.Kind())
        {
            case SyntaxKind.ExternAliasDirective:
                return ((ExternAliasDirectiveSyntax)node).Identifier;
 
            case SyntaxKind.UsingDirective:
                return ((UsingDirectiveSyntax)node).NamespaceOrType;
 
            case SyntaxKind.NamespaceDeclaration:
            case SyntaxKind.FileScopedNamespaceDeclaration:
                return ((BaseNamespaceDeclarationSyntax)node).Name;
 
            case SyntaxKind.ClassDeclaration:
            case SyntaxKind.StructDeclaration:
            case SyntaxKind.InterfaceDeclaration:
            case SyntaxKind.RecordDeclaration:
            case SyntaxKind.RecordStructDeclaration:
                return ((TypeDeclarationSyntax)node).Identifier;
 
            case SyntaxKind.EnumDeclaration:
                return ((EnumDeclarationSyntax)node).Identifier;
 
            case SyntaxKind.DelegateDeclaration:
                return ((DelegateDeclarationSyntax)node).Identifier;
 
            case SyntaxKind.FieldDeclaration:
            case SyntaxKind.EventFieldDeclaration:
            case SyntaxKind.VariableDeclaration:
                return null;
 
            case SyntaxKind.VariableDeclarator:
                return ((VariableDeclaratorSyntax)node).Identifier;
 
            case SyntaxKind.MethodDeclaration:
                return ((MethodDeclarationSyntax)node).Identifier;
 
            case SyntaxKind.ConversionOperatorDeclaration:
                return ((ConversionOperatorDeclarationSyntax)node).Type;
 
            case SyntaxKind.OperatorDeclaration:
                return ((OperatorDeclarationSyntax)node).OperatorToken;
 
            case SyntaxKind.ConstructorDeclaration:
                return ((ConstructorDeclarationSyntax)node).Identifier;
 
            case SyntaxKind.DestructorDeclaration:
                return ((DestructorDeclarationSyntax)node).Identifier;
 
            case SyntaxKind.PropertyDeclaration:
                return ((PropertyDeclarationSyntax)node).Identifier;
 
            case SyntaxKind.IndexerDeclaration:
                return null;
 
            case SyntaxKind.ArrowExpressionClause:
                return null;
 
            case SyntaxKind.EventDeclaration:
                return ((EventDeclarationSyntax)node).Identifier;
 
            case SyntaxKind.EnumMemberDeclaration:
                return ((EnumMemberDeclarationSyntax)node).Identifier;
 
            case SyntaxKind.GetAccessorDeclaration:
            case SyntaxKind.SetAccessorDeclaration:
            case SyntaxKind.InitAccessorDeclaration:
                return null;
 
            case SyntaxKind.TypeParameterConstraintClause:
                return ((TypeParameterConstraintClauseSyntax)node).Name.Identifier;
 
            case SyntaxKind.TypeParameter:
                return ((TypeParameterSyntax)node).Identifier;
 
            case SyntaxKind.TypeParameterList:
            case SyntaxKind.ParameterList:
            case SyntaxKind.BracketedParameterList:
                return null;
 
            case SyntaxKind.Parameter:
                return ((ParameterSyntax)node).Identifier;
 
            case SyntaxKind.AttributeList:
                return ((AttributeListSyntax)node).Target;
 
            case SyntaxKind.Attribute:
                return ((AttributeSyntax)node).Name;
 
            default:
                return null;
        }
    }
 
    public sealed override double GetDistance(SyntaxNode oldNode, SyntaxNode newNode)
    {
        Debug.Assert(GetLabel(oldNode) == GetLabel(newNode) && GetLabel(oldNode) != IgnoredNode);
 
        if (oldNode == newNode)
        {
            return ExactMatchDist;
        }
 
        if (TryComputeWeightedDistance(oldNode, newNode, out var weightedDistance))
        {
            if (weightedDistance == ExactMatchDist && !SyntaxFactory.AreEquivalent(oldNode, newNode))
            {
                weightedDistance = EpsilonDist;
            }
 
            return weightedDistance;
        }
 
        return ComputeValueDistance(oldNode, newNode);
    }
 
    internal static double ComputeValueDistance(SyntaxNode? oldNode, SyntaxNode? newNode)
    {
        if (SyntaxFactory.AreEquivalent(oldNode, newNode))
        {
            return ExactMatchDist;
        }
 
        var distance = ComputeDistance(oldNode, newNode);
 
        // We don't want to return an exact match, because there
        // must be something different, since we got here 
        return (distance == ExactMatchDist) ? EpsilonDist : distance;
    }
 
    internal static double ComputeDistance(SyntaxNodeOrToken oldNodeOrToken, SyntaxNodeOrToken newNodeOrToken)
    {
        Debug.Assert(newNodeOrToken.IsToken == oldNodeOrToken.IsToken);
 
        double distance;
        if (oldNodeOrToken.IsToken)
        {
            var leftToken = oldNodeOrToken.AsToken();
            var rightToken = newNodeOrToken.AsToken();
 
            distance = ComputeDistance(leftToken, rightToken);
            Debug.Assert(!SyntaxFactory.AreEquivalent(leftToken, rightToken) || distance == ExactMatchDist);
        }
        else
        {
            var leftNode = oldNodeOrToken.AsNode();
            var rightNode = newNodeOrToken.AsNode();
 
            distance = ComputeDistance(leftNode, rightNode);
            Debug.Assert(!SyntaxFactory.AreEquivalent(leftNode, rightNode) || distance == ExactMatchDist);
        }
 
        return distance;
    }
 
    /// <summary>
    /// Enumerates tokens of all nodes in the list. Doesn't include separators.
    /// </summary>
    internal static IEnumerable<SyntaxToken> GetDescendantTokensIgnoringSeparators<TSyntaxNode>(SeparatedSyntaxList<TSyntaxNode> list)
        where TSyntaxNode : SyntaxNode
    {
        foreach (var node in list)
        {
            foreach (var token in node.DescendantTokens())
            {
                yield return token;
            }
        }
    }
 
    /// <summary>
    /// Calculates the distance between two syntax nodes, disregarding trivia. 
    /// </summary>
    /// <remarks>
    /// Distance is a number within [0, 1], the smaller the more similar the nodes are. 
    /// </remarks>
    public static double ComputeDistance(SyntaxNode? oldNode, SyntaxNode? newNode)
    {
        if (oldNode == null || newNode == null)
        {
            return (oldNode == newNode) ? 0.0 : 1.0;
        }
 
        return ComputeDistance(oldNode.DescendantTokens(), newNode.DescendantTokens());
    }
 
    /// <summary>
    /// Calculates the distance between two syntax tokens, disregarding trivia. 
    /// </summary>
    /// <remarks>
    /// Distance is a number within [0, 1], the smaller the more similar the tokens are. 
    /// </remarks>
    public static double ComputeDistance(SyntaxToken oldToken, SyntaxToken newToken)
        => LongestCommonSubstring.ComputePrefixDistance(
            oldToken.Text, Math.Min(oldToken.Text.Length, LongestCommonSubsequence.MaxSequenceLengthForDistanceCalculation),
            newToken.Text, Math.Min(newToken.Text.Length, LongestCommonSubsequence.MaxSequenceLengthForDistanceCalculation));
 
    private static ImmutableArray<T> CreateArrayForDistanceCalculation<T>(IEnumerable<T>? enumerable)
        => enumerable is null ? [] : enumerable.Take(LongestCommonSubsequence.MaxSequenceLengthForDistanceCalculation).ToImmutableArray();
 
    /// <summary>
    /// Calculates the distance between two sequences of syntax tokens, disregarding trivia. 
    /// </summary>
    /// <remarks>
    /// Distance is a number within [0, 1], the smaller the more similar the sequences are. 
    /// </remarks>
    public static double ComputeDistance(IEnumerable<SyntaxToken>? oldTokens, IEnumerable<SyntaxToken>? newTokens)
        => LcsTokens.Instance.ComputeDistance(CreateArrayForDistanceCalculation(oldTokens), CreateArrayForDistanceCalculation(newTokens));
 
    /// <summary>
    /// Calculates the distance between two sequences of syntax nodes, disregarding trivia. 
    /// </summary>
    /// <remarks>
    /// Distance is a number within [0, 1], the smaller the more similar the sequences are. 
    /// </remarks>
    public static double ComputeDistance(IEnumerable<SyntaxNode>? oldNodes, IEnumerable<SyntaxNode>? newNodes)
        => LcsNodes.Instance.ComputeDistance(CreateArrayForDistanceCalculation(oldNodes), CreateArrayForDistanceCalculation(newNodes));
 
    /// <summary>
    /// Calculates the edits that transform one sequence of syntax nodes to another, disregarding trivia.
    /// </summary>
    public static IEnumerable<SequenceEdit> GetSequenceEdits(IEnumerable<SyntaxNode>? oldNodes, IEnumerable<SyntaxNode>? newNodes)
        => LcsNodes.Instance.GetEdits(oldNodes.AsImmutableOrEmpty(), newNodes.AsImmutableOrEmpty());
 
    /// <summary>
    /// Calculates the edits that transform one sequence of syntax nodes to another, disregarding trivia.
    /// </summary>
    public static IEnumerable<SequenceEdit> GetSequenceEdits(ImmutableArray<SyntaxNode> oldNodes, ImmutableArray<SyntaxNode> newNodes)
        => LcsNodes.Instance.GetEdits(oldNodes.NullToEmpty(), newNodes.NullToEmpty());
 
    /// <summary>
    /// Calculates the edits that transform one sequence of syntax tokens to another, disregarding trivia.
    /// </summary>
    public static IEnumerable<SequenceEdit> GetSequenceEdits(IEnumerable<SyntaxToken>? oldTokens, IEnumerable<SyntaxToken>? newTokens)
        => LcsTokens.Instance.GetEdits(oldTokens.AsImmutableOrEmpty(), newTokens.AsImmutableOrEmpty());
 
    /// <summary>
    /// Calculates the edits that transform one sequence of syntax tokens to another, disregarding trivia.
    /// </summary>
    public static IEnumerable<SequenceEdit> GetSequenceEdits(ImmutableArray<SyntaxToken> oldTokens, ImmutableArray<SyntaxToken> newTokens)
        => LcsTokens.Instance.GetEdits(oldTokens.NullToEmpty(), newTokens.NullToEmpty());
 
    private sealed class LcsTokens : LongestCommonImmutableArraySubsequence<SyntaxToken>
    {
        internal static readonly LcsTokens Instance = new LcsTokens();
 
        protected override bool Equals(SyntaxToken oldElement, SyntaxToken newElement)
            => SyntaxFactory.AreEquivalent(oldElement, newElement);
    }
 
    private sealed class LcsNodes : LongestCommonImmutableArraySubsequence<SyntaxNode>
    {
        internal static readonly LcsNodes Instance = new LcsNodes();
 
        protected override bool Equals(SyntaxNode oldElement, SyntaxNode newElement)
            => SyntaxFactory.AreEquivalent(oldElement, newElement);
    }
 
    #endregion
}