File: EditAndContinue\AbstractEditAndContinueAnalyzer.cs
Web Access
Project: ..\..\..\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.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 System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Differencing;
using Microsoft.CodeAnalysis.EditAndContinue.Contracts;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.EditAndContinue
{
    internal abstract class AbstractEditAndContinueAnalyzer : IEditAndContinueAnalyzer
    {
        internal const int DefaultStatementPart = 0;
        private const string CreateNewOnMetadataUpdateAttributeName = "CreateNewOnMetadataUpdateAttribute";
 
        /// <summary>
        /// Contains enough information to determine whether two symbols have the same signature.
        /// </summary>
        private static readonly SymbolDisplayFormat s_unqualifiedMemberDisplayFormat =
            new(
                globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted,
                typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameOnly,
                propertyStyle: SymbolDisplayPropertyStyle.NameOnly,
                genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters | SymbolDisplayGenericsOptions.IncludeVariance,
                memberOptions:
                    SymbolDisplayMemberOptions.IncludeParameters |
                    SymbolDisplayMemberOptions.IncludeExplicitInterface,
                parameterOptions:
                    SymbolDisplayParameterOptions.IncludeParamsRefOut |
                    SymbolDisplayParameterOptions.IncludeExtensionThis |
                    SymbolDisplayParameterOptions.IncludeType |
                    SymbolDisplayParameterOptions.IncludeName,
                miscellaneousOptions:
                    SymbolDisplayMiscellaneousOptions.UseSpecialTypes);
 
        private static readonly SymbolDisplayFormat s_fullyQualifiedMemberDisplayFormat =
            new(
                globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted,
                typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
                propertyStyle: SymbolDisplayPropertyStyle.NameOnly,
                genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters | SymbolDisplayGenericsOptions.IncludeVariance,
                memberOptions:
                    SymbolDisplayMemberOptions.IncludeParameters |
                    SymbolDisplayMemberOptions.IncludeExplicitInterface |
                    SymbolDisplayMemberOptions.IncludeContainingType,
                parameterOptions:
                    SymbolDisplayParameterOptions.IncludeParamsRefOut |
                    SymbolDisplayParameterOptions.IncludeExtensionThis |
                    SymbolDisplayParameterOptions.IncludeType,
                miscellaneousOptions:
                    SymbolDisplayMiscellaneousOptions.UseSpecialTypes);
 
        // used by tests to validate correct handlign of unexpected exceptions
        private readonly Action<SyntaxNode>? _testFaultInjector;
 
        protected AbstractEditAndContinueAnalyzer(Action<SyntaxNode>? testFaultInjector)
        {
            _testFaultInjector = testFaultInjector;
        }
 
        private static TraceLog Log
            => EditAndContinueWorkspaceService.AnalysisLog;
 
        internal abstract bool ExperimentalFeaturesEnabled(SyntaxTree tree);
 
        /// <summary>
        /// Finds member declaration node(s) containing given <paramref name="node"/>.
        /// Specified <paramref name="node"/> may be either a node of the declaration body or an active node that belongs to the declaration.
        /// </summary>
        /// <remarks>
        /// The implementation has to decide what kinds of nodes in top-level match relationship represent a declaration.
        /// Every member declaration must be represented by exactly one node, but not all nodes have to represent a declaration.
        /// 
        /// Note that in some cases the set of nodes of the declaration body may differ from the set of active nodes that 
        /// belong to the declaration. For example, in <c>Dim a, b As New T</c> the sets for member <c>a</c> are
        /// { <c>New</c>, <c>T</c> } and { <c>a</c> }, respectively.
        /// 
        /// May return multiple declarations if the specified <paramref name="node"/> belongs to multiple declarations,
        /// such as in VB <c>Dim a, b As New T</c> case when <paramref name="node"/> is e.g. <c>T</c>.
        /// </remarks>
        internal abstract bool TryFindMemberDeclaration(SyntaxNode? root, SyntaxNode node, out OneOrMany<SyntaxNode> declarations);
 
        /// <summary>
        /// If the specified node represents a member declaration returns a node that represents its body,
        /// i.e. a node used as the root of statement-level match.
        /// </summary>
        /// <param name="node">A node representing a declaration or a top-level edit node.</param>
        /// 
        /// <returns>
        /// Returns null for nodes that don't represent declarations.
        /// </returns>
        /// <remarks>
        /// The implementation has to decide what kinds of nodes in top-level match relationship represent a declaration.
        /// Every member declaration must be represented by exactly one node, but not all nodes have to represent a declaration.
        /// 
        /// If a member doesn't have a body (null is returned) it can't have associated active statements.
        /// 
        /// Body does not need to cover all active statements that may be associated with the member. 
        /// E.g. Body of a C# constructor is the method body block. Active statements may be placed on the base constructor call.
        ///      Body of a VB field declaration with shared AsNew initializer is the New expression. Active statements might be placed on the field variables.
        /// <see cref="FindStatementAndPartner"/> has to account for such cases.
        /// </remarks>
        internal abstract SyntaxNode? TryGetDeclarationBody(SyntaxNode node);
 
        /// <summary>
        /// True if the specified <paramref name="declaration"/> node shares body with another declaration.
        /// </summary>
        internal abstract bool IsDeclarationWithSharedBody(SyntaxNode declaration);
 
        /// <summary>
        /// If the specified node represents a member declaration returns all tokens of the member declaration
        /// that might be covered by an active statement.
        /// </summary>
        /// <returns>
        /// Tokens covering all possible breakpoint spans associated with the member, 
        /// or null if the specified node doesn't represent a member declaration or 
        /// doesn't have a body that can contain active statements.
        /// </returns>
        /// <remarks>
        /// The implementation has to decide what kinds of nodes in top-level match relationship represent a declaration.
        /// Every member declaration must be represented by exactly one node, but not all nodes have to represent a declaration.
        /// 
        /// TODO: consider implementing this via <see cref="GetActiveSpanEnvelope"/>.
        /// </remarks>
        internal abstract IEnumerable<SyntaxToken>? TryGetActiveTokens(SyntaxNode node);
 
        /// <summary>
        /// Returns a span that contains all possible breakpoint spans of the <paramref name="declaration"/>
        /// and no breakpoint spans that do not belong to the <paramref name="declaration"/>.
        /// 
        /// Returns default if the declaration does not have any breakpoint spans.
        /// </summary>
        internal abstract (TextSpan envelope, TextSpan hole) GetActiveSpanEnvelope(SyntaxNode declaration);
 
        /// <summary>
        /// Returns an ancestor that encompasses all active and statement level 
        /// nodes that belong to the member represented by <paramref name="bodyOrMatchRoot"/>.
        /// </summary>
        protected SyntaxNode? GetEncompassingAncestor(SyntaxNode? bodyOrMatchRoot)
        {
            if (bodyOrMatchRoot == null)
            {
                return null;
            }
 
            var root = GetEncompassingAncestorImpl(bodyOrMatchRoot);
            Debug.Assert(root.Span.Contains(bodyOrMatchRoot.Span));
            return root;
        }
 
        protected abstract SyntaxNode GetEncompassingAncestorImpl(SyntaxNode bodyOrMatchRoot);
 
        /// <summary>
        /// Finds a statement at given span and a declaration body.
        /// Also returns the corresponding partner statement in <paramref name="partnerDeclarationBody"/>, if specified.
        /// </summary>
        /// <remarks>
        /// The declaration body node may not contain the <paramref name="span"/>. 
        /// This happens when an active statement associated with the member is outside of its body
        /// (e.g. C# constructor, or VB <c>Dim a,b As New T</c>).
        /// If the position doesn't correspond to any statement uses the start of the <paramref name="declarationBody"/>.
        /// </remarks>
        protected abstract SyntaxNode FindStatementAndPartner(SyntaxNode declarationBody, TextSpan span, SyntaxNode? partnerDeclarationBody, out SyntaxNode? partner, out int statementPart);
 
        private SyntaxNode FindStatement(SyntaxNode declarationBody, TextSpan span, out int statementPart)
            => FindStatementAndPartner(declarationBody, span, null, out _, out statementPart);
 
        /// <summary>
        /// Maps <paramref name="leftNode"/> of a body of <paramref name="leftDeclaration"/> to corresponding body node
        /// of <paramref name="rightDeclaration"/>, assuming that the declaration bodies only differ in trivia.
        /// </summary>
        internal abstract SyntaxNode FindDeclarationBodyPartner(SyntaxNode leftDeclaration, SyntaxNode rightDeclaration, SyntaxNode leftNode);
 
        /// <summary>
        /// Returns a node that represents a body of a lambda containing specified <paramref name="node"/>,
        /// or null if the node isn't contained in a lambda. If a node is returned it must uniquely represent the lambda,
        /// i.e. be no two distinct nodes may represent the same lambda.
        /// </summary>
        protected abstract SyntaxNode? FindEnclosingLambdaBody(SyntaxNode? container, SyntaxNode node);
 
        /// <summary>
        /// Given a node that represents a lambda body returns all nodes of the body in a syntax list.
        /// </summary>
        /// <remarks>
        /// Note that VB lambda bodies are represented by a lambda header and that some lambda bodies share 
        /// their parent nodes with other bodies (e.g. join clause expressions).
        /// </remarks>
        protected abstract IEnumerable<SyntaxNode> GetLambdaBodyExpressionsAndStatements(SyntaxNode lambdaBody);
 
        protected abstract SyntaxNode? TryGetPartnerLambdaBody(SyntaxNode oldBody, SyntaxNode newLambda);
 
        protected abstract Match<SyntaxNode> ComputeTopLevelMatch(SyntaxNode oldCompilationUnit, SyntaxNode newCompilationUnit);
        protected abstract Match<SyntaxNode> ComputeBodyMatch(SyntaxNode oldBody, SyntaxNode newBody, IEnumerable<KeyValuePair<SyntaxNode, SyntaxNode>>? knownMatches);
        protected abstract Match<SyntaxNode> ComputeTopLevelDeclarationMatch(SyntaxNode oldDeclaration, SyntaxNode newDeclaration);
        protected abstract IEnumerable<SequenceEdit> GetSyntaxSequenceEdits(ImmutableArray<SyntaxNode> oldNodes, ImmutableArray<SyntaxNode> newNodes);
 
        /// <summary>
        /// Matches old active statement to new active statement without constructing full method body match.
        /// This is needed for active statements that are outside of method body, like constructor initializer.
        /// </summary>
        protected abstract bool TryMatchActiveStatement(
            SyntaxNode oldStatement,
            int statementPart,
            SyntaxNode oldBody,
            SyntaxNode newBody,
            [NotNullWhen(true)] out SyntaxNode? newStatement);
 
        protected abstract bool TryGetEnclosingBreakpointSpan(SyntaxNode root, int position, out TextSpan span);
 
        /// <summary>
        /// Get the active span that corresponds to specified node (or its part).
        /// </summary>
        /// <returns>
        /// True if the node has an active span associated with it, false otherwise.
        /// </returns>
        protected abstract bool TryGetActiveSpan(SyntaxNode node, int statementPart, int minLength, out TextSpan span);
 
        /// <summary>
        /// Yields potential active statements around the specified active statement
        /// starting with siblings following the statement, then preceding the statement, follows with its parent, its following siblings, etc.
        /// </summary>
        /// <returns>
        /// Pairs of (node, statement part), or (node, -1) indicating there is no logical following statement.
        /// The enumeration continues until the root is reached.
        /// </returns>
        protected abstract IEnumerable<(SyntaxNode statement, int statementPart)> EnumerateNearStatements(SyntaxNode statement);
 
        protected abstract bool StatementLabelEquals(SyntaxNode node1, SyntaxNode node2);
 
        /// <summary>
        /// Determines if two syntax nodes are the same, disregarding trivia differences.
        /// </summary>
        protected abstract bool AreEquivalent(SyntaxNode left, SyntaxNode right);
 
        /// <summary>
        /// Returns true if the code emitted for the old active statement part (<paramref name="statementPart"/> of <paramref name="oldStatement"/>) 
        /// is the same as the code emitted for the corresponding new active statement part (<paramref name="statementPart"/> of <paramref name="newStatement"/>). 
        /// </summary>
        /// <remarks>
        /// A rude edit is reported if an active statement is changed and this method returns true.
        /// </remarks>
        protected abstract bool AreEquivalentActiveStatements(SyntaxNode oldStatement, SyntaxNode newStatement, int statementPart);
 
        protected abstract bool IsNamespaceDeclaration(SyntaxNode node);
        protected abstract bool IsCompilationUnitWithGlobalStatements(SyntaxNode node);
        protected abstract bool IsGlobalStatement(SyntaxNode node);
        protected abstract TextSpan GetGlobalStatementDiagnosticSpan(SyntaxNode node);
 
        /// <summary>
        /// Returns all top-level type declarations (non-nested) for a given compilation unit node.
        /// </summary>
        protected abstract IEnumerable<SyntaxNode> GetTopLevelTypeDeclarations(SyntaxNode compilationUnit);
 
        /// <summary>
        /// Returns all symbols associated with an edit and an actual edit kind, which may be different then the specified one.
        /// Returns an empty set if the edit is not associated with any symbols.
        /// </summary>
        protected abstract OneOrMany<(ISymbol? oldSymbol, ISymbol? newSymbol, EditKind editKind)> GetSymbolEdits(
            EditKind editKind,
            SyntaxNode? oldNode,
            SyntaxNode? newNode,
            SemanticModel? oldModel,
            SemanticModel newModel,
            IReadOnlyDictionary<SyntaxNode, EditKind> editMap,
            CancellationToken cancellationToken);
 
        /// <summary>
        /// Analyzes data flow in the member body represented by the specified node and returns all captured variables and parameters (including "this").
        /// If the body is a field/property initializer analyzes the initializer expression only.
        /// </summary>
        protected abstract ImmutableArray<ISymbol> GetCapturedVariables(SemanticModel model, SyntaxNode memberBody);
 
        /// <summary>
        /// Enumerates all use sites of a specified variable within the specified syntax subtrees.
        /// </summary>
        protected abstract IEnumerable<SyntaxNode> GetVariableUseSites(IEnumerable<SyntaxNode> roots, ISymbol localOrParameter, SemanticModel model, CancellationToken cancellationToken);
 
        protected abstract bool AreHandledEventsEqual(IMethodSymbol oldMethod, IMethodSymbol newMethod);
 
        // diagnostic spans:
        protected abstract TextSpan? TryGetDiagnosticSpan(SyntaxNode node, EditKind editKind);
 
        internal TextSpan GetDiagnosticSpan(SyntaxNode node, EditKind editKind)
          => TryGetDiagnosticSpan(node, editKind) ?? node.Span;
 
        protected virtual TextSpan GetBodyDiagnosticSpan(SyntaxNode node, EditKind editKind)
        {
            var current = node.Parent;
            while (true)
            {
                if (current == null)
                {
                    return node.Span;
                }
 
                var span = TryGetDiagnosticSpan(current, editKind);
                if (span != null)
                {
                    return span.Value;
                }
 
                current = current.Parent;
            }
        }
 
        internal abstract TextSpan GetLambdaParameterDiagnosticSpan(SyntaxNode lambda, int ordinal);
 
        // display names:
        internal string GetDisplayName(SyntaxNode node, EditKind editKind = EditKind.Update)
          => TryGetDisplayName(node, editKind) ?? throw ExceptionUtilities.UnexpectedValue(node.GetType().Name);
 
        internal string GetDisplayName(ISymbol symbol)
            => symbol.Kind switch
            {
                SymbolKind.Event => FeaturesResources.event_,
                SymbolKind.Field => GetDisplayName((IFieldSymbol)symbol),
                SymbolKind.Method => GetDisplayName((IMethodSymbol)symbol),
                SymbolKind.NamedType => GetDisplayName((INamedTypeSymbol)symbol),
                SymbolKind.Parameter => FeaturesResources.parameter,
                SymbolKind.Property => GetDisplayName((IPropertySymbol)symbol),
                SymbolKind.TypeParameter => FeaturesResources.type_parameter,
                _ => throw ExceptionUtilities.UnexpectedValue(symbol.Kind)
            };
 
        internal virtual string GetDisplayName(IPropertySymbol symbol)
            => FeaturesResources.property_;
 
        internal virtual string GetDisplayName(INamedTypeSymbol symbol)
            => symbol.TypeKind switch
            {
                TypeKind.Class => FeaturesResources.class_,
                TypeKind.Interface => FeaturesResources.interface_,
                TypeKind.Delegate => FeaturesResources.delegate_,
                TypeKind.Enum => FeaturesResources.enum_,
                TypeKind.TypeParameter => FeaturesResources.type_parameter,
                _ => FeaturesResources.type,
            };
 
        internal virtual string GetDisplayName(IFieldSymbol symbol)
            => symbol.IsConst ? ((symbol.ContainingType.TypeKind == TypeKind.Enum) ? FeaturesResources.enum_value : FeaturesResources.const_field) :
               FeaturesResources.field;
 
        internal virtual string GetDisplayName(IMethodSymbol symbol)
            => symbol.MethodKind switch
            {
                MethodKind.Constructor => FeaturesResources.constructor,
                MethodKind.PropertyGet or MethodKind.PropertySet => FeaturesResources.property_accessor,
                MethodKind.EventAdd or MethodKind.EventRaise or MethodKind.EventRemove => FeaturesResources.event_accessor,
                MethodKind.BuiltinOperator or MethodKind.UserDefinedOperator or MethodKind.Conversion => FeaturesResources.operator_,
                _ => FeaturesResources.method,
            };
 
        /// <summary>
        /// Returns the display name of an ancestor node that contains the specified node and has a display name.
        /// </summary>
        protected virtual string GetBodyDisplayName(SyntaxNode node, EditKind editKind = EditKind.Update)
        {
            var current = node.Parent;
 
            if (current == null)
            {
                var displayName = TryGetDisplayName(node, editKind);
                if (displayName != null)
                {
                    return displayName;
                }
            }
 
            while (true)
            {
                if (current == null)
                {
                    throw ExceptionUtilities.UnexpectedValue(node.GetType().Name);
                }
 
                var displayName = TryGetDisplayName(current, editKind);
                if (displayName != null)
                {
                    return displayName;
                }
 
                current = current.Parent;
            }
        }
 
        protected abstract string? TryGetDisplayName(SyntaxNode node, EditKind editKind);
 
        protected virtual string GetSuspensionPointDisplayName(SyntaxNode node, EditKind editKind)
            => GetDisplayName(node, editKind);
 
        protected abstract string LineDirectiveKeyword { get; }
        protected abstract ushort LineDirectiveSyntaxKind { get; }
        protected abstract SymbolDisplayFormat ErrorDisplayFormat { get; }
        protected abstract List<SyntaxNode> GetExceptionHandlingAncestors(SyntaxNode node, bool isNonLeaf);
        protected abstract void GetStateMachineInfo(SyntaxNode body, out ImmutableArray<SyntaxNode> suspensionPoints, out StateMachineKinds kinds);
        protected abstract TextSpan GetExceptionHandlingRegion(SyntaxNode node, out bool coversAllChildren);
 
        internal abstract void ReportTopLevelSyntacticRudeEdits(ArrayBuilder<RudeEditDiagnostic> diagnostics, Match<SyntaxNode> match, Edit<SyntaxNode> edit, Dictionary<SyntaxNode, EditKind> editMap);
        internal abstract void ReportEnclosingExceptionHandlingRudeEdits(ArrayBuilder<RudeEditDiagnostic> diagnostics, IEnumerable<Edit<SyntaxNode>> exceptionHandlingEdits, SyntaxNode oldStatement, TextSpan newStatementSpan);
        internal abstract void ReportOtherRudeEditsAroundActiveStatement(ArrayBuilder<RudeEditDiagnostic> diagnostics, Match<SyntaxNode> match, SyntaxNode oldStatement, SyntaxNode newStatement, bool isNonLeaf);
        internal abstract void ReportMemberBodyUpdateRudeEdits(ArrayBuilder<RudeEditDiagnostic> diagnostics, SyntaxNode newMember, TextSpan? span);
        internal abstract void ReportInsertedMemberSymbolRudeEdits(ArrayBuilder<RudeEditDiagnostic> diagnostics, ISymbol newSymbol, SyntaxNode newNode, bool insertingIntoExistingContainingType);
        internal abstract void ReportStateMachineSuspensionPointRudeEdits(ArrayBuilder<RudeEditDiagnostic> diagnostics, SyntaxNode oldNode, SyntaxNode newNode);
 
        internal abstract bool IsLambda(SyntaxNode node);
        internal abstract bool IsInterfaceDeclaration(SyntaxNode node);
        internal abstract bool IsRecordDeclaration(SyntaxNode node);
 
        /// <summary>
        /// True if the node represents any form of a function definition nested in another function body (i.e. anonymous function, lambda, local function).
        /// </summary>
        internal abstract bool IsNestedFunction(SyntaxNode node);
 
        internal abstract bool IsLocalFunction(SyntaxNode node);
        internal abstract bool IsClosureScope(SyntaxNode node);
        internal abstract bool ContainsLambda(SyntaxNode declaration);
        internal abstract SyntaxNode GetLambda(SyntaxNode lambdaBody);
        internal abstract IMethodSymbol GetLambdaExpressionSymbol(SemanticModel model, SyntaxNode lambdaExpression, CancellationToken cancellationToken);
        internal abstract SyntaxNode? GetContainingQueryExpression(SyntaxNode node);
        internal abstract bool QueryClauseLambdasTypeEquivalent(SemanticModel oldModel, SyntaxNode oldNode, SemanticModel newModel, SyntaxNode newNode, CancellationToken cancellationToken);
 
        /// <summary>
        /// Returns true if the parameters of the symbol are lifted into a scope that is different from the symbol's body.
        /// </summary>
        internal abstract bool HasParameterClosureScope(ISymbol member);
 
        /// <summary>
        /// Returns all lambda bodies of a node representing a lambda, 
        /// or false if the node doesn't represent a lambda.
        /// </summary>
        /// <remarks>
        /// C# anonymous function expression and VB lambda expression both have a single body
        /// (in VB the body is the header of the lambda expression).
        /// 
        /// Some lambda queries (group by, join by) have two bodies.
        /// </remarks>
        internal abstract bool TryGetLambdaBodies(SyntaxNode node, [NotNullWhen(true)] out SyntaxNode? body1, out SyntaxNode? body2);
 
        internal abstract bool IsStateMachineMethod(SyntaxNode declaration);
 
        /// <summary>
        /// Returns the type declaration that contains a specified <paramref name="node"/>.
        /// This can be class, struct, interface, record or enum declaration.
        /// </summary>
        internal abstract SyntaxNode? TryGetContainingTypeDeclaration(SyntaxNode node);
 
        /// <summary>
        /// Returns the declaration of 
        /// - a property, indexer or event declaration whose accessor is the specified <paramref name="node"/>,
        /// - a method, an indexer or a type (delegate) if the <paramref name="node"/> is a parameter,
        /// - a method or an type if the <paramref name="node"/> is a type parameter.
        /// </summary>
        internal abstract bool TryGetAssociatedMemberDeclaration(SyntaxNode node, EditKind editKind, [NotNullWhen(true)] out SyntaxNode? declaration);
 
        internal abstract bool HasBackingField(SyntaxNode propertyDeclaration);
 
        /// <summary>
        /// Return true if the declaration is a field/property declaration with an initializer. 
        /// Shall return false for enum members.
        /// </summary>
        internal abstract bool IsDeclarationWithInitializer(SyntaxNode declaration);
 
        /// <summary>
        /// Return true if the declaration is a parameter that is part of a records primary constructor.
        /// </summary>
        internal abstract bool IsRecordPrimaryConstructorParameter(SyntaxNode declaration);
 
        /// <summary>
        /// Return true if the declaration is a property accessor for a property that represents one of the parameters in a records primary constructor.
        /// </summary>
        internal abstract bool IsPropertyAccessorDeclarationMatchingPrimaryConstructorParameter(SyntaxNode declaration, INamedTypeSymbol newContainingType, out bool isFirstAccessor);
 
        /// <summary>
        /// Return true if the declaration is a constructor declaration to which field/property initializers are emitted. 
        /// </summary>
        internal abstract bool IsConstructorWithMemberInitializers(SyntaxNode declaration);
 
        internal abstract bool IsPartial(INamedTypeSymbol type);
 
        internal abstract SyntaxNode EmptyCompilationUnit { get; }
 
        private static readonly SourceText s_emptySource = SourceText.From("");
 
        #region Document Analysis 
 
        public async Task<DocumentAnalysisResults> AnalyzeDocumentAsync(
            Project oldProject,
            AsyncLazy<ActiveStatementsMap> lazyOldActiveStatementMap,
            Document newDocument,
            ImmutableArray<LinePositionSpan> newActiveStatementSpans,
            AsyncLazy<EditAndContinueCapabilities> lazyCapabilities,
            CancellationToken cancellationToken)
        {
            var filePath = newDocument.FilePath;
 
            Debug.Assert(newDocument.State.SupportsEditAndContinue());
            Debug.Assert(!newActiveStatementSpans.IsDefault);
            Debug.Assert(newDocument.SupportsSyntaxTree);
            Debug.Assert(newDocument.SupportsSemanticModel);
            Debug.Assert(filePath != null);
 
            // assume changes until we determine there are none so that EnC is blocked on unexpected exception:
            var hasChanges = true;
 
            try
            {
                cancellationToken.ThrowIfCancellationRequested();
 
                SyntaxTree? oldTree;
                SyntaxNode oldRoot;
                SourceText oldText;
 
                var oldDocument = await oldProject.GetDocumentAsync(newDocument.Id, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false);
                if (oldDocument != null)
                {
                    oldTree = await oldDocument.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
                    oldRoot = await oldTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
                    oldText = await oldDocument.GetTextAsync(cancellationToken).ConfigureAwait(false);
                }
                else
                {
                    oldTree = null;
                    oldRoot = EmptyCompilationUnit;
                    oldText = s_emptySource;
                }
 
                var newTree = await newDocument.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
                Contract.ThrowIfNull(newTree);
 
                // Changes in parse options might change the meaning of the code even if nothing else changed.
                // The IDE should disallow changing the options during debugging session. 
                Debug.Assert(oldTree == null || oldTree.Options.Equals(newTree.Options));
 
                var newRoot = await newTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
                var newText = await newDocument.GetTextAsync(cancellationToken).ConfigureAwait(false);
                hasChanges = !oldText.ContentEquals(newText);
 
                _testFaultInjector?.Invoke(newRoot);
                cancellationToken.ThrowIfCancellationRequested();
 
                // TODO: newTree.HasErrors?
                var syntaxDiagnostics = newRoot.GetDiagnostics();
                var syntaxError = syntaxDiagnostics.FirstOrDefault(d => d.Severity == DiagnosticSeverity.Error);
                if (syntaxError != null)
                {
                    // Bail, since we can't do syntax diffing on broken trees (it would not produce useful results anyways).
                    // If we needed to do so for some reason, we'd need to harden the syntax tree comparers.
                    Log.Write("Syntax errors found in '{0}'", filePath);
                    return DocumentAnalysisResults.SyntaxErrors(newDocument.Id, filePath, ImmutableArray<RudeEditDiagnostic>.Empty, syntaxError, hasChanges);
                }
 
                if (!hasChanges)
                {
                    // The document might have been closed and reopened, which might have triggered analysis. 
                    // If the document is unchanged don't continue the analysis since 
                    // a) comparing texts is cheaper than diffing trees
                    // b) we need to ignore errors in unchanged documents
 
                    Log.Write("Document unchanged: '{0}'", filePath);
                    return DocumentAnalysisResults.Unchanged(newDocument.Id, filePath);
                }
 
                // Disallow modification of a file with experimental features enabled.
                // These features may not be handled well by the analysis below.
                if (ExperimentalFeaturesEnabled(newTree))
                {
                    Log.Write("Experimental features enabled in '{0}'", filePath);
 
                    return DocumentAnalysisResults.SyntaxErrors(newDocument.Id, filePath, ImmutableArray.Create(
                        new RudeEditDiagnostic(RudeEditKind.ExperimentalFeaturesEnabled, default)), syntaxError: null, hasChanges);
                }
 
                var capabilities = new EditAndContinueCapabilitiesGrantor(await lazyCapabilities.GetValueAsync(cancellationToken).ConfigureAwait(false));
                var oldActiveStatementMap = await lazyOldActiveStatementMap.GetValueAsync(cancellationToken).ConfigureAwait(false);
 
                // If the document has changed at all, lets make sure Edit and Continue is supported
                if (!capabilities.Grant(EditAndContinueCapabilities.Baseline))
                {
                    return DocumentAnalysisResults.SyntaxErrors(newDocument.Id, filePath, ImmutableArray.Create(
                       new RudeEditDiagnostic(RudeEditKind.NotSupportedByRuntime, default)), syntaxError: null, hasChanges);
                }
 
                // We are in break state when there are no active statements.
                var inBreakState = !oldActiveStatementMap.IsEmpty;
 
                // We do calculate diffs even if there are semantic errors for the following reasons: 
                // 1) We need to be able to find active spans in the new document. 
                //    If we didn't calculate them we would only rely on tracking spans (might be ok).
                // 2) If there are syntactic rude edits we'll report them faster without waiting for semantic analysis.
                //    The user may fix them before they address all the semantic errors.
 
                using var _2 = ArrayBuilder<RudeEditDiagnostic>.GetInstance(out var diagnostics);
 
                cancellationToken.ThrowIfCancellationRequested();
 
                var topMatch = ComputeTopLevelMatch(oldRoot, newRoot);
                var syntacticEdits = topMatch.GetTreeEdits();
                var editMap = BuildEditMap(syntacticEdits);
 
                ReportTopLevelSyntacticRudeEdits(diagnostics, syntacticEdits, editMap);
 
                cancellationToken.ThrowIfCancellationRequested();
 
                using var _3 = ArrayBuilder<(SyntaxNode OldNode, SyntaxNode NewNode, TextSpan DiagnosticSpan)>.GetInstance(out var triviaEdits);
                using var _4 = ArrayBuilder<SequencePointUpdates>.GetInstance(out var lineEdits);
 
                AnalyzeTrivia(
                    topMatch,
                    editMap,
                    triviaEdits,
                    lineEdits,
                    cancellationToken);
 
                cancellationToken.ThrowIfCancellationRequested();
 
                var oldActiveStatements = (oldTree == null) ? ImmutableArray<UnmappedActiveStatement>.Empty :
                    oldActiveStatementMap.GetOldActiveStatements(this, oldTree, oldText, oldRoot, cancellationToken);
 
                var newActiveStatements = ImmutableArray.CreateBuilder<ActiveStatement>(oldActiveStatements.Length);
                newActiveStatements.Count = oldActiveStatements.Length;
 
                var newExceptionRegions = ImmutableArray.CreateBuilder<ImmutableArray<SourceFileSpan>>(oldActiveStatements.Length);
                newExceptionRegions.Count = oldActiveStatements.Length;
 
                var semanticEdits = await AnalyzeSemanticsAsync(
                    syntacticEdits,
                    editMap,
                    oldActiveStatements,
                    newActiveStatementSpans,
                    triviaEdits,
                    oldProject,
                    oldDocument,
                    newDocument,
                    newText,
                    diagnostics,
                    newActiveStatements,
                    newExceptionRegions,
                    capabilities,
                    inBreakState,
                    cancellationToken).ConfigureAwait(false);
 
                cancellationToken.ThrowIfCancellationRequested();
 
                AnalyzeUnchangedActiveMemberBodies(diagnostics, syntacticEdits.Match, newText, oldActiveStatements, newActiveStatementSpans, newActiveStatements, newExceptionRegions, cancellationToken);
                Debug.Assert(newActiveStatements.All(a => a != null));
 
                var hasRudeEdits = diagnostics.Count > 0;
                if (hasRudeEdits)
                {
                    LogRudeEdits(diagnostics, newText, filePath);
                }
                else
                {
                    Log.Write("Capabilities required by '{0}': {1}", filePath, capabilities.GrantedCapabilities);
                }
 
                return new DocumentAnalysisResults(
                    newDocument.Id,
                    filePath,
                    newActiveStatements.MoveToImmutable(),
                    diagnostics.ToImmutable(),
                    syntaxError: null,
                    hasRudeEdits ? default : semanticEdits,
                    hasRudeEdits ? default : newExceptionRegions.MoveToImmutable(),
                    hasRudeEdits ? default : lineEdits.ToImmutable(),
                    hasRudeEdits ? default : capabilities.GrantedCapabilities,
                    hasChanges: true,
                    hasSyntaxErrors: false);
            }
            catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken))
            {
                // The same behavior as if there was a syntax error - we are unable to analyze the document. 
                // We expect OOM to be thrown during the analysis if the number of top-level entities is too large.
                // In such case we report a rude edit for the document. If the host is actually running out of memory,
                // it might throw another OOM here or later on.
                var diagnostic = (e is OutOfMemoryException)
                    ? new RudeEditDiagnostic(RudeEditKind.SourceFileTooBig, span: default, arguments: new[] { newDocument.FilePath })
                    : new RudeEditDiagnostic(RudeEditKind.InternalError, span: default, arguments: new[] { newDocument.FilePath, e.ToString() });
 
                // Report as "syntax error" - we can't analyze the document
                return DocumentAnalysisResults.SyntaxErrors(newDocument.Id, filePath, ImmutableArray.Create(diagnostic), syntaxError: null, hasChanges);
            }
 
            static void LogRudeEdits(ArrayBuilder<RudeEditDiagnostic> diagnostics, SourceText text, string filePath)
            {
                foreach (var diagnostic in diagnostics)
                {
                    int lineNumber;
                    string? lineText;
                    try
                    {
                        var line = text.Lines.GetLineFromPosition(diagnostic.Span.Start);
                        lineNumber = line.LineNumber;
                        lineText = text.ToString(TextSpan.FromBounds(diagnostic.Span.Start, Math.Min(diagnostic.Span.Start + 120, line.End)));
                    }
                    catch
                    {
                        lineNumber = -1;
                        lineText = null;
                    }
 
                    Log.Write("Rude edit {0}:{1} '{2}' line {3}: '{4}'", diagnostic.Kind, diagnostic.SyntaxKind, filePath, lineNumber, lineText);
                }
            }
        }
 
        private void ReportTopLevelSyntacticRudeEdits(ArrayBuilder<RudeEditDiagnostic> diagnostics, EditScript<SyntaxNode> syntacticEdits, Dictionary<SyntaxNode, EditKind> editMap)
        {
            foreach (var edit in syntacticEdits.Edits)
            {
                ReportTopLevelSyntacticRudeEdits(diagnostics, syntacticEdits.Match, edit, editMap);
            }
        }
 
        /// <summary>
        /// Reports rude edits for a symbol that's been deleted in one location and inserted in another and the edit was not classified as
        /// <see cref="EditKind.Move"/> or <see cref="EditKind.Reorder"/>.
        /// The scenarios include moving a type declaration from one file to another and moving a member of a partial type from one partial declaration to another.
        /// </summary>
        internal virtual void ReportDeclarationInsertDeleteRudeEdits(ArrayBuilder<RudeEditDiagnostic> diagnostics, SyntaxNode oldNode, SyntaxNode newNode, ISymbol oldSymbol, ISymbol newSymbol, EditAndContinueCapabilitiesGrantor capabilities, CancellationToken cancellationToken)
        {
            // When a method is moved to a different declaration and its parameters are changed at the same time
            // the new method symbol key will not resolve to the old one since the parameters are different.
            // As a result we will report separate delete and insert rude edits.
            //
            // For delegates, however, the symbol key will resolve to the old type so we need to report
            // rude edits here.
            if (oldSymbol is INamedTypeSymbol { DelegateInvokeMethod: not null and var oldDelegateInvoke } &&
                newSymbol is INamedTypeSymbol { DelegateInvokeMethod: not null and var newDelegateInvoke })
            {
                if (!ParameterTypesEquivalent(oldDelegateInvoke.Parameters, newDelegateInvoke.Parameters, exact: false))
                {
                    ReportUpdateRudeEdit(diagnostics, RudeEditKind.ChangingParameterTypes, newSymbol, newNode, cancellationToken);
                }
            }
        }
 
        internal Dictionary<SyntaxNode, EditKind> BuildEditMap(EditScript<SyntaxNode> editScript)
        {
            var map = new Dictionary<SyntaxNode, EditKind>(editScript.Edits.Length);
 
            foreach (var edit in editScript.Edits)
            {
                // do not include reorder and move edits
 
                if (edit.Kind is EditKind.Delete or EditKind.Update)
                {
                    map.Add(edit.OldNode, edit.Kind);
                }
 
                if (edit.Kind is EditKind.Insert or EditKind.Update)
                {
                    map.Add(edit.NewNode, edit.Kind);
                }
            }
 
            // When a global statement is updated, inserted or deleted it means that the containing
            // compilation unit has been updated. This update is not recorded in the edit script
            // since compilation unit contains other items then global statements as well and 
            // we only want it to be updated in presence of changed global statements.
            if ((IsCompilationUnitWithGlobalStatements(editScript.Match.OldRoot) || IsCompilationUnitWithGlobalStatements(editScript.Match.NewRoot)) &&
                map.Any(entry => IsGlobalStatement(entry.Key)))
            {
                map.Add(editScript.Match.OldRoot, EditKind.Update);
                map.Add(editScript.Match.NewRoot, EditKind.Update);
            }
 
            return map;
        }
 
        #endregion
 
        #region Syntax Analysis
 
        private void AnalyzeUnchangedActiveMemberBodies(
            ArrayBuilder<RudeEditDiagnostic> diagnostics,
            Match<SyntaxNode> topMatch,
            SourceText newText,
            ImmutableArray<UnmappedActiveStatement> oldActiveStatements,
            ImmutableArray<LinePositionSpan> newActiveStatementSpans,
            [In, Out] ImmutableArray<ActiveStatement>.Builder newActiveStatements,
            [In, Out] ImmutableArray<ImmutableArray<SourceFileSpan>>.Builder newExceptionRegions,
            CancellationToken cancellationToken)
        {
            Debug.Assert(!newActiveStatementSpans.IsDefault);
            Debug.Assert(newActiveStatementSpans.IsEmpty || oldActiveStatements.Length == newActiveStatementSpans.Length);
            Debug.Assert(oldActiveStatements.Length == newActiveStatements.Count);
            Debug.Assert(oldActiveStatements.Length == newExceptionRegions.Count);
 
            // Active statements in methods that were not updated 
            // are not changed but their spans might have been. 
 
            for (var i = 0; i < newActiveStatements.Count; i++)
            {
                if (newActiveStatements[i] == null)
                {
                    Contract.ThrowIfFalse(newExceptionRegions[i].IsDefault);
 
                    var oldStatementSpan = oldActiveStatements[i].UnmappedSpan;
 
                    var node = TryGetNode(topMatch.OldRoot, oldStatementSpan.Start);
 
                    // Guard against invalid active statement spans (in case PDB was somehow out of sync with the source).
                    if (node != null && TryFindMemberDeclaration(topMatch.OldRoot, node, out var oldMemberDeclarations))
                    {
                        foreach (var oldMember in oldMemberDeclarations)
                        {
                            var hasPartner = topMatch.TryGetNewNode(oldMember, out var newMember);
                            Contract.ThrowIfFalse(hasPartner);
 
                            var oldBody = TryGetDeclarationBody(oldMember);
                            var newBody = TryGetDeclarationBody(newMember);
 
                            // Guard against invalid active statement spans (in case PDB was somehow out of sync with the source).
                            if (oldBody == null || newBody == null)
                            {
                                Log.Write("Invalid active statement span: [{0}..{1})", oldStatementSpan.Start, oldStatementSpan.End);
                                continue;
                            }
 
                            var statementPart = -1;
                            SyntaxNode? newStatement = null;
 
                            // We seed the method body matching algorithm with tracking spans (unless they were deleted)
                            // to get precise matching.
                            if (TryGetTrackedStatement(newActiveStatementSpans, i, newText, newMember, newBody, out var trackedStatement, out var trackedStatementPart))
                            {
                                // Adjust for active statements that cover more than the old member span.
                                // For example, C# variable declarators that represent field initializers:
                                //   [|public int <<F = Expr()>>;|]
                                var adjustedOldStatementStart = oldMember.FullSpan.Contains(oldStatementSpan.Start) ? oldStatementSpan.Start : oldMember.SpanStart;
 
                                // The tracking span might have been moved outside of lambda.
                                // It is not an error to move the statement - we just ignore it.
                                var oldEnclosingLambdaBody = FindEnclosingLambdaBody(oldBody, oldMember.FindToken(adjustedOldStatementStart).Parent!);
                                var newEnclosingLambdaBody = FindEnclosingLambdaBody(newBody, trackedStatement);
                                if (oldEnclosingLambdaBody == newEnclosingLambdaBody)
                                {
                                    newStatement = trackedStatement;
                                    statementPart = trackedStatementPart;
                                }
                            }
 
                            if (newStatement == null)
                            {
                                Contract.ThrowIfFalse(statementPart == -1);
                                FindStatementAndPartner(oldBody, oldStatementSpan, newBody, out newStatement, out statementPart);
                                Contract.ThrowIfNull(newStatement);
                            }
 
                            if (diagnostics.Count == 0)
                            {
                                var ancestors = GetExceptionHandlingAncestors(newStatement, oldActiveStatements[i].Statement.IsNonLeaf);
                                newExceptionRegions[i] = GetExceptionRegions(ancestors, newStatement.SyntaxTree, cancellationToken).Spans;
                            }
 
                            // Even though the body of the declaration haven't changed, 
                            // changes to its header might have caused the active span to become unavailable.
                            // (e.g. In C# "const" was added to modifiers of a field with an initializer).
                            var newStatementSpan = FindClosestActiveSpan(newStatement, statementPart);
 
                            newActiveStatements[i] = GetActiveStatementWithSpan(oldActiveStatements[i], newBody.SyntaxTree, newStatementSpan, diagnostics, cancellationToken);
                        }
                    }
                    else
                    {
                        Log.Write("Invalid active statement span: [{0}..{1})", oldStatementSpan.Start, oldStatementSpan.End);
                    }
 
                    // we were not able to determine the active statement location (PDB data might be invalid)
                    if (newActiveStatements[i] == null)
                    {
                        newActiveStatements[i] = oldActiveStatements[i].Statement.WithSpan(default);
                        newExceptionRegions[i] = ImmutableArray<SourceFileSpan>.Empty;
                    }
                }
            }
        }
 
        internal readonly struct ActiveNode
        {
            public readonly int ActiveStatementIndex;
            public readonly SyntaxNode OldNode;
            public readonly SyntaxNode? NewTrackedNode;
            public readonly SyntaxNode? EnclosingLambdaBody;
            public readonly int StatementPart;
 
            public ActiveNode(int activeStatementIndex, SyntaxNode oldNode, SyntaxNode? enclosingLambdaBody, int statementPart, SyntaxNode? newTrackedNode)
            {
                ActiveStatementIndex = activeStatementIndex;
                OldNode = oldNode;
                NewTrackedNode = newTrackedNode;
                EnclosingLambdaBody = enclosingLambdaBody;
                StatementPart = statementPart;
            }
        }
 
        /// <summary>
        /// Information about an active and/or a matched lambda.
        /// </summary>
        internal readonly struct LambdaInfo
        {
            // non-null for an active lambda (lambda containing an active statement)
            public readonly List<int>? ActiveNodeIndices;
 
            // both fields are non-null for a matching lambda (lambda that exists in both old and new document):
            public readonly Match<SyntaxNode>? Match;
            public readonly SyntaxNode? NewBody;
 
            public LambdaInfo(List<int> activeNodeIndices)
                : this(activeNodeIndices, null, null)
            {
            }
 
            private LambdaInfo(List<int>? activeNodeIndices, Match<SyntaxNode>? match, SyntaxNode? newLambdaBody)
            {
                ActiveNodeIndices = activeNodeIndices;
                Match = match;
                NewBody = newLambdaBody;
            }
 
            public LambdaInfo WithMatch(Match<SyntaxNode> match, SyntaxNode newLambdaBody)
                => new(ActiveNodeIndices, match, newLambdaBody);
        }
 
        private void AnalyzeChangedMemberBody(
            SyntaxNode oldDeclaration,
            SyntaxNode newDeclaration,
            SyntaxNode oldBody,
            SyntaxNode? newBody,
            SemanticModel oldModel,
            SemanticModel newModel,
            ISymbol oldSymbol,
            ISymbol newSymbol,
            SourceText newText,
            ImmutableArray<UnmappedActiveStatement> oldActiveStatements,
            ImmutableArray<LinePositionSpan> newActiveStatementSpans,
            EditAndContinueCapabilitiesGrantor capabilities,
            [Out] ImmutableArray<ActiveStatement>.Builder newActiveStatements,
            [Out] ImmutableArray<ImmutableArray<SourceFileSpan>>.Builder newExceptionRegions,
            [Out] ArrayBuilder<RudeEditDiagnostic> diagnostics,
            out Func<SyntaxNode, SyntaxNode?>? syntaxMap,
            CancellationToken cancellationToken)
        {
            Debug.Assert(!newActiveStatementSpans.IsDefault);
            Debug.Assert(newActiveStatementSpans.IsEmpty || oldActiveStatements.Length == newActiveStatementSpans.Length);
            Debug.Assert(oldActiveStatements.IsEmpty || oldActiveStatements.Length == newActiveStatements.Count);
            Debug.Assert(newActiveStatements.Count == newExceptionRegions.Count);
 
            syntaxMap = null;
 
            var activeStatementIndices = GetOverlappingActiveStatements(oldDeclaration, oldActiveStatements);
 
            if (newBody == null)
            {
                // The body has been deleted.
                var newSpan = FindClosestActiveSpan(newDeclaration, DefaultStatementPart);
                Debug.Assert(newSpan != default);
 
                foreach (var activeStatementIndex in activeStatementIndices)
                {
                    // We have already calculated the new location of this active statement when analyzing another member declaration.
                    // This may only happen when two or more member declarations share the same body (VB AsNew clause).
                    if (newActiveStatements[activeStatementIndex] != null)
                    {
                        Debug.Assert(IsDeclarationWithSharedBody(newDeclaration));
                        continue;
                    }
 
                    newActiveStatements[activeStatementIndex] = GetActiveStatementWithSpan(oldActiveStatements[activeStatementIndex], newDeclaration.SyntaxTree, newSpan, diagnostics, cancellationToken);
                    newExceptionRegions[activeStatementIndex] = ImmutableArray<SourceFileSpan>.Empty;
                }
 
                return;
            }
 
            try
            {
                ReportMemberBodyUpdateRudeEdits(diagnostics, newDeclaration, GetDiagnosticSpan(newDeclaration, EditKind.Update));
 
                _testFaultInjector?.Invoke(newBody);
 
                // Populated with active lambdas and matched lambdas. 
                // Unmatched non-active lambdas are not included.
                // { old-lambda-body -> info }
                Dictionary<SyntaxNode, LambdaInfo>? lazyActiveOrMatchedLambdas = null;
 
                // finds leaf nodes that correspond to the old active statements:
                using var _ = ArrayBuilder<ActiveNode>.GetInstance(out var activeNodes);
                foreach (var activeStatementIndex in activeStatementIndices)
                {
                    var oldStatementSpan = oldActiveStatements[activeStatementIndex].UnmappedSpan;
 
                    var oldStatementSyntax = FindStatement(oldBody, oldStatementSpan, out var statementPart);
                    Contract.ThrowIfNull(oldStatementSyntax);
 
                    var oldEnclosingLambdaBody = FindEnclosingLambdaBody(oldBody, oldStatementSyntax);
                    if (oldEnclosingLambdaBody != null)
                    {
                        lazyActiveOrMatchedLambdas ??= new Dictionary<SyntaxNode, LambdaInfo>();
 
                        if (!lazyActiveOrMatchedLambdas.TryGetValue(oldEnclosingLambdaBody, out var lambda))
                        {
                            lambda = new LambdaInfo(new List<int>());
                            lazyActiveOrMatchedLambdas.Add(oldEnclosingLambdaBody, lambda);
                        }
 
                        lambda.ActiveNodeIndices!.Add(activeNodes.Count);
                    }
 
                    SyntaxNode? trackedNode = null;
 
                    if (TryGetTrackedStatement(newActiveStatementSpans, activeStatementIndex, newText, newDeclaration, newBody, out var newStatementSyntax, out var _))
                    {
                        var newEnclosingLambdaBody = FindEnclosingLambdaBody(newBody, newStatementSyntax);
 
                        // The tracking span might have been moved outside of the lambda span.
                        // It is not an error to move the statement - we just ignore it.
                        if (oldEnclosingLambdaBody == newEnclosingLambdaBody &&
                            StatementLabelEquals(oldStatementSyntax, newStatementSyntax))
                        {
                            trackedNode = newStatementSyntax;
                        }
                    }
 
                    activeNodes.Add(new ActiveNode(activeStatementIndex, oldStatementSyntax, oldEnclosingLambdaBody, statementPart, trackedNode));
                }
 
                var bodyMatch = ComputeBodyMatch(oldBody, newBody, activeNodes.Where(n => n.EnclosingLambdaBody == null).ToArray(), diagnostics, out var oldHasStateMachineSuspensionPoint, out var newHasStateMachineSuspensionPoint);
                var map = ComputeMap(bodyMatch, activeNodes, ref lazyActiveOrMatchedLambdas, diagnostics);
 
                if (oldHasStateMachineSuspensionPoint)
                {
                    ReportStateMachineRudeEdits(oldModel.Compilation, oldSymbol, newBody, diagnostics);
                }
                else if (newHasStateMachineSuspensionPoint &&
                    !capabilities.Grant(EditAndContinueCapabilities.NewTypeDefinition))
                {
                    // Adding a state machine, either for async or iterator, will require creating a new helper class
                    // so is a rude edit if the runtime doesn't support it
                    if (newSymbol is IMethodSymbol { IsAsync: true })
                    {
                        diagnostics.Add(new RudeEditDiagnostic(RudeEditKind.MakeMethodAsyncNotSupportedByRuntime, GetDiagnosticSpan(newDeclaration, EditKind.Insert)));
                    }
                    else
                    {
                        diagnostics.Add(new RudeEditDiagnostic(RudeEditKind.MakeMethodIteratorNotSupportedByRuntime, GetDiagnosticSpan(newDeclaration, EditKind.Insert)));
                    }
                }
 
                ReportLambdaAndClosureRudeEdits(
                    oldModel,
                    oldBody,
                    newModel,
                    newBody,
                    newSymbol,
                    lazyActiveOrMatchedLambdas,
                    map,
                    capabilities,
                    diagnostics,
                    out var newBodyHasLambdas,
                    cancellationToken);
 
                // We need to provide syntax map to the compiler if 
                // 1) The new member has a active statement
                //    The values of local variables declared or synthesized in the method have to be preserved.
                // 2) The new member generates a state machine 
                //    In case the state machine is suspended we need to preserve variables.
                // 3) The new member contains lambdas
                //    We need to map new lambdas in the method to the matching old ones. 
                //    If the old method has lambdas but the new one doesn't there is nothing to preserve.
                // 4) Constructor that emits initializers is updated.
                //    We create syntax map even if it's not necessary: if any data member initializers are active/contain lambdas.
                //    Since initializers are usually simple the map should not be large enough to make it worth optimizing it away.
                if (!activeNodes.IsEmpty() ||
                    newHasStateMachineSuspensionPoint ||
                    newBodyHasLambdas ||
                    IsConstructorWithMemberInitializers(newDeclaration) ||
                    IsDeclarationWithInitializer(oldDeclaration) ||
                    IsDeclarationWithInitializer(newDeclaration))
                {
                    syntaxMap = CreateSyntaxMap(map.Reverse);
                }
 
                foreach (var activeNode in activeNodes)
                {
                    var activeStatementIndex = activeNode.ActiveStatementIndex;
                    var hasMatching = false;
                    var isNonLeaf = oldActiveStatements[activeStatementIndex].Statement.IsNonLeaf;
                    var isPartiallyExecuted = (oldActiveStatements[activeStatementIndex].Statement.Flags & ActiveStatementFlags.PartiallyExecuted) != 0;
                    var statementPart = activeNode.StatementPart;
                    var oldStatementSyntax = activeNode.OldNode;
                    var oldEnclosingLambdaBody = activeNode.EnclosingLambdaBody;
 
                    newExceptionRegions[activeStatementIndex] = ImmutableArray<SourceFileSpan>.Empty;
 
                    TextSpan newSpan;
                    SyntaxNode? newStatementSyntax;
                    Match<SyntaxNode>? match;
 
                    if (oldEnclosingLambdaBody == null)
                    {
                        match = bodyMatch;
 
                        hasMatching = TryMatchActiveStatement(oldStatementSyntax, statementPart, oldBody, newBody, out newStatementSyntax) ||
                                      match.TryGetNewNode(oldStatementSyntax, out newStatementSyntax);
                    }
                    else
                    {
                        RoslynDebug.Assert(lazyActiveOrMatchedLambdas != null);
 
                        var oldLambdaInfo = lazyActiveOrMatchedLambdas[oldEnclosingLambdaBody];
                        var newEnclosingLambdaBody = oldLambdaInfo.NewBody;
                        match = oldLambdaInfo.Match;
 
                        if (match != null)
                        {
                            RoslynDebug.Assert(newEnclosingLambdaBody != null); // matching lambda has body
 
                            hasMatching = TryMatchActiveStatement(oldStatementSyntax, statementPart, oldEnclosingLambdaBody, newEnclosingLambdaBody, out newStatementSyntax) ||
                                          match.TryGetNewNode(oldStatementSyntax, out newStatementSyntax);
                        }
                        else
                        {
                            // Lambda match is null if lambdas can't be matched, 
                            // in such case we won't have active statement matched either.
                            hasMatching = false;
                            newStatementSyntax = null;
                        }
                    }
 
                    if (hasMatching)
                    {
                        RoslynDebug.Assert(newStatementSyntax != null);
                        RoslynDebug.Assert(match != null);
 
                        // The matching node doesn't produce sequence points.
                        // E.g. "const" keyword is inserted into a local variable declaration with an initializer.
                        newSpan = FindClosestActiveSpan(newStatementSyntax, statementPart);
 
                        if ((isNonLeaf || isPartiallyExecuted) && !AreEquivalentActiveStatements(oldStatementSyntax, newStatementSyntax, statementPart))
                        {
                            // rude edit: non-leaf active statement changed
                            diagnostics.Add(new RudeEditDiagnostic(isNonLeaf ? RudeEditKind.ActiveStatementUpdate : RudeEditKind.PartiallyExecutedActiveStatementUpdate, newSpan));
                        }
 
                        // other statements around active statement:
                        ReportOtherRudeEditsAroundActiveStatement(diagnostics, match, oldStatementSyntax, newStatementSyntax, isNonLeaf);
                    }
                    else if (match == null)
                    {
                        RoslynDebug.Assert(oldEnclosingLambdaBody != null);
                        RoslynDebug.Assert(lazyActiveOrMatchedLambdas != null);
 
                        newSpan = GetDeletedNodeDiagnosticSpan(oldEnclosingLambdaBody, bodyMatch, lazyActiveOrMatchedLambdas);
 
                        // Lambda containing the active statement can't be found in the new source.
                        var oldLambda = GetLambda(oldEnclosingLambdaBody);
                        diagnostics.Add(new RudeEditDiagnostic(RudeEditKind.ActiveStatementLambdaRemoved, newSpan, oldLambda,
                            new[] { GetDisplayName(oldLambda) }));
                    }
                    else
                    {
                        newSpan = GetDeletedNodeActiveSpan(match.Matches, oldStatementSyntax);
 
                        if (isNonLeaf || isPartiallyExecuted)
                        {
                            // rude edit: internal active statement deleted
                            diagnostics.Add(
                                new RudeEditDiagnostic(isNonLeaf ? RudeEditKind.DeleteActiveStatement : RudeEditKind.PartiallyExecutedActiveStatementDelete,
                                GetDeletedNodeDiagnosticSpan(match.Matches, oldStatementSyntax),
                                arguments: new[] { FeaturesResources.code }));
                        }
                    }
 
                    // If there was a lambda, but we couldn't match its body to the new tree, then the lambda was
                    // removed, so we don't need to check it for active statements. If there wasn't a lambda then
                    // match here will be the same as bodyMatch.
                    if (match is not null)
                    {
                        // exception handling around the statement:
                        CalculateExceptionRegionsAroundActiveStatement(
                            match,
                            oldStatementSyntax,
                            newStatementSyntax,
                            newSpan,
                            activeStatementIndex,
                            isNonLeaf,
                            newExceptionRegions,
                            diagnostics,
                            cancellationToken);
                    }
 
                    // We have already calculated the new location of this active statement when analyzing another member declaration.
                    // This may only happen when two or more member declarations share the same body (VB AsNew clause).
                    Debug.Assert(IsDeclarationWithSharedBody(newDeclaration) || newActiveStatements[activeStatementIndex] == null);
                    Debug.Assert(newSpan != default);
 
                    newActiveStatements[activeStatementIndex] = GetActiveStatementWithSpan(oldActiveStatements[activeStatementIndex], newDeclaration.SyntaxTree, newSpan, diagnostics, cancellationToken);
                }
            }
            catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken))
            {
                // Set the new spans of active statements overlapping the method body to match the old spans.
                // Even though these might be now outside of the method body it's ok since we report a rude edit and don't allow to continue.
 
                foreach (var i in activeStatementIndices)
                {
                    newActiveStatements[i] = oldActiveStatements[i].Statement;
                    newExceptionRegions[i] = ImmutableArray<SourceFileSpan>.Empty;
                }
 
                string bodyName;
                try
                {
                    bodyName = GetBodyDisplayName(newBody);
                }
                catch
                {
                    bodyName = $"<node {newBody.RawKind} has no display name>";
                }
 
                var bodySpan = GetBodyDiagnosticSpan(newBody, EditKind.Update);
 
                // We expect OOM to be thrown during the analysis if the number of statements is too large.
                // In such case we report a rude edit for the document. If the host is actually running out of memory,
                // it might throw another OOM here or later on.
                if (e is OutOfMemoryException)
                {
                    diagnostics.Add(new RudeEditDiagnostic(
                        RudeEditKind.MemberBodyTooBig,
                        bodySpan,
                        newBody,
                        arguments: new[] { bodyName }));
                }
                else
                {
                    diagnostics.Add(new RudeEditDiagnostic(
                        RudeEditKind.MemberBodyInternalError,
                        bodySpan,
                        newBody,
                        arguments: new[] { bodyName, e.ToString() }));
                }
            }
        }
 
        private bool TryGetTrackedStatement(ImmutableArray<LinePositionSpan> activeStatementSpans, int index, SourceText text, SyntaxNode declaration, SyntaxNode body, [NotNullWhen(true)] out SyntaxNode? trackedStatement, out int trackedStatementPart)
        {
            trackedStatement = null;
            trackedStatementPart = -1;
 
            // Active statements are not tracked in this document (e.g. the file is closed).
            if (activeStatementSpans.IsEmpty)
            {
                return false;
            }
 
            var trackedLineSpan = activeStatementSpans[index];
            if (trackedLineSpan == default)
            {
                return false;
            }
 
            var trackedSpan = text.Lines.GetTextSpan(trackedLineSpan);
 
            // The tracking span might have been deleted or moved outside of the member span.
            // It is not an error to move the statement - we just ignore it.
            // Consider: Instead of checking here, explicitly handle all cases when active statements can be outside of the body in FindStatement and 
            // return false if the requested span is outside of the active envelope.
            var (envelope, hole) = GetActiveSpanEnvelope(declaration);
            if (!envelope.Contains(trackedSpan) || hole.Contains(trackedSpan))
            {
                return false;
            }
 
            trackedStatement = FindStatement(body, trackedSpan, out trackedStatementPart);
            return true;
        }
 
        private ActiveStatement GetActiveStatementWithSpan(UnmappedActiveStatement oldStatement, SyntaxTree newTree, TextSpan newSpan, ArrayBuilder<RudeEditDiagnostic> diagnostics, CancellationToken cancellationToken)
        {
            var mappedLineSpan = newTree.GetMappedLineSpan(newSpan, cancellationToken);
            if (mappedLineSpan.HasMappedPath && mappedLineSpan.Path != oldStatement.Statement.FileSpan.Path)
            {
                // changing the source file of an active statement
                diagnostics.Add(new RudeEditDiagnostic(
                    RudeEditKind.UpdateAroundActiveStatement,
                    newSpan,
                    LineDirectiveSyntaxKind,
                    arguments: new[] { string.Format(FeaturesResources._0_directive, LineDirectiveKeyword) }));
            }
 
            return oldStatement.Statement.WithFileSpan(mappedLineSpan);
        }
 
        private void CalculateExceptionRegionsAroundActiveStatement(
            Match<SyntaxNode> bodyMatch,
            SyntaxNode oldStatementSyntax,
            SyntaxNode? newStatementSyntax,
            TextSpan newStatementSyntaxSpan,
            int ordinal,
            bool isNonLeaf,
            ImmutableArray<ImmutableArray<SourceFileSpan>>.Builder newExceptionRegions,
            ArrayBuilder<RudeEditDiagnostic> diagnostics,
            CancellationToken cancellationToken)
        {
            if (newStatementSyntax == null)
            {
                if (!bodyMatch.NewRoot.Span.Contains(newStatementSyntaxSpan.Start))
                {
                    return;
                }
 
                newStatementSyntax = bodyMatch.NewRoot.FindToken(newStatementSyntaxSpan.Start).Parent;
 
                Contract.ThrowIfNull(newStatementSyntax);
            }
 
            var oldAncestors = GetExceptionHandlingAncestors(oldStatementSyntax, isNonLeaf);
            var newAncestors = GetExceptionHandlingAncestors(newStatementSyntax, isNonLeaf);
 
            if (oldAncestors.Count > 0 || newAncestors.Count > 0)
            {
                var edits = bodyMatch.GetSequenceEdits(oldAncestors, newAncestors);
                ReportEnclosingExceptionHandlingRudeEdits(diagnostics, edits, oldStatementSyntax, newStatementSyntaxSpan);
 
                // Exception regions are not needed in presence of errors.
                if (diagnostics.Count == 0)
                {
                    Debug.Assert(oldAncestors.Count == newAncestors.Count);
                    newExceptionRegions[ordinal] = GetExceptionRegions(newAncestors, newStatementSyntax.SyntaxTree, cancellationToken).Spans;
                }
            }
        }
 
        /// <summary>
        /// Calculates a syntax map of the entire method body including all lambda bodies it contains (recursively).
        /// </summary>
        private BidirectionalMap<SyntaxNode> ComputeMap(
            Match<SyntaxNode> bodyMatch,
            ArrayBuilder<ActiveNode> activeNodes,
            ref Dictionary<SyntaxNode, LambdaInfo>? lazyActiveOrMatchedLambdas,
            ArrayBuilder<RudeEditDiagnostic> diagnostics)
        {
            ArrayBuilder<Match<SyntaxNode>>? lambdaBodyMatches = null;
            var currentLambdaBodyMatch = -1;
            var currentBodyMatch = bodyMatch;
 
            while (true)
            {
                foreach (var (oldNode, newNode) in currentBodyMatch.Matches)
                {
                    // Skip root, only enumerate body matches.
                    if (oldNode == currentBodyMatch.OldRoot)
                    {
                        Debug.Assert(newNode == currentBodyMatch.NewRoot);
                        continue;
                    }
 
                    if (TryGetLambdaBodies(oldNode, out var oldLambdaBody1, out var oldLambdaBody2))
                    {
                        lambdaBodyMatches ??= ArrayBuilder<Match<SyntaxNode>>.GetInstance();
                        lazyActiveOrMatchedLambdas ??= new Dictionary<SyntaxNode, LambdaInfo>();
 
                        var newLambdaBody1 = TryGetPartnerLambdaBody(oldLambdaBody1, newNode);
                        if (newLambdaBody1 != null)
                        {
                            lambdaBodyMatches.Add(ComputeLambdaBodyMatch(oldLambdaBody1, newLambdaBody1, activeNodes, lazyActiveOrMatchedLambdas, diagnostics));
                        }
 
                        if (oldLambdaBody2 != null)
                        {
                            var newLambdaBody2 = TryGetPartnerLambdaBody(oldLambdaBody2, newNode);
                            if (newLambdaBody2 != null)
                            {
                                lambdaBodyMatches.Add(ComputeLambdaBodyMatch(oldLambdaBody2, newLambdaBody2, activeNodes, lazyActiveOrMatchedLambdas, diagnostics));
                            }
                        }
                    }
                }
 
                currentLambdaBodyMatch++;
                if (lambdaBodyMatches == null || currentLambdaBodyMatch == lambdaBodyMatches.Count)
                {
                    break;
                }
 
                currentBodyMatch = lambdaBodyMatches[currentLambdaBodyMatch];
            }
 
            if (lambdaBodyMatches == null)
            {
                return BidirectionalMap<SyntaxNode>.FromMatch(bodyMatch);
            }
 
            var map = new Dictionary<SyntaxNode, SyntaxNode>();
            var reverseMap = new Dictionary<SyntaxNode, SyntaxNode>();
 
            // include all matches, including the root:
            map.AddRange(bodyMatch.Matches);
            reverseMap.AddRange(bodyMatch.ReverseMatches);
 
            foreach (var lambdaBodyMatch in lambdaBodyMatches)
            {
                foreach (var (oldNode, newNode) in lambdaBodyMatch.Matches)
                {
                    if (!map.ContainsKey(oldNode))
                    {
                        map[oldNode] = newNode;
                        reverseMap[newNode] = oldNode;
                    }
                }
            }
 
            lambdaBodyMatches?.Free();
 
            return new BidirectionalMap<SyntaxNode>(map, reverseMap);
        }
 
        private Match<SyntaxNode> ComputeLambdaBodyMatch(
            SyntaxNode oldLambdaBody,
            SyntaxNode newLambdaBody,
            IReadOnlyList<ActiveNode> activeNodes,
            [Out] Dictionary<SyntaxNode, LambdaInfo> activeOrMatchedLambdas,
            [Out] ArrayBuilder<RudeEditDiagnostic> diagnostics)
        {
            ActiveNode[]? activeNodesInLambda;
            if (activeOrMatchedLambdas.TryGetValue(oldLambdaBody, out var info))
            {
                // Lambda may be matched but not be active.
                activeNodesInLambda = info.ActiveNodeIndices?.Select(i => activeNodes[i]).ToArray();
            }
            else
            {
                // If the lambda body isn't in the map it doesn't have any active/tracked statements.
                activeNodesInLambda = null;
                info = new LambdaInfo();
            }
 
            var lambdaBodyMatch = ComputeBodyMatch(oldLambdaBody,
                newLambdaBody, activeNodesInLambda ?? Array.Empty<ActiveNode>(),
                diagnostics, out _, out _);
 
            activeOrMatchedLambdas[oldLambdaBody] = info.WithMatch(lambdaBodyMatch, newLambdaBody);
 
            return lambdaBodyMatch;
        }
 
        private Match<SyntaxNode> ComputeBodyMatch(
            SyntaxNode oldBody,
            SyntaxNode newBody,
            ActiveNode[] activeNodes,
            ArrayBuilder<RudeEditDiagnostic> diagnostics,
            out bool oldHasStateMachineSuspensionPoint,
            out bool newHasStateMachineSuspensionPoint)
        {
            List<KeyValuePair<SyntaxNode, SyntaxNode>>? lazyKnownMatches = null;
            GetStateMachineInfo(oldBody, out var oldStateMachineSuspensionPoints, out var oldStateMachineKinds);
            GetStateMachineInfo(newBody, out var newStateMachineSuspensionPoints, out var newStateMachineKinds);
 
            AddMatchingActiveNodes(ref lazyKnownMatches, activeNodes);
 
            // Consider following cases:
            // 1) The new method contains yields/awaits but the old doesn't.
            //    If the method has active statements report rude edits for each inserted yield/await (insert "around" an active statement).
            // 2) The old method is async/iterator, the new method is not and it contains an active statement.
            //    Report rude edit since we can't remap IP from MoveNext to the kickoff method.
            //    Note that iterators in VB don't need to contain yield, so this case is not covered by change in number of yields.
 
            oldHasStateMachineSuspensionPoint = oldStateMachineSuspensionPoints.Length > 0;
            newHasStateMachineSuspensionPoint = newStateMachineSuspensionPoints.Length > 0;
 
            var match = ComputeBodyMatch(oldBody, newBody, lazyKnownMatches);
 
            if (IsLocalFunction(match.OldRoot) && IsLocalFunction(match.NewRoot))
            {
                ReportMemberBodyUpdateRudeEdits(diagnostics, match.NewRoot, match.NewRoot.Span);
            }
 
            if (oldStateMachineSuspensionPoints.Length > 0)
            {
                foreach (var (oldNode, newNode) in match.Matches)
                {
                    ReportStateMachineSuspensionPointRudeEdits(diagnostics, oldNode, newNode);
                }
            }
            else if (activeNodes.Length > 0)
            {
                // It is allowed to update a regular method to an async method or an iterator.
                // The only restriction is a presence of an active statement in the method body
                // since the debugger does not support remapping active statements to a different method.
                if (oldStateMachineKinds != newStateMachineKinds)
                {
                    diagnostics.Add(new RudeEditDiagnostic(
                        RudeEditKind.UpdatingStateMachineMethodAroundActiveStatement,
                        GetBodyDiagnosticSpan(newBody, EditKind.Update)));
                }
            }
 
            // report removing async as rude:
            if ((oldStateMachineKinds & StateMachineKinds.Async) != 0 && (newStateMachineKinds & StateMachineKinds.Async) == 0)
            {
                diagnostics.Add(new RudeEditDiagnostic(
                    RudeEditKind.ChangingFromAsynchronousToSynchronous,
                    GetBodyDiagnosticSpan(newBody, EditKind.Update),
                    newBody,
                    new[] { GetBodyDisplayName(newBody) }));
            }
 
            // VB supports iterator lambdas/methods without yields
            if ((oldStateMachineKinds & StateMachineKinds.Iterator) != 0 && (newStateMachineKinds & StateMachineKinds.Iterator) == 0)
            {
                diagnostics.Add(new RudeEditDiagnostic(
                    RudeEditKind.ModifiersUpdate,
                    GetBodyDiagnosticSpan(newBody, EditKind.Update),
                    newBody,
                    new[] { GetBodyDisplayName(newBody) }));
            }
 
            return match;
        }
 
        internal virtual void ReportStateMachineSuspensionPointDeletedRudeEdit(ArrayBuilder<RudeEditDiagnostic> diagnostics, Match<SyntaxNode> match, SyntaxNode deletedSuspensionPoint)
        {
            diagnostics.Add(new RudeEditDiagnostic(
                RudeEditKind.Delete,
                GetDeletedNodeDiagnosticSpan(match.Matches, deletedSuspensionPoint),
                deletedSuspensionPoint,
                new[] { GetSuspensionPointDisplayName(deletedSuspensionPoint, EditKind.Delete) }));
        }
 
        internal virtual void ReportStateMachineSuspensionPointInsertedRudeEdit(ArrayBuilder<RudeEditDiagnostic> diagnostics, Match<SyntaxNode> match, SyntaxNode insertedSuspensionPoint, bool aroundActiveStatement)
        {
            diagnostics.Add(new RudeEditDiagnostic(
                aroundActiveStatement ? RudeEditKind.InsertAroundActiveStatement : RudeEditKind.Insert,
                GetDiagnosticSpan(insertedSuspensionPoint, EditKind.Insert),
                insertedSuspensionPoint,
                new[] { GetSuspensionPointDisplayName(insertedSuspensionPoint, EditKind.Insert) }));
        }
 
        private static void AddMatchingActiveNodes(ref List<KeyValuePair<SyntaxNode, SyntaxNode>>? lazyKnownMatches, IEnumerable<ActiveNode> activeNodes)
        {
            // add nodes that are tracked by the editor buffer to known matches:
            foreach (var activeNode in activeNodes)
            {
                if (activeNode.NewTrackedNode != null)
                {
                    lazyKnownMatches ??= new List<KeyValuePair<SyntaxNode, SyntaxNode>>();
                    lazyKnownMatches.Add(KeyValuePairUtil.Create(activeNode.OldNode, activeNode.NewTrackedNode));
                }
            }
        }
 
        public ActiveStatementExceptionRegions GetExceptionRegions(SyntaxNode syntaxRoot, TextSpan unmappedActiveStatementSpan, bool isNonLeaf, CancellationToken cancellationToken)
        {
            var token = syntaxRoot.FindToken(unmappedActiveStatementSpan.Start);
            var ancestors = GetExceptionHandlingAncestors(token.Parent!, isNonLeaf);
            return GetExceptionRegions(ancestors, syntaxRoot.SyntaxTree, cancellationToken);
        }
 
        private ActiveStatementExceptionRegions GetExceptionRegions(List<SyntaxNode> exceptionHandlingAncestors, SyntaxTree tree, CancellationToken cancellationToken)
        {
            if (exceptionHandlingAncestors.Count == 0)
            {
                return new ActiveStatementExceptionRegions(ImmutableArray<SourceFileSpan>.Empty, isActiveStatementCovered: false);
            }
 
            var isCovered = false;
            using var _ = ArrayBuilder<SourceFileSpan>.GetInstance(out var result);
 
            for (var i = exceptionHandlingAncestors.Count - 1; i >= 0; i--)
            {
                var span = GetExceptionHandlingRegion(exceptionHandlingAncestors[i], out var coversAllChildren);
 
                // TODO: https://github.com/dotnet/roslyn/issues/52971
                // 1) Check that the span doesn't cross #line pragmas with different file mappings.
                // 2) Check that the mapped path does not change and report rude edits if it does.
                result.Add(tree.GetMappedLineSpan(span, cancellationToken));
 
                // Exception regions describe regions of code that can't be edited.
                // If the span covers all the children nodes we don't need to descend further.
                if (coversAllChildren)
                {
                    isCovered = true;
                    break;
                }
            }
 
            return new ActiveStatementExceptionRegions(result.ToImmutable(), isCovered);
        }
 
        private TextSpan GetDeletedNodeDiagnosticSpan(SyntaxNode deletedLambdaBody, Match<SyntaxNode> match, Dictionary<SyntaxNode, LambdaInfo> lambdaInfos)
        {
            var oldLambdaBody = deletedLambdaBody;
            while (true)
            {
                var oldParentLambdaBody = FindEnclosingLambdaBody(match.OldRoot, GetLambda(oldLambdaBody));
                if (oldParentLambdaBody == null)
                {
                    return GetDeletedNodeDiagnosticSpan(match.Matches, oldLambdaBody);
                }
 
                if (lambdaInfos.TryGetValue(oldParentLambdaBody, out var lambdaInfo) && lambdaInfo.Match != null)
                {
                    return GetDeletedNodeDiagnosticSpan(lambdaInfo.Match.Matches, oldLambdaBody);
                }
 
                oldLambdaBody = oldParentLambdaBody;
            }
        }
 
        private TextSpan FindClosestActiveSpan(SyntaxNode statement, int statementPart)
        {
            if (TryGetActiveSpan(statement, statementPart, minLength: statement.Span.Length, out var span))
            {
                return span;
            }
 
            // The node doesn't have sequence points.
            // E.g. "const" keyword is inserted into a local variable declaration with an initializer.
            foreach (var (node, part) in EnumerateNearStatements(statement))
            {
                if (part == -1)
                {
                    return node.Span;
                }
 
                if (TryGetActiveSpan(node, part, minLength: 0, out span))
                {
                    return span;
                }
            }
 
            // This might occur in cases where we report rude edit, so the exact location of the active span doesn't matter.
            // For example, when a method expression body is removed in C#.
            return statement.Span;
        }
 
        internal TextSpan GetDeletedNodeActiveSpan(IReadOnlyDictionary<SyntaxNode, SyntaxNode> forwardMap, SyntaxNode deletedNode)
        {
            foreach (var (oldNode, part) in EnumerateNearStatements(deletedNode))
            {
                if (part == -1)
                {
                    break;
                }
 
                if (forwardMap.TryGetValue(oldNode, out var newNode))
                {
                    return FindClosestActiveSpan(newNode, part);
                }
            }
 
            return GetDeletedNodeDiagnosticSpan(forwardMap, deletedNode);
        }
 
        internal TextSpan GetDeletedNodeDiagnosticSpan(IReadOnlyDictionary<SyntaxNode, SyntaxNode> forwardMap, SyntaxNode deletedNode)
        {
            var hasAncestor = TryGetMatchingAncestor(forwardMap, deletedNode, out var newAncestor);
            RoslynDebug.Assert(hasAncestor && newAncestor != null);
            return GetDiagnosticSpan(newAncestor, EditKind.Delete);
        }
 
        /// <summary>
        /// Finds the inner-most ancestor of the specified node that has a matching node in the new tree.
        /// </summary>
        private static bool TryGetMatchingAncestor(IReadOnlyDictionary<SyntaxNode, SyntaxNode> forwardMap, SyntaxNode? oldNode, [NotNullWhen(true)] out SyntaxNode? newAncestor)
        {
            while (oldNode != null)
            {
                if (forwardMap.TryGetValue(oldNode, out newAncestor))
                {
                    return true;
                }
 
                oldNode = oldNode.Parent;
            }
 
            // only happens if original oldNode is a root, 
            // otherwise we always find a matching ancestor pair (roots).
            newAncestor = null;
            return false;
        }
 
        private IEnumerable<int> GetOverlappingActiveStatements(SyntaxNode declaration, ImmutableArray<UnmappedActiveStatement> statements)
        {
            var (envelope, hole) = GetActiveSpanEnvelope(declaration);
            if (envelope == default)
            {
                yield break;
            }
 
            var range = ActiveStatementsMap.GetSpansStartingInSpan(
                envelope.Start,
                envelope.End,
                statements,
                startPositionComparer: (x, y) => x.UnmappedSpan.Start.CompareTo(y));
 
            for (var i = range.Start.Value; i < range.End.Value; i++)
            {
                if (!hole.Contains(statements[i].UnmappedSpan.Start))
                {
                    yield return i;
                }
            }
        }
 
        protected static bool HasParentEdit(IReadOnlyDictionary<SyntaxNode, EditKind> editMap, Edit<SyntaxNode> edit)
        {
            SyntaxNode node;
            switch (edit.Kind)
            {
                case EditKind.Insert:
                    node = edit.NewNode;
                    break;
 
                case EditKind.Delete:
                    node = edit.OldNode;
                    break;
 
                default:
                    return false;
            }
 
            return HasEdit(editMap, node.Parent, edit.Kind);
        }
 
        protected static bool HasEdit(IReadOnlyDictionary<SyntaxNode, EditKind> editMap, SyntaxNode? node, EditKind editKind)
        {
            return
                node is object &&
                editMap.TryGetValue(node, out var parentEdit) &&
                parentEdit == editKind;
        }
 
        #endregion
 
        #region Rude Edits around Active Statement 
 
        protected void AddAroundActiveStatementRudeDiagnostic(ArrayBuilder<RudeEditDiagnostic> diagnostics, SyntaxNode? oldNode, SyntaxNode? newNode, TextSpan newActiveStatementSpan)
        {
            if (oldNode == null)
            {
                RoslynDebug.Assert(newNode != null);
                AddRudeInsertAroundActiveStatement(diagnostics, newNode);
            }
            else if (newNode == null)
            {
                RoslynDebug.Assert(oldNode != null);
                AddRudeDeleteAroundActiveStatement(diagnostics, oldNode, newActiveStatementSpan);
            }
            else
            {
                AddRudeUpdateAroundActiveStatement(diagnostics, newNode);
            }
        }
 
        protected void AddRudeUpdateAroundActiveStatement(ArrayBuilder<RudeEditDiagnostic> diagnostics, SyntaxNode newNode)
        {
            diagnostics.Add(new RudeEditDiagnostic(
                RudeEditKind.UpdateAroundActiveStatement,
                GetDiagnosticSpan(newNode, EditKind.Update),
                newNode,
                new[] { GetDisplayName(newNode, EditKind.Update) }));
        }
 
        protected void AddRudeInsertAroundActiveStatement(ArrayBuilder<RudeEditDiagnostic> diagnostics, SyntaxNode newNode)
        {
            diagnostics.Add(new RudeEditDiagnostic(
                RudeEditKind.InsertAroundActiveStatement,
                GetDiagnosticSpan(newNode, EditKind.Insert),
                newNode,
                new[] { GetDisplayName(newNode, EditKind.Insert) }));
        }
 
        protected void AddRudeDeleteAroundActiveStatement(ArrayBuilder<RudeEditDiagnostic> diagnostics, SyntaxNode oldNode, TextSpan newActiveStatementSpan)
        {
            diagnostics.Add(new RudeEditDiagnostic(
                RudeEditKind.DeleteAroundActiveStatement,
                newActiveStatementSpan,
                oldNode,
                new[] { GetDisplayName(oldNode, EditKind.Delete) }));
        }
 
        protected void ReportUnmatchedStatements<TSyntaxNode>(
            ArrayBuilder<RudeEditDiagnostic> diagnostics,
            Match<SyntaxNode> match,
            Func<SyntaxNode, bool> nodeSelector,
            SyntaxNode oldActiveStatement,
            SyntaxNode newActiveStatement,
            Func<TSyntaxNode, TSyntaxNode, bool> areEquivalent,
            Func<TSyntaxNode, TSyntaxNode, bool>? areSimilar)
            where TSyntaxNode : SyntaxNode
        {
            var newNodes = GetAncestors(GetEncompassingAncestor(match.NewRoot), newActiveStatement, nodeSelector);
            if (newNodes == null)
            {
                return;
            }
 
            var oldNodes = GetAncestors(GetEncompassingAncestor(match.OldRoot), oldActiveStatement, nodeSelector);
 
            int matchCount;
            if (oldNodes != null)
            {
                matchCount = MatchNodes(oldNodes, newNodes, diagnostics: null, match: match, comparer: areEquivalent);
 
                // Do another pass over the nodes to improve error messages.
                if (areSimilar != null && matchCount < Math.Min(oldNodes.Count, newNodes.Count))
                {
                    matchCount += MatchNodes(oldNodes, newNodes, diagnostics: diagnostics, match: null, comparer: areSimilar);
                }
            }
            else
            {
                matchCount = 0;
            }
 
            if (matchCount < newNodes.Count)
            {
                ReportRudeEditsAndInserts(oldNodes, newNodes, diagnostics);
            }
        }
 
        private void ReportRudeEditsAndInserts(List<SyntaxNode?>? oldNodes, List<SyntaxNode?> newNodes, ArrayBuilder<RudeEditDiagnostic> diagnostics)
        {
            var oldNodeCount = (oldNodes != null) ? oldNodes.Count : 0;
 
            for (var i = 0; i < newNodes.Count; i++)
            {
                var newNode = newNodes[i];
 
                if (newNode != null)
                {
                    // Any difference can be expressed as insert, delete & insert, edit, or move & edit.
                    // Heuristic: If the nesting levels of the old and new nodes are the same we report an edit.
                    // Otherwise we report an insert.
                    if (i < oldNodeCount && oldNodes![i] != null)
                    {
                        AddRudeUpdateAroundActiveStatement(diagnostics, newNode);
                    }
                    else
                    {
                        AddRudeInsertAroundActiveStatement(diagnostics, newNode);
                    }
                }
            }
        }
 
        private int MatchNodes<TSyntaxNode>(
            List<SyntaxNode?> oldNodes,
            List<SyntaxNode?> newNodes,
            ArrayBuilder<RudeEditDiagnostic>? diagnostics,
            Match<SyntaxNode>? match,
            Func<TSyntaxNode, TSyntaxNode, bool> comparer)
            where TSyntaxNode : SyntaxNode
        {
            var matchCount = 0;
            var oldIndex = 0;
            for (var newIndex = 0; newIndex < newNodes.Count; newIndex++)
            {
                var newNode = newNodes[newIndex];
                if (newNode == null)
                {
                    continue;
                }
 
                SyntaxNode? oldNode;
                while (oldIndex < oldNodes.Count)
                {
                    oldNode = oldNodes[oldIndex];
 
                    if (oldNode != null)
                    {
                        break;
                    }
 
                    // node has already been matched with a previous new node:
                    oldIndex++;
                }
 
                if (oldIndex == oldNodes.Count)
                {
                    break;
                }
 
                var i = -1;
                if (match == null)
                {
                    i = IndexOfEquivalent(newNode, oldNodes, oldIndex, comparer);
                }
                else if (match.TryGetOldNode(newNode, out var partner) && comparer((TSyntaxNode)partner, (TSyntaxNode)newNode))
                {
                    i = oldNodes.IndexOf(partner, oldIndex);
                }
 
                if (i >= 0)
                {
                    // we have an update or an exact match:
                    oldNodes[i] = null;
                    newNodes[newIndex] = null;
                    matchCount++;
 
                    if (diagnostics != null)
                    {
                        AddRudeUpdateAroundActiveStatement(diagnostics, newNode);
                    }
                }
            }
 
            return matchCount;
        }
 
        private static int IndexOfEquivalent<TSyntaxNode>(SyntaxNode newNode, List<SyntaxNode?> oldNodes, int startIndex, Func<TSyntaxNode, TSyntaxNode, bool> comparer)
            where TSyntaxNode : SyntaxNode
        {
            for (var i = startIndex; i < oldNodes.Count; i++)
            {
                var oldNode = oldNodes[i];
                if (oldNode != null && comparer((TSyntaxNode)oldNode, (TSyntaxNode)newNode))
                {
                    return i;
                }
            }
 
            return -1;
        }
 
        private static List<SyntaxNode?>? GetAncestors(SyntaxNode? root, SyntaxNode node, Func<SyntaxNode, bool> nodeSelector)
        {
            List<SyntaxNode?>? list = null;
            var current = node;
 
            while (current is object && current != root)
            {
                if (nodeSelector(current))
                {
                    list ??= new List<SyntaxNode?>();
                    list.Add(current);
                }
 
                current = current.Parent;
            }
 
            list?.Reverse();
 
            return list;
        }
 
        #endregion
 
        #region Trivia Analysis
 
        /// <summary>
        /// Top-level edit script does not contain edits for a member if only trivia changed in its body.
        /// It also does not reflect changes in line mapping directives.
        /// Members that are unchanged but their location in the file changes are not considered updated.
        /// This method calculates line and trivia edits for all these cases.
        /// 
        /// The resulting line edits are grouped by mapped document path and sorted by <see cref="SourceLineUpdate.OldLine"/> in each group.
        /// </summary>
        private void AnalyzeTrivia(
            Match<SyntaxNode> topMatch,
            IReadOnlyDictionary<SyntaxNode, EditKind> editMap,
            [Out] ArrayBuilder<(SyntaxNode OldNode, SyntaxNode NewNode, TextSpan DiagnosticSpan)> triviaEdits,
            [Out] ArrayBuilder<SequencePointUpdates> lineEdits,
            CancellationToken cancellationToken)
        {
            var oldTree = topMatch.OldRoot.SyntaxTree;
            var newTree = topMatch.NewRoot.SyntaxTree;
 
            // note: range [oldStartLine, oldEndLine] is end-inclusive
            using var _ = ArrayBuilder<(string filePath, int oldStartLine, int oldEndLine, int delta, SyntaxNode oldNode, SyntaxNode newNode)>.GetInstance(out var segments);
 
            foreach (var (oldNode, newNode) in topMatch.Matches)
            {
                cancellationToken.ThrowIfCancellationRequested();
 
                if (editMap.ContainsKey(newNode))
                {
                    // Updated or inserted members will be (re)generated and don't need line edits.
                    Debug.Assert(editMap[newNode] is EditKind.Update or EditKind.Insert);
                    continue;
                }
 
                var newTokens = TryGetActiveTokens(newNode);
                if (newTokens == null)
                {
                    continue;
                }
 
                // A (rude) edit could have been made that changes whether the node may contain active statements,
                // so although the nodes match they might not have the same active tokens.
                // E.g. field declaration changed to const field declaration.
                var oldTokens = TryGetActiveTokens(oldNode);
                if (oldTokens == null)
                {
                    continue;
                }
 
                var newTokensEnum = newTokens.GetEnumerator();
                var oldTokensEnum = oldTokens.GetEnumerator();
 
                // We enumerate tokens of the body and split them into segments.
                // Each segment has sequence points mapped to the same file and also all lines the segment covers map to the same line delta.
                // The first token of a segment must be the first token that starts on the line. If the first segment token was in the middle line 
                // the previous token on the same line would have different line delta and we wouldn't be able to map both of them at the same time.
                // All segments are included in the segments list regardless of their line delta (even when it's 0 - i.e. the lines did not change).
                // This is necessary as we need to detect collisions of multiple segments with different deltas later on.
 
                var lastNewToken = default(SyntaxToken);
                var lastOldStartLine = -1;
                var lastOldFilePath = (string?)null;
                var requiresUpdate = false;
 
                var firstSegmentIndex = segments.Count;
                var currentSegment = (path: (string?)null, oldStartLine: 0, delta: 0, firstOldNode: (SyntaxNode?)null, firstNewNode: (SyntaxNode?)null);
                var rudeEditSpan = default(TextSpan);
 
                // Check if the breakpoint span that covers the first node of the segment can be translated from the old to the new by adding a line delta.
                // If not we need to recompile the containing member since we are not able to produce line update for it.
                // The first node of the segment can be the first node on its line but the breakpoint span might start on the previous line.
                bool IsCurrentSegmentBreakpointSpanMappable()
                {
                    var oldNode = currentSegment.firstOldNode;
                    var newNode = currentSegment.firstNewNode;
                    Contract.ThrowIfNull(oldNode);
                    Contract.ThrowIfNull(newNode);
 
                    // Some nodes (e.g. const local declaration) may not be covered by a breakpoint span.
                    if (!TryGetEnclosingBreakpointSpan(oldNode, oldNode.SpanStart, out var oldBreakpointSpan) ||
                        !TryGetEnclosingBreakpointSpan(newNode, newNode.SpanStart, out var newBreakpointSpan))
                    {
                        return true;
                    }
 
                    var oldMappedBreakpointSpan = (SourceFileSpan)oldTree.GetMappedLineSpan(oldBreakpointSpan, cancellationToken);
                    var newMappedBreakpointSpan = (SourceFileSpan)newTree.GetMappedLineSpan(newBreakpointSpan, cancellationToken);
 
                    if (oldMappedBreakpointSpan.AddLineDelta(currentSegment.delta) == newMappedBreakpointSpan)
                    {
                        return true;
                    }
 
                    rudeEditSpan = newBreakpointSpan;
                    return false;
                }
 
                void AddCurrentSegment()
                {
                    Debug.Assert(currentSegment.path != null);
                    Debug.Assert(lastOldStartLine >= 0);
 
                    // segment it ends on the line where the previous token starts (lastOldStartLine)
                    segments.Add((currentSegment.path, currentSegment.oldStartLine, lastOldStartLine, currentSegment.delta, oldNode, newNode));
                }
 
                bool oldHasToken;
                bool newHasToken;
 
                while (true)
                {
                    oldHasToken = oldTokensEnum.MoveNext();
                    newHasToken = newTokensEnum.MoveNext();
 
                    // no update edit => tokens must match:
                    Debug.Assert(oldHasToken == newHasToken);
 
                    if (!oldHasToken)
                    {
                        if (!IsCurrentSegmentBreakpointSpanMappable())
                        {
                            requiresUpdate = true;
                        }
                        else
                        {
                            // add last segment of the method body:
                            AddCurrentSegment();
                        }
 
                        break;
                    }
 
                    var oldSpan = oldTokensEnum.Current.Span;
                    var newSpan = newTokensEnum.Current.Span;
 
                    var oldMappedSpan = oldTree.GetMappedLineSpan(oldSpan, cancellationToken);
                    var newMappedSpan = newTree.GetMappedLineSpan(newSpan, cancellationToken);
 
                    var oldStartLine = oldMappedSpan.Span.Start.Line;
                    var newStartLine = newMappedSpan.Span.Start.Line;
                    var lineDelta = newStartLine - oldStartLine;
 
                    // If any tokens in the method change their mapped column or mapped path the method must be recompiled 
                    // since the Debugger/SymReader does not support these updates.
                    if (oldMappedSpan.Span.Start.Character != newMappedSpan.Span.Start.Character)
                    {
                        requiresUpdate = true;
                        break;
                    }
 
                    if (currentSegment.path != oldMappedSpan.Path || currentSegment.delta != lineDelta)
                    {
                        // end of segment:
                        if (currentSegment.path != null)
                        {
                            // Previous token start line is the same as this token start line, but the previous token line delta is not the same.
                            // We can't therefore map the old start line to a new one using line delta since that would affect both tokens the same.
                            if (lastOldStartLine == oldStartLine && string.Equals(lastOldFilePath, oldMappedSpan.Path))
                            {
                                requiresUpdate = true;
                                break;
                            }
 
                            if (!IsCurrentSegmentBreakpointSpanMappable())
                            {
                                requiresUpdate = true;
                                break;
                            }
 
                            // add current segment:
                            AddCurrentSegment();
                        }
 
                        // start new segment:
                        currentSegment = (oldMappedSpan.Path, oldStartLine, lineDelta, oldTokensEnum.Current.Parent, newTokensEnum.Current.Parent);
                    }
 
                    lastNewToken = newTokensEnum.Current;
                    lastOldStartLine = oldStartLine;
                    lastOldFilePath = oldMappedSpan.Path;
                }
 
                // All tokens of a member body have been processed now.
                if (requiresUpdate)
                {
                    // report the rude edit for the span of tokens that forced recompilation:
                    if (rudeEditSpan.IsEmpty)
                    {
                        rudeEditSpan = TextSpan.FromBounds(
                            lastNewToken.HasTrailingTrivia ? lastNewToken.Span.End : newTokensEnum.Current.FullSpan.Start,
                            newTokensEnum.Current.SpanStart);
                    }
 
                    triviaEdits.Add((oldNode, newNode, rudeEditSpan));
 
                    // remove all segments added for the current member body:
                    segments.Count = firstSegmentIndex;
                }
            }
 
            if (segments.Count == 0)
            {
                return;
            }
 
            // sort segments by file and then by start line:
            segments.Sort((x, y) =>
            {
                var result = string.CompareOrdinal(x.filePath, y.filePath);
                return (result != 0) ? result : x.oldStartLine.CompareTo(y.oldStartLine);
            });
 
            // Calculate line updates based on segments.
            // If two segments with different line deltas overlap we need to recompile all overlapping members except for the first one.
            // The debugger does not apply line deltas to recompiled methods and hence we can chose to recompile either of the overlapping segments
            // and apply line delta to the others.
            // 
            // The line delta is applied to the start line of a sequence point. If start lines of two sequence points mapped to the same location
            // before the delta is applied then they will point to the same location after the delta is applied. But that wouldn't be correct
            // if two different mappings required applying different deltas and thus different locations.
            // This also applies when two methods are on the same line in the old version and they move by different deltas.
 
            using var _1 = ArrayBuilder<SourceLineUpdate>.GetInstance(out var documentLineEdits);
 
            var currentDocumentPath = segments[0].filePath;
            var previousOldEndLine = -1;
            var previousLineDelta = 0;
            foreach (var segment in segments)
            {
                if (segment.filePath != currentDocumentPath)
                {
                    // store results for the previous document:
                    if (documentLineEdits.Count > 0)
                    {
                        lineEdits.Add(new SequencePointUpdates(currentDocumentPath, documentLineEdits.ToImmutableAndClear()));
                    }
 
                    // switch to the next document:
                    currentDocumentPath = segment.filePath;
                    previousOldEndLine = -1;
                    previousLineDelta = 0;
                }
                else if (segment.oldStartLine <= previousOldEndLine && segment.delta != previousLineDelta)
                {
                    // The segment overlaps the previous one that has a different line delta. We need to recompile the method.
                    // The debugger filters out line deltas that correspond to recompiled methods so we don't need to.
                    triviaEdits.Add((segment.oldNode, segment.newNode, segment.newNode.Span));
                    continue;
                }
 
                // If the segment being added does not start on the line immediately following the previous segment end line
                // we need to insert another line update that resets the delta to 0 for the lines following the end line.
                if (documentLineEdits.Count > 0 && segment.oldStartLine > previousOldEndLine + 1)
                {
                    Debug.Assert(previousOldEndLine >= 0);
                    documentLineEdits.Add(new SourceLineUpdate(previousOldEndLine + 1, previousOldEndLine + 1));
                    previousLineDelta = 0;
                }
 
                // Skip segment that doesn't change line numbers - the line edit would have no effect.
                // It was only added to facilitate detection of overlap with other segments.
                // Also skip the segment if the last line update has the same line delta as
                // consecutive same line deltas has the same effect as a single one.
                if (segment.delta != 0 && segment.delta != previousLineDelta)
                {
                    documentLineEdits.Add(new SourceLineUpdate(segment.oldStartLine, segment.oldStartLine + segment.delta));
                }
 
                previousOldEndLine = segment.oldEndLine;
                previousLineDelta = segment.delta;
            }
 
            if (currentDocumentPath != null && documentLineEdits.Count > 0)
            {
                lineEdits.Add(new SequencePointUpdates(currentDocumentPath, documentLineEdits.ToImmutable()));
            }
        }
 
        #endregion
 
        #region Semantic Analysis
 
        private sealed class AssemblyEqualityComparer : IEqualityComparer<IAssemblySymbol?>
        {
            public static readonly IEqualityComparer<IAssemblySymbol?> Instance = new AssemblyEqualityComparer();
 
            public bool Equals(IAssemblySymbol? x, IAssemblySymbol? y)
            {
                // Types defined in old source assembly need to be treated as equivalent to types in the new source assembly,
                // provided that they only differ in their containing assemblies.
                // 
                // The old source symbol has the same identity as the new one.
                // Two distinct assembly symbols that are referenced by the compilations have to have distinct identities.
                // If the compilation has two metadata references whose identities unify the compiler de-dups them and only creates
                // a single PE symbol. Thus comparing assemblies by identity partitions them so that each partition
                // contains assemblies that originated from the same Gen0 assembly.
 
                return Equals(x?.Identity, y?.Identity);
            }
 
            public int GetHashCode(IAssemblySymbol? obj)
                => obj?.Identity.GetHashCode() ?? 0;
        }
 
        // Ignore tuple element changes, nullability and dynamic. These type changes do not affect runtime type.
        // They only affect custom attributes emitted on the members - all runtimes are expected to accept
        // custom attribute updates in metadata deltas, even if they do not have any observable effect.
        private static readonly SymbolEquivalenceComparer s_runtimeSymbolEqualityComparer = new(
            AssemblyEqualityComparer.Instance, distinguishRefFromOut: true, tupleNamesMustMatch: false, ignoreNullableAnnotations: true);
 
        private static readonly SymbolEquivalenceComparer s_exactSymbolEqualityComparer = new(
            AssemblyEqualityComparer.Instance, distinguishRefFromOut: true, tupleNamesMustMatch: true, ignoreNullableAnnotations: false);
 
        protected static bool SymbolsEquivalent(ISymbol oldSymbol, ISymbol newSymbol)
            => s_exactSymbolEqualityComparer.Equals(oldSymbol, newSymbol);
 
        protected static bool SignaturesEquivalent(ImmutableArray<IParameterSymbol> oldParameters, ITypeSymbol oldReturnType, ImmutableArray<IParameterSymbol> newParameters, ITypeSymbol newReturnType)
            => ParameterTypesEquivalent(oldParameters, newParameters, exact: false) &&
               s_runtimeSymbolEqualityComparer.Equals(oldReturnType, newReturnType); // TODO: should check ref, ref readonly, custom mods
 
        protected static bool ParameterTypesEquivalent(ImmutableArray<IParameterSymbol> oldParameters, ImmutableArray<IParameterSymbol> newParameters, bool exact)
            => oldParameters.SequenceEqual(newParameters, exact, ParameterTypesEquivalent);
 
        protected static bool CustomModifiersEquivalent(CustomModifier oldModifier, CustomModifier newModifier, bool exact)
            => oldModifier.IsOptional == newModifier.IsOptional &&
               TypesEquivalent(oldModifier.Modifier, newModifier.Modifier, exact);
 
        protected static bool CustomModifiersEquivalent(ImmutableArray<CustomModifier> oldModifiers, ImmutableArray<CustomModifier> newModifiers, bool exact)
            => oldModifiers.SequenceEqual(newModifiers, exact, CustomModifiersEquivalent);
 
        protected static bool ReturnTypesEquivalent(IMethodSymbol oldMethod, IMethodSymbol newMethod, bool exact)
            => oldMethod.ReturnsByRef == newMethod.ReturnsByRef &&
               oldMethod.ReturnsByRefReadonly == newMethod.ReturnsByRefReadonly &&
               CustomModifiersEquivalent(oldMethod.ReturnTypeCustomModifiers, newMethod.ReturnTypeCustomModifiers, exact) &&
               CustomModifiersEquivalent(oldMethod.RefCustomModifiers, newMethod.RefCustomModifiers, exact) &&
               TypesEquivalent(oldMethod.ReturnType, newMethod.ReturnType, exact);
 
        protected static bool ReturnTypesEquivalent(IPropertySymbol oldProperty, IPropertySymbol newProperty, bool exact)
            => oldProperty.ReturnsByRef == newProperty.ReturnsByRef &&
               oldProperty.ReturnsByRefReadonly == newProperty.ReturnsByRefReadonly &&
               CustomModifiersEquivalent(oldProperty.TypeCustomModifiers, newProperty.TypeCustomModifiers, exact) &&
               CustomModifiersEquivalent(oldProperty.RefCustomModifiers, newProperty.RefCustomModifiers, exact) &&
               TypesEquivalent(oldProperty.Type, newProperty.Type, exact);
 
        protected static bool ReturnTypesEquivalent(IEventSymbol oldEvent, IEventSymbol newEvent, bool exact)
            => TypesEquivalent(oldEvent.Type, newEvent.Type, exact);
 
        // Note: SignatureTypeEquivalenceComparer compares dynamic and object the same.
        protected static bool TypesEquivalent(ITypeSymbol? oldType, ITypeSymbol? newType, bool exact)
            => (exact ? s_exactSymbolEqualityComparer : (IEqualityComparer<ITypeSymbol?>)s_runtimeSymbolEqualityComparer.SignatureTypeEquivalenceComparer).Equals(oldType, newType);
 
        protected static bool TypesEquivalent<T>(ImmutableArray<T> oldTypes, ImmutableArray<T> newTypes, bool exact) where T : ITypeSymbol
            => oldTypes.SequenceEqual(newTypes, exact, (x, y, exact) => TypesEquivalent(x, y, exact));
 
        protected static bool ParameterTypesEquivalent(IParameterSymbol oldParameter, IParameterSymbol newParameter, bool exact)
            => (exact ? s_exactSymbolEqualityComparer : s_runtimeSymbolEqualityComparer).ParameterEquivalenceComparer.Equals(oldParameter, newParameter);
 
        protected static bool TypeParameterConstraintsEquivalent(ITypeParameterSymbol oldParameter, ITypeParameterSymbol newParameter, bool exact)
            => TypesEquivalent(oldParameter.ConstraintTypes, newParameter.ConstraintTypes, exact) &&
               oldParameter.HasReferenceTypeConstraint == newParameter.HasReferenceTypeConstraint &&
               oldParameter.HasValueTypeConstraint == newParameter.HasValueTypeConstraint &&
               oldParameter.HasConstructorConstraint == newParameter.HasConstructorConstraint &&
               oldParameter.HasNotNullConstraint == newParameter.HasNotNullConstraint &&
               oldParameter.HasUnmanagedTypeConstraint == newParameter.HasUnmanagedTypeConstraint &&
               oldParameter.Variance == newParameter.Variance;
 
        protected static bool TypeParametersEquivalent(ImmutableArray<ITypeParameterSymbol> oldParameters, ImmutableArray<ITypeParameterSymbol> newParameters, bool exact)
            => oldParameters.SequenceEqual(newParameters, exact, (oldParameter, newParameter, exact) => oldParameter.Name == newParameter.Name && TypeParameterConstraintsEquivalent(oldParameter, newParameter, exact));
 
        protected static bool BaseTypesEquivalent(INamedTypeSymbol oldType, INamedTypeSymbol newType, bool exact)
            => TypesEquivalent(oldType.BaseType, newType.BaseType, exact) &&
               TypesEquivalent(oldType.AllInterfaces, newType.AllInterfaces, exact);
 
        protected static bool MemberSignaturesEquivalent(
            ISymbol? oldMember,
            ISymbol? newMember,
            Func<ImmutableArray<IParameterSymbol>, ITypeSymbol, ImmutableArray<IParameterSymbol>, ITypeSymbol, bool>? signatureComparer = null)
        {
            if (oldMember == newMember)
            {
                return true;
            }
 
            if (oldMember == null || newMember == null || oldMember.Kind != newMember.Kind)
            {
                return false;
            }
 
            signatureComparer ??= SignaturesEquivalent;
 
            switch (oldMember.Kind)
            {
                case SymbolKind.Field:
                    var oldField = (IFieldSymbol)oldMember;
                    var newField = (IFieldSymbol)newMember;
                    return signatureComparer(ImmutableArray<IParameterSymbol>.Empty, oldField.Type, ImmutableArray<IParameterSymbol>.Empty, newField.Type);
 
                case SymbolKind.Property:
                    var oldProperty = (IPropertySymbol)oldMember;
                    var newProperty = (IPropertySymbol)newMember;
                    return signatureComparer(oldProperty.Parameters, oldProperty.Type, newProperty.Parameters, newProperty.Type);
 
                case SymbolKind.Method:
                    var oldMethod = (IMethodSymbol)oldMember;
                    var newMethod = (IMethodSymbol)newMember;
                    return signatureComparer(oldMethod.Parameters, oldMethod.ReturnType, newMethod.Parameters, newMethod.ReturnType);
 
                default:
                    throw ExceptionUtilities.UnexpectedValue(oldMember.Kind);
            }
        }
 
        private readonly struct ConstructorEdit
        {
            public readonly INamedTypeSymbol OldType;
 
            /// <summary>
            /// Contains syntax maps for all changed data member initializers or constructor declarations (of constructors emitting initializers)
            /// in the currently analyzed document. The key is the declaration of the member.
            /// </summary>
            public readonly Dictionary<SyntaxNode, Func<SyntaxNode, SyntaxNode?>?> ChangedDeclarations;
 
            public ConstructorEdit(INamedTypeSymbol oldType)
            {
                OldType = oldType;
                ChangedDeclarations = new Dictionary<SyntaxNode, Func<SyntaxNode, SyntaxNode?>?>();
            }
        }
 
        protected virtual bool IsRudeEditDueToPrimaryConstructor(ISymbol symbol, CancellationToken cancellationToken)
        {
            return false;
        }
 
        private async Task<ImmutableArray<SemanticEditInfo>> AnalyzeSemanticsAsync(
            EditScript<SyntaxNode> editScript,
            IReadOnlyDictionary<SyntaxNode, EditKind> editMap,
            ImmutableArray<UnmappedActiveStatement> oldActiveStatements,
            ImmutableArray<LinePositionSpan> newActiveStatementSpans,
            IReadOnlyList<(SyntaxNode OldNode, SyntaxNode NewNode, TextSpan DiagnosticSpan)> triviaEdits,
            Project oldProject,
            Document? oldDocument,
            Document newDocument,
            SourceText newText,
            ArrayBuilder<RudeEditDiagnostic> diagnostics,
            ImmutableArray<ActiveStatement>.Builder newActiveStatements,
            ImmutableArray<ImmutableArray<SourceFileSpan>>.Builder newExceptionRegions,
            EditAndContinueCapabilitiesGrantor capabilities,
            bool inBreakState,
            CancellationToken cancellationToken)
        {
            Debug.Assert(inBreakState || newActiveStatementSpans.IsEmpty);
 
            if (editScript.Edits.Length == 0 && triviaEdits.Count == 0)
            {
                return ImmutableArray<SemanticEditInfo>.Empty;
            }
 
            // { new type -> constructor update }
            PooledDictionary<INamedTypeSymbol, ConstructorEdit>? instanceConstructorEdits = null;
            PooledDictionary<INamedTypeSymbol, ConstructorEdit>? staticConstructorEdits = null;
 
            var oldModel = (oldDocument != null) ? await oldDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false) : null;
            var newModel = await newDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
            var oldCompilation = oldModel?.Compilation ?? await oldProject.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false);
            var newCompilation = newModel.Compilation;
 
            using var _1 = PooledHashSet<ISymbol>.GetInstance(out var processedSymbols);
            using var _2 = ArrayBuilder<SemanticEditInfo>.GetInstance(out var semanticEdits);
 
            try
            {
                INamedTypeSymbol? lazyLayoutAttribute = null;
 
                foreach (var edit in editScript.Edits)
                {
                    cancellationToken.ThrowIfCancellationRequested();
 
                    Debug.Assert(edit.OldNode is null || edit.NewNode is null || IsNamespaceDeclaration(edit.OldNode) == IsNamespaceDeclaration(edit.NewNode));
 
                    // We can ignore namespace nodes in newly added documents (old model is not available) since 
                    // all newly added types in these namespaces will have their own syntax edit.
                    var symbolEdits = oldModel != null && IsNamespaceDeclaration(edit.OldNode ?? edit.NewNode!)
                        ? OneOrMany.Create(GetNamespaceSymbolEdits(oldModel, newModel, cancellationToken))
                        : GetSymbolEdits(edit.Kind, edit.OldNode, edit.NewNode, oldModel, newModel, editMap, cancellationToken);
 
                    foreach (var symbolEdit in symbolEdits)
                    {
                        Func<SyntaxNode, SyntaxNode?>? syntaxMap;
                        SemanticEditKind editKind;
 
                        var (oldSymbol, newSymbol, syntacticEditKind) = symbolEdit;
                        var symbol = newSymbol ?? oldSymbol;
                        Contract.ThrowIfNull(symbol);
 
                        if (!processedSymbols.Add(symbol))
                        {
                            continue;
                        }
 
                        if (syntacticEditKind == EditKind.Move)
                        {
                            Debug.Assert(oldSymbol is INamedTypeSymbol);
                            Debug.Assert(newSymbol is INamedTypeSymbol);
 
                            var oldSymbolInNewCompilation = SymbolKey.Create(oldSymbol, cancellationToken).Resolve(newCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol;
                            var newSymbolInOldCompilation = SymbolKey.Create(newSymbol, cancellationToken).Resolve(oldCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol;
 
                            if (oldSymbolInNewCompilation == null || newSymbolInOldCompilation == null)
                            {
                                if (TypesEquivalent(oldSymbol.ContainingType, newSymbol.ContainingType, exact: false) &&
                                    !SymbolsEquivalent(oldSymbol.ContainingNamespace, newSymbol.ContainingNamespace))
                                {
                                    // pick the first declaration in the new file that contains the namespace change:
                                    var newTypeDeclaration = GetSymbolDeclarationSyntax(newSymbol.DeclaringSyntaxReferences.First(r => r.SyntaxTree == edit.NewNode!.SyntaxTree), cancellationToken);
 
                                    diagnostics.Add(new RudeEditDiagnostic(
                                        RudeEditKind.ChangingNamespace,
                                        GetDiagnosticSpan(newTypeDeclaration, EditKind.Update),
                                        newTypeDeclaration,
                                        new[] { GetDisplayName(newTypeDeclaration), oldSymbol.ContainingNamespace.ToDisplayString(), newSymbol.ContainingNamespace.ToDisplayString() }));
                                }
                                else
                                {
                                    ReportUpdateRudeEdit(diagnostics, RudeEditKind.Move, edit.NewNode!);
                                }
                            }
 
                            continue;
                        }
 
                        var symbolKey = SymbolKey.Create(symbol, cancellationToken);
 
                        // Ignore ambiguous resolution result - it may happen if there are semantic errors in the compilation.
                        oldSymbol ??= symbolKey.Resolve(oldCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol;
                        newSymbol ??= symbolKey.Resolve(newCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol;
 
                        var (oldDeclaration, newDeclaration) = GetSymbolDeclarationNodes(oldSymbol, newSymbol, edit.OldNode, edit.NewNode);
 
                        // The syntax change implies an update of the associated symbol but the old/new symbol does not actually exist.
                        // Treat the edit as Insert/Delete. This may happen e.g. when all C# global statements are removed, the first one is added or they are moved to another file.
                        if (syntacticEditKind == EditKind.Update)
                        {
                            if (oldSymbol == null || oldDeclaration != null && oldDeclaration.SyntaxTree != oldModel?.SyntaxTree)
                            {
                                syntacticEditKind = EditKind.Insert;
                            }
                            else if (newSymbol == null || newDeclaration != null && newDeclaration.SyntaxTree != newModel.SyntaxTree)
                            {
                                syntacticEditKind = EditKind.Delete;
                            }
                        }
 
                        if (!inBreakState)
                        {
                            // Delete/insert/update edit of a member of a reloadable type (including nested types) results in Replace edit of the containing type.
                            // If a Delete edit is part of delete-insert operation (member moved to a different partial type declaration or to a different file)
                            // skip producing Replace semantic edit for this Delete edit as one will be reported by the corresponding Insert edit.
 
                            var oldContainingType = oldSymbol?.ContainingType;
                            var newContainingType = newSymbol?.ContainingType;
                            var containingType = newContainingType ?? oldContainingType;
 
                            if (containingType != null && (syntacticEditKind != EditKind.Delete || newSymbol == null))
                            {
                                var containingTypeSymbolKey = SymbolKey.Create(containingType, cancellationToken);
                                oldContainingType ??= (INamedTypeSymbol?)containingTypeSymbolKey.Resolve(oldCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol;
                                newContainingType ??= (INamedTypeSymbol?)containingTypeSymbolKey.Resolve(newCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol;
 
                                if (oldContainingType != null && newContainingType != null && IsReloadable(oldContainingType))
                                {
                                    if (processedSymbols.Add(newContainingType))
                                    {
                                        if (capabilities.Grant(EditAndContinueCapabilities.NewTypeDefinition))
                                        {
                                            semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Replace, containingTypeSymbolKey, syntaxMap: null, syntaxMapTree: null,
                                                IsPartialEdit(oldContainingType, newContainingType, editScript.Match.OldRoot.SyntaxTree, editScript.Match.NewRoot.SyntaxTree) ? containingTypeSymbolKey : null));
                                        }
                                        else
                                        {
                                            ReportUpdateRudeEdit(diagnostics, RudeEditKind.ChangingReloadableTypeNotSupportedByRuntime, newContainingType, newDeclaration, cancellationToken);
                                        }
                                    }
 
                                    continue;
                                }
                            }
 
                            var oldType = oldSymbol as INamedTypeSymbol;
                            var newType = newSymbol as INamedTypeSymbol;
 
                            // Deleting a reloadable type is a rude edit, reported the same as for non-reloadable.
                            // Adding a reloadable type is a standard type addition (TODO: unless added to a reloadable type?).
                            // Making reloadable attribute non-reloadable results in a new version of the type that is
                            // not reloadable but does not update the old version in-place.
                            if (syntacticEditKind != EditKind.Delete && oldType != null && newType != null && IsReloadable(oldType))
                            {
                                if (symbol == newType || processedSymbols.Add(newType))
                                {
                                    if (oldType.Name != newType.Name)
                                    {
                                        // https://github.com/dotnet/roslyn/issues/54886
                                        ReportUpdateRudeEdit(diagnostics, RudeEditKind.Renamed, newType, newDeclaration, cancellationToken);
                                    }
                                    else if (oldType.Arity != newType.Arity)
                                    {
                                        // https://github.com/dotnet/roslyn/issues/54881
                                        ReportUpdateRudeEdit(diagnostics, RudeEditKind.ChangingTypeParameters, newType, newDeclaration, cancellationToken);
                                    }
                                    else if (!capabilities.Grant(EditAndContinueCapabilities.NewTypeDefinition))
                                    {
                                        ReportUpdateRudeEdit(diagnostics, RudeEditKind.ChangingReloadableTypeNotSupportedByRuntime, newType, newDeclaration, cancellationToken);
                                    }
                                    else
                                    {
                                        semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Replace, symbolKey, syntaxMap: null, syntaxMapTree: null,
                                            IsPartialEdit(oldType, newType, editScript.Match.OldRoot.SyntaxTree, editScript.Match.NewRoot.SyntaxTree) ? symbolKey : null));
                                    }
                                }
 
                                continue;
                            }
                        }
 
                        switch (syntacticEditKind)
                        {
                            case EditKind.Delete:
                                {
                                    Contract.ThrowIfNull(oldModel);
                                    Contract.ThrowIfNull(oldSymbol);
                                    Contract.ThrowIfNull(oldDeclaration);
 
                                    if (IsRudeEditDueToPrimaryConstructor(oldSymbol, cancellationToken))
                                    {
                                        // https://github.com/dotnet/roslyn/issues/67108: Disable edits for now
                                        diagnostics.Add(new RudeEditDiagnostic(RudeEditKind.Delete, GetDeletedNodeDiagnosticSpan(editScript.Match.Matches, oldDeclaration),
                                                                               oldDeclaration, new[] { GetDisplayName(oldDeclaration, EditKind.Delete) }));
                                        continue;
                                    }
 
                                    var activeStatementIndices = GetOverlappingActiveStatements(oldDeclaration, oldActiveStatements);
                                    var hasActiveStatement = activeStatementIndices.Any();
 
                                    // TODO: if the member isn't a field/property we should return empty span.
                                    // We need to adjust the tracking span design and UpdateUneditedSpans to account for such empty spans.
                                    if (hasActiveStatement)
                                    {
                                        var newSpan = IsDeclarationWithInitializer(oldDeclaration)
                                            ? GetDeletedNodeActiveSpan(editScript.Match.Matches, oldDeclaration)
                                            : GetDeletedNodeDiagnosticSpan(editScript.Match.Matches, oldDeclaration);
 
                                        foreach (var index in activeStatementIndices)
                                        {
                                            Debug.Assert(newActiveStatements[index] is null);
 
                                            newActiveStatements[index] = GetActiveStatementWithSpan(oldActiveStatements[index], editScript.Match.NewRoot.SyntaxTree, newSpan, diagnostics, cancellationToken);
                                            newExceptionRegions[index] = ImmutableArray<SourceFileSpan>.Empty;
                                        }
                                    }
 
                                    syntaxMap = null;
                                    editKind = SemanticEditKind.Delete;
 
                                    // Check if the declaration has been moved from one document to another.
                                    if (newSymbol != null && !(newSymbol is IMethodSymbol newMethod && newMethod.IsPartialDefinition))
                                    {
                                        // Symbol has actually not been deleted but rather moved to another document, another partial type declaration
                                        // or replaced with an implicitly generated one (e.g. parameterless constructor, auto-generated record methods, etc.)
 
                                        // Report rude edit if the deleted code contains active statements.
                                        // TODO (https://github.com/dotnet/roslyn/issues/51177):
                                        // Only report rude edit when replacing member with an implicit one if it has an active statement.
                                        // We might be able to support moving active members but we would need to 
                                        // 1) Move AnalyzeChangedMemberBody from Insert to Delete
                                        // 2) Handle active statements that moved to a different document in ActiveStatementTrackingService
                                        // 3) The debugger's ManagedActiveStatementUpdate might need another field indicating the source file path.
                                        if (hasActiveStatement)
                                        {
                                            ReportDeletedMemberRudeEdit(diagnostics, oldSymbol, newCompilation, RudeEditKind.DeleteActiveStatement, cancellationToken);
                                            continue;
                                        }
 
                                        if (!newSymbol.IsImplicitlyDeclared)
                                        {
                                            // Ignore the delete. The new symbol is explicitly declared and thus there will be an insert edit that will issue a semantic update.
                                            // Note that this could also be the case for deleting properties of records, but they will be handled when we see
                                            // their accessors below.
                                            continue;
                                        }
 
                                        if (IsPropertyAccessorDeclarationMatchingPrimaryConstructorParameter(oldDeclaration, newSymbol.ContainingType, out var isFirst))
                                        {
                                            // Defer a constructor edit to cover the property initializer changing
                                            DeferConstructorEdit(oldSymbol.ContainingType, newSymbol.ContainingType, newDeclaration: null, syntaxMap, oldSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits);
 
                                            // If there was no body deleted then we are done since the compiler generated property also has no body
                                            if (TryGetDeclarationBody(oldDeclaration) is null)
                                            {
                                                continue;
                                            }
 
                                            // If there was a body, then the backing field of the property will be affected so we
                                            // need to issue edits for the synthezied members.
                                            // We only need to do this once though.
                                            if (isFirst)
                                            {
                                                AddEditsForSynthesizedRecordMembers(newCompilation, newSymbol.ContainingType, semanticEdits, cancellationToken);
                                            }
                                        }
 
                                        // If a constructor is deleted and replaced by an implicit one the update needs to aggregate updates to all data member initializers,
                                        // or if a property is deleted that is part of a records primary constructor, which is effectivelly moving from an explicit to implicit
                                        // initializer.
                                        if (IsConstructorWithMemberInitializers(oldDeclaration))
                                        {
                                            processedSymbols.Remove(oldSymbol);
                                            DeferConstructorEdit(oldSymbol.ContainingType, newSymbol.ContainingType, newDeclaration: null, syntaxMap, oldSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits);
                                            continue;
                                        }
 
                                        // there is no insert edit for an implicit declaration, therefore we need to issue an update:
                                        editKind = SemanticEditKind.Update;
                                    }
                                    else
                                    {
                                        var diagnosticSpan = GetDeletedNodeDiagnosticSpan(editScript.Match.Matches, oldDeclaration);
 
                                        // If we got here for a global statement then the actual edit is a delete of the synthesized Main method
                                        if (IsGlobalMain(oldSymbol))
                                        {
                                            diagnostics.Add(new RudeEditDiagnostic(RudeEditKind.Delete, diagnosticSpan, edit.OldNode, new[] { GetDisplayName(edit.OldNode!, EditKind.Delete) }));
                                            continue;
                                        }
 
                                        var rudeEditKind = RudeEditKind.Delete;
 
                                        // If the associated member declaration (parameter/type parameter -> method) has also been deleted skip
                                        // the delete of the symbol as it will be deleted by the delete of the associated member. We pass the edit kind
                                        // in here to avoid property/event accessors from being caught up in this, because those deletes we want to process
                                        // separately, below.
                                        //
                                        // Associated member declarations must be in the same document as the symbol, so we don't need to resolve their symbol.
                                        // In some cases the symbol even can't be resolved unambiguously. Consider e.g. resolving a method with its parameter deleted -
                                        // we wouldn't know which overload to resolve to.
                                        if (TryGetAssociatedMemberDeclaration(oldDeclaration, EditKind.Delete, out var oldAssociatedMemberDeclaration))
                                        {
                                            if (HasEdit(editMap, oldAssociatedMemberDeclaration, EditKind.Delete))
                                            {
                                                continue;
                                            }
 
                                            // We allow deleting parameters, by issuing delete and insert edits for the old and new method
                                            if (oldSymbol is IParameterSymbol)
                                            {
                                                if (TryAddParameterInsertOrDeleteEdits(semanticEdits, oldSymbol.ContainingSymbol, newModel, capabilities, syntaxMap, editScript, processedSymbols, cancellationToken, out var notSupportedByRuntime))
                                                {
                                                    continue;
                                                }
 
                                                if (notSupportedByRuntime)
                                                {
                                                    rudeEditKind = RudeEditKind.DeleteNotSupportedByRuntime;
                                                }
                                            }
                                        }
                                        else if (oldSymbol.ContainingType != null)
                                        {
                                            // Check if the symbol being deleted is a member of a type that's also being deleted.
                                            // If so, skip the member deletion and only report the containing symbol deletion.
                                            var containingSymbolKey = SymbolKey.Create(oldSymbol.ContainingType, cancellationToken);
                                            var newContainingSymbol = containingSymbolKey.Resolve(newCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol;
                                            if (newContainingSymbol == null)
                                            {
                                                continue;
                                            }
 
                                            if (!hasActiveStatement && AllowsDeletion(oldSymbol))
                                            {
                                                AddMemberOrAssociatedMemberSemanticEdits(semanticEdits, SemanticEditKind.Delete, oldSymbol, containingSymbolKey, syntaxMap, partialType: null, processedSymbols, cancellationToken);
                                                continue;
                                            }
                                        }
 
                                        // deleting symbol is not allowed
 
                                        diagnostics.Add(new RudeEditDiagnostic(
                                            rudeEditKind,
                                            diagnosticSpan,
                                            oldDeclaration,
                                            new[]
                                            {
                                                string.Format(FeaturesResources.member_kind_and_name,
                                                    GetDisplayName(oldDeclaration, EditKind.Delete),
                                                    oldSymbol.ToDisplayString(diagnosticSpan.IsEmpty ? s_fullyQualifiedMemberDisplayFormat : s_unqualifiedMemberDisplayFormat))
                                            }));
 
                                        continue;
                                    }
                                }
 
                                break;
 
                            case EditKind.Insert:
                                {
                                    Contract.ThrowIfNull(newModel);
                                    Contract.ThrowIfNull(newSymbol);
                                    Contract.ThrowIfNull(newDeclaration);
 
                                    if (IsRudeEditDueToPrimaryConstructor(newSymbol, cancellationToken))
                                    {
                                        // https://github.com/dotnet/roslyn/issues/67108: Disable edits for now
                                        diagnostics.Add(new RudeEditDiagnostic(RudeEditKind.Insert, GetDiagnosticSpan(newDeclaration, EditKind.Insert),
                                                                               newDeclaration, new[] { GetDisplayName(newDeclaration, EditKind.Insert) }));
                                        continue;
                                    }
 
                                    syntaxMap = null;
 
                                    editKind = SemanticEditKind.Insert;
                                    INamedTypeSymbol? oldContainingType;
                                    var newContainingType = newSymbol.ContainingType;
 
                                    // Check if the declaration has been moved from one document to another.
                                    if (oldSymbol != null)
                                    {
                                        // Symbol has actually not been inserted but rather moved between documents or partial type declarations,
                                        // or is replacing an implicitly generated one (e.g. parameterless constructor, auto-generated record methods, etc.)
                                        oldContainingType = oldSymbol.ContainingType;
 
                                        if (oldSymbol.IsImplicitlyDeclared)
                                        {
                                            // If a user explicitly implements a member of a record then we want to issue an update, not an insert.
                                            if (oldSymbol.DeclaringSyntaxReferences.Length == 1)
                                            {
                                                Contract.ThrowIfNull(oldDeclaration);
                                                ReportDeclarationInsertDeleteRudeEdits(diagnostics, oldDeclaration, newDeclaration, oldSymbol, newSymbol, capabilities, cancellationToken);
 
                                                if (IsPropertyAccessorDeclarationMatchingPrimaryConstructorParameter(newDeclaration, newContainingType, out var isFirst))
                                                {
                                                    // If there is no body declared we can skip it entirely because for a property accessor
                                                    // it matches what the compiler would have previously implicitly implemented.
                                                    if (TryGetDeclarationBody(newDeclaration) is null)
                                                    {
                                                        continue;
                                                    }
 
                                                    // If there was a body, then the backing field of the property will be affected so we
                                                    // need to issue edits for the synthezied members. Only need to do it once.
                                                    if (isFirst)
                                                    {
                                                        AddEditsForSynthesizedRecordMembers(newCompilation, newContainingType, semanticEdits, cancellationToken);
                                                    }
                                                }
 
                                                editKind = SemanticEditKind.Update;
                                            }
                                        }
                                        else if (oldSymbol.DeclaringSyntaxReferences.Length == 1 && newSymbol.DeclaringSyntaxReferences.Length == 1)
                                        {
                                            Contract.ThrowIfNull(oldDeclaration);
 
                                            // Handles partial methods and explicitly implemented properties that implement positional parameters of records
 
                                            // We ignore partial method definition parts when processing edits (GetSymbolForEdit).
                                            // The only declaration in compilation without syntax errors that can have multiple declaring references is a type declaration.
                                            // We can therefore ignore any symbols that have more than one declaration.
                                            ReportTypeLayoutUpdateRudeEdits(diagnostics, newSymbol, newDeclaration, newModel, ref lazyLayoutAttribute);
 
                                            // Compare the old declaration syntax of the symbol with its new declaration and report rude edits
                                            // if it changed in any way that's not allowed.
                                            ReportDeclarationInsertDeleteRudeEdits(diagnostics, oldDeclaration, newDeclaration, oldSymbol, newSymbol, capabilities, cancellationToken);
 
                                            var oldBody = TryGetDeclarationBody(oldDeclaration);
                                            if (oldBody != null)
                                            {
                                                // The old symbol's declaration syntax may be located in a different document than the old version of the current document.
                                                var oldSyntaxDocument = oldProject.Solution.GetRequiredDocument(oldDeclaration.SyntaxTree);
                                                var oldSyntaxModel = await oldSyntaxDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
                                                var oldSyntaxText = await oldSyntaxDocument.GetTextAsync(cancellationToken).ConfigureAwait(false);
                                                var newBody = TryGetDeclarationBody(newDeclaration);
 
                                                // Skip analysis of active statements. We already report rude edit for removal of code containing
                                                // active statements in the old declaration and don't currently support moving active statements.
                                                AnalyzeChangedMemberBody(
                                                    oldDeclaration,
                                                    newDeclaration,
                                                    oldBody,
                                                    newBody,
                                                    oldSyntaxModel,
                                                    newModel,
                                                    oldSymbol,
                                                    newSymbol,
                                                    newText,
                                                    oldActiveStatements: ImmutableArray<UnmappedActiveStatement>.Empty,
                                                    newActiveStatementSpans: ImmutableArray<LinePositionSpan>.Empty,
                                                    capabilities: capabilities,
                                                    newActiveStatements,
                                                    newExceptionRegions,
                                                    diagnostics,
                                                    out syntaxMap,
                                                    cancellationToken);
                                            }
 
                                            // If a constructor changes from including initializers to not including initializers
                                            // we don't need to aggregate syntax map from all initializers for the constructor update semantic edit.
                                            var isNewConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(newDeclaration);
                                            var isDeclarationWithInitializer = IsDeclarationWithInitializer(oldDeclaration) || IsDeclarationWithInitializer(newDeclaration);
                                            var isRecordPrimaryConstructorParameter = IsRecordPrimaryConstructorParameter(oldDeclaration);
 
                                            if (isNewConstructorWithMemberInitializers || isDeclarationWithInitializer || isRecordPrimaryConstructorParameter)
                                            {
                                                if (isNewConstructorWithMemberInitializers)
                                                {
                                                    processedSymbols.Remove(newSymbol);
                                                }
 
                                                if (isDeclarationWithInitializer)
                                                {
                                                    AnalyzeSymbolUpdate(oldSymbol, newSymbol, edit.NewNode, newCompilation, editScript.Match, capabilities, diagnostics, semanticEdits, syntaxMap, processedSymbols, cancellationToken);
                                                }
 
                                                DeferConstructorEdit(oldSymbol.ContainingType, newContainingType, newDeclaration, syntaxMap, newSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits);
 
                                                // Don't add a separate semantic edit.
                                                // Updates of data members with initializers and constructors that emit initializers will be aggregated and added later.
                                                continue;
                                            }
 
                                            editKind = SemanticEditKind.Update;
                                        }
                                        else
                                        {
                                            editKind = SemanticEditKind.Update;
                                        }
                                    }
                                    else if (TryGetAssociatedMemberDeclaration(newDeclaration, EditKind.Insert, out var newAssociatedMemberDeclaration) &&
                                             HasEdit(editMap, newAssociatedMemberDeclaration, EditKind.Insert))
                                    {
                                        // If the symbol is an accessor and the containing property/indexer/event declaration has also been inserted skip
                                        // the insert of the accessor as it will be inserted by the property/indexer/event.
                                        continue;
                                    }
                                    else if (newSymbol is ITypeParameterSymbol)
                                    {
                                        diagnostics.Add(new RudeEditDiagnostic(
                                            RudeEditKind.Insert,
                                            GetDiagnosticSpan(newDeclaration, EditKind.Insert),
                                            newDeclaration,
                                            arguments: new[] { GetDisplayName(newDeclaration, EditKind.Insert) }));
 
                                        continue;
                                    }
                                    else if (newSymbol is IParameterSymbol)
                                    {
                                        if (!TryAddParameterInsertOrDeleteEdits(semanticEdits, newSymbol.ContainingSymbol, oldModel, capabilities, syntaxMap, editScript, processedSymbols, cancellationToken, out var notSupportedByRuntime))
                                        {
                                            diagnostics.Add(new RudeEditDiagnostic(
                                               notSupportedByRuntime ? RudeEditKind.InsertNotSupportedByRuntime : RudeEditKind.Insert,
                                               GetDiagnosticSpan(newDeclaration, EditKind.Insert),
                                               newDeclaration,
                                               arguments: new[] { GetDisplayName(newDeclaration, EditKind.Insert) }));
                                        }
 
                                        continue;
                                    }
                                    else if (newContainingType != null && !IsGlobalMain(newSymbol))
                                    {
                                        // The edit actually adds a new symbol into an existing or a new type.
 
                                        var containingSymbolKey = SymbolKey.Create(newContainingType, cancellationToken);
                                        oldContainingType = containingSymbolKey.Resolve(oldCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol as INamedTypeSymbol;
 
                                        if (oldContainingType != null && !CanAddNewMember(newSymbol, capabilities, cancellationToken))
                                        {
                                            diagnostics.Add(new RudeEditDiagnostic(
                                                RudeEditKind.InsertNotSupportedByRuntime,
                                                GetDiagnosticSpan(newDeclaration, EditKind.Insert),
                                                newDeclaration,
                                                arguments: new[] { GetDisplayName(newDeclaration, EditKind.Insert) }));
                                        }
 
                                        // Check rude edits for each member even if it is inserted into a new type.
                                        ReportInsertedMemberSymbolRudeEdits(diagnostics, newSymbol, newDeclaration, insertingIntoExistingContainingType: oldContainingType != null);
 
                                        if (oldContainingType == null)
                                        {
                                            // Insertion of a new symbol into a new type.
                                            // We'll produce a single insert edit for the entire type.
                                            continue;
                                        }
 
                                        // Report rude edits for changes to data member changes of a type with an explicit layout.
                                        // We disallow moving a data member of a partial type with explicit layout even when it actually does not change the layout.
                                        // We could compare the exact order of the members but the scenario is unlikely to occur.
                                        ReportTypeLayoutUpdateRudeEdits(diagnostics, newSymbol, newDeclaration, newModel, ref lazyLayoutAttribute);
 
                                        // If a property or field is added to a record then the implicit constructors change,
                                        // and we need to mark a number of other synthesized members as having changed.
                                        if (newSymbol is IPropertySymbol or IFieldSymbol && newContainingType.IsRecord)
                                        {
                                            DeferConstructorEdit(oldContainingType, newContainingType, newDeclaration, syntaxMap, newSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits);
 
                                            AddEditsForSynthesizedRecordMembers(newCompilation, newContainingType, semanticEdits, cancellationToken);
                                        }
                                    }
                                    else
                                    {
                                        // adds a new top-level type, or a global statement where none existed before, which is
                                        // therefore inserting the <Program>$ type
                                        Contract.ThrowIfFalse(newSymbol is INamedTypeSymbol || IsGlobalMain(newSymbol));
 
                                        if (!capabilities.Grant(EditAndContinueCapabilities.NewTypeDefinition))
                                        {
                                            diagnostics.Add(new RudeEditDiagnostic(
                                                RudeEditKind.InsertNotSupportedByRuntime,
                                                GetDiagnosticSpan(newDeclaration, EditKind.Insert),
                                                newDeclaration,
                                                arguments: new[] { GetDisplayName(newDeclaration, EditKind.Insert) }));
                                        }
 
                                        oldContainingType = null;
                                        ReportInsertedMemberSymbolRudeEdits(diagnostics, newSymbol, newDeclaration, insertingIntoExistingContainingType: false);
                                    }
 
                                    var isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(newDeclaration);
                                    if (isConstructorWithMemberInitializers || IsDeclarationWithInitializer(newDeclaration))
                                    {
                                        Contract.ThrowIfNull(newContainingType);
                                        Contract.ThrowIfNull(oldContainingType);
 
                                        // TODO (bug https://github.com/dotnet/roslyn/issues/2504)
                                        if (isConstructorWithMemberInitializers &&
                                            editKind == SemanticEditKind.Insert &&
                                            IsPartial(newContainingType) &&
                                            HasMemberInitializerContainingLambda(oldContainingType, newSymbol.IsStatic, cancellationToken))
                                        {
                                            // rude edit: Adding a constructor to a type with a field or property initializer that contains an anonymous function
                                            diagnostics.Add(new RudeEditDiagnostic(RudeEditKind.InsertConstructorToTypeWithInitializersWithLambdas, GetDiagnosticSpan(newDeclaration, EditKind.Insert)));
                                            break;
                                        }
 
                                        DeferConstructorEdit(oldContainingType, newContainingType, newDeclaration, syntaxMap, newSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits);
 
                                        if (isConstructorWithMemberInitializers)
                                        {
                                            processedSymbols.Remove(newSymbol);
                                        }
 
                                        if (isConstructorWithMemberInitializers || editKind == SemanticEditKind.Update)
                                        {
                                            // Don't add a separate semantic edit.
                                            // Edits of data members with initializers and constructors that emit initializers will be aggregated and added later.
                                            continue;
                                        }
 
                                        // A semantic edit to create the field/property is gonna be added.
                                        Contract.ThrowIfFalse(editKind == SemanticEditKind.Insert);
                                    }
                                }
 
                                break;
 
                            case EditKind.Update:
                                {
                                    Contract.ThrowIfNull(oldModel);
                                    Contract.ThrowIfNull(newModel);
                                    Contract.ThrowIfNull(oldSymbol);
                                    Contract.ThrowIfNull(newSymbol);
 
                                    editKind = SemanticEditKind.Update;
                                    syntaxMap = null;
 
                                    // Partial type declarations and their type parameters.
                                    if (oldSymbol.DeclaringSyntaxReferences.Length != 1 && newSymbol.DeclaringSyntaxReferences.Length != 1)
                                    {
                                        break;
                                    }
 
                                    Contract.ThrowIfNull(oldDeclaration);
                                    Contract.ThrowIfNull(newDeclaration);
 
                                    if (IsRudeEditDueToPrimaryConstructor(oldSymbol, cancellationToken) ||
                                        IsRudeEditDueToPrimaryConstructor(newSymbol, cancellationToken))
                                    {
                                        // https://github.com/dotnet/roslyn/issues/67108: Disable edits for now
                                        diagnostics.Add(new RudeEditDiagnostic(RudeEditKind.Update, GetDiagnosticSpan(newDeclaration, EditKind.Update),
                                                                               newDeclaration, new[] { GetDisplayName(newDeclaration, EditKind.Update) }));
                                        continue;
                                    }
 
                                    var oldBody = TryGetDeclarationBody(oldDeclaration);
                                    if (oldBody != null)
                                    {
                                        var newBody = TryGetDeclarationBody(newDeclaration);
 
                                        AnalyzeChangedMemberBody(
                                            oldDeclaration,
                                            newDeclaration,
                                            oldBody,
                                            newBody,
                                            oldModel,
                                            newModel,
                                            oldSymbol,
                                            newSymbol,
                                            newText,
                                            oldActiveStatements,
                                            newActiveStatementSpans,
                                            capabilities,
                                            newActiveStatements,
                                            newExceptionRegions,
                                            diagnostics,
                                            out syntaxMap,
                                            cancellationToken);
                                    }
 
                                    // If a constructor changes from including initializers to not including initializers
                                    // we don't need to aggregate syntax map from all initializers for the constructor update semantic edit.
                                    var isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(newDeclaration);
                                    var isDeclarationWithInitializer = IsDeclarationWithInitializer(oldDeclaration) || IsDeclarationWithInitializer(newDeclaration);
 
                                    if (isConstructorWithMemberInitializers || isDeclarationWithInitializer)
                                    {
                                        if (isConstructorWithMemberInitializers)
                                        {
                                            processedSymbols.Remove(newSymbol);
                                        }
 
                                        if (isDeclarationWithInitializer)
                                        {
                                            AnalyzeSymbolUpdate(oldSymbol, newSymbol, edit.NewNode, newCompilation, editScript.Match, capabilities, diagnostics, semanticEdits, syntaxMap, processedSymbols, cancellationToken);
                                        }
 
                                        DeferConstructorEdit(oldSymbol.ContainingType, newSymbol.ContainingType, newDeclaration, syntaxMap, newSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits);
 
                                        // Don't add a separate semantic edit.
                                        // Updates of data members with initializers and constructors that emit initializers will be aggregated and added later.
                                        continue;
                                    }
                                }
 
                                break;
 
                            default:
                                throw ExceptionUtilities.UnexpectedValue(edit.Kind);
                        }
 
                        Contract.ThrowIfFalse(editKind is SemanticEditKind.Update or SemanticEditKind.Insert);
 
                        if (editKind == SemanticEditKind.Update)
                        {
                            Contract.ThrowIfNull(oldSymbol);
 
                            AnalyzeSymbolUpdate(oldSymbol, newSymbol, edit.NewNode, newCompilation, editScript.Match, capabilities, diagnostics, semanticEdits, syntaxMap, processedSymbols, cancellationToken);
 
                            if (newSymbol is INamedTypeSymbol or IFieldSymbol or IParameterSymbol or ITypeParameterSymbol)
                            {
                                continue;
                            }
 
                            // For renames where the symbol allows deletion, we don't create an update edit, we create a delete
                            // and an add. During emit an empty body will be created for the old name.
                            var createDeleteAndInsertEdits = oldSymbol.Name != newSymbol.Name;
 
                            // When a methods parameters are reordered or there is an insert or an add, we need to handle things differently
                            if (oldSymbol is IMethodSymbol oldMethod &&
                                newSymbol is IMethodSymbol newMethod)
                            {
                                // For inserts and deletes, the edits for the parameter itself will do the work
                                if (oldMethod.Parameters.Length != newMethod.Parameters.Length)
                                {
                                    continue;
                                }
 
                                // For reordering of parameters we need to report insert and delete edits, but we also need to account for
                                // renames if the runtime doesn't support it. We track this with a syntax node that we can use to report
                                // the rude edit.
                                IParameterSymbol? renamedParameter = null;
                                for (var i = 0; i < oldMethod.Parameters.Length; i++)
                                {
                                    var rudeEditKind = RudeEditKind.None;
                                    var hasParameterTypeChange = false;
                                    var unused = false;
                                    AnalyzeParameterType(oldMethod.Parameters[i], newMethod.Parameters[i], capabilities, ref rudeEditKind, ref unused, ref hasParameterTypeChange, cancellationToken);
 
                                    createDeleteAndInsertEdits |= hasParameterTypeChange;
                                    renamedParameter ??= oldMethod.Parameters[i].Name != newMethod.Parameters[i].Name ? newMethod.Parameters[i] : null;
                                }
 
                                if (!createDeleteAndInsertEdits && renamedParameter is not null && !capabilities.Grant(EditAndContinueCapabilities.UpdateParameters))
                                {
                                    processedSymbols.Add(renamedParameter);
                                    ReportUpdateRudeEdit(diagnostics, RudeEditKind.RenamingNotSupportedByRuntime, renamedParameter, GetRudeEditDiagnosticNode(renamedParameter, cancellationToken), cancellationToken);
                                    continue;
                                }
                            }
 
                            // Sometimes when members are moved between documents in partial classes, they can appear as renames,
                            // so we also check that the old symbol can't be resolved in the new compilation
                            if (createDeleteAndInsertEdits &&
                                AllowsDeletion(oldSymbol) &&
                                CanAddNewMember(oldSymbol, capabilities, cancellationToken) &&
                                SymbolKey.Create(oldSymbol, cancellationToken).Resolve(newCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol is null)
                            {
                                Contract.ThrowIfNull(oldDeclaration);
                                var activeStatementIndices = GetOverlappingActiveStatements(oldDeclaration, oldActiveStatements);
                                if (activeStatementIndices.Any())
                                {
                                    Contract.ThrowIfNull(newDeclaration);
                                    AddRudeUpdateAroundActiveStatement(diagnostics, newDeclaration);
                                }
                                else
                                {
                                    var containingSymbolKey = SymbolKey.Create(oldSymbol.ContainingType, cancellationToken);
 
                                    AddMemberOrAssociatedMemberSemanticEdits(semanticEdits, SemanticEditKind.Delete, oldSymbol, containingSymbolKey, syntaxMap, partialType: null, processedSymbols, cancellationToken);
                                    AddMemberOrAssociatedMemberSemanticEdits(semanticEdits, SemanticEditKind.Insert, newSymbol, containingSymbolKey: null, syntaxMap,
                                        partialType: IsPartialEdit(oldSymbol, newSymbol, editScript.Match.OldRoot.SyntaxTree, editScript.Match.NewRoot.SyntaxTree) ? symbolKey : null, processedSymbols,
                                        cancellationToken);
                                }
 
                                continue;
                            }
 
                            if (newSymbol is IPropertySymbol or IEventSymbol)
                            {
                                continue;
                            }
                        }
 
                        semanticEdits.Add(new SemanticEditInfo(editKind, symbolKey, syntaxMap, syntaxMapTree: null,
                            IsPartialEdit(oldSymbol, newSymbol, editScript.Match.OldRoot.SyntaxTree, editScript.Match.NewRoot.SyntaxTree) ? symbolKey : null));
                    }
                }
 
                foreach (var (oldEditNode, newEditNode, diagnosticSpan) in triviaEdits)
                {
                    Contract.ThrowIfNull(oldModel);
                    Contract.ThrowIfNull(newModel);
 
                    foreach (var (oldSymbol, newSymbol, editKind) in GetSymbolEdits(EditKind.Update, oldEditNode, newEditNode, oldModel, newModel, editMap, cancellationToken))
                    {
                        // Trivia edits are only calculated for member bodies and each member has a symbol.
                        Contract.ThrowIfNull(newSymbol);
                        Contract.ThrowIfNull(oldSymbol);
 
                        if (!processedSymbols.Add(newSymbol))
                        {
                            // symbol already processed
                            continue;
                        }
 
                        var (oldDeclaration, newDeclaration) = GetSymbolDeclarationNodes(oldSymbol, newSymbol, oldEditNode, newEditNode);
                        Contract.ThrowIfNull(oldDeclaration);
                        Contract.ThrowIfNull(newDeclaration);
 
                        var oldContainingType = oldSymbol.ContainingType;
                        var newContainingType = newSymbol.ContainingType;
                        Contract.ThrowIfNull(oldContainingType);
                        Contract.ThrowIfNull(newContainingType);
 
                        if (IsReloadable(oldContainingType))
                        {
                            if (processedSymbols.Add(newContainingType))
                            {
                                if (capabilities.Grant(EditAndContinueCapabilities.NewTypeDefinition))
                                {
                                    var containingTypeSymbolKey = SymbolKey.Create(oldContainingType, cancellationToken);
                                    semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Replace, containingTypeSymbolKey, syntaxMap: null, syntaxMapTree: null,
                                        IsPartialEdit(oldContainingType, newContainingType, editScript.Match.OldRoot.SyntaxTree, editScript.Match.NewRoot.SyntaxTree) ? containingTypeSymbolKey : null));
                                }
                                else
                                {
                                    ReportUpdateRudeEdit(diagnostics, RudeEditKind.ChangingReloadableTypeNotSupportedByRuntime, newContainingType, newDeclaration, cancellationToken);
                                }
                            }
 
                            continue;
                        }
 
                        // We need to provide syntax map to the compiler if the member is active (see member update above):
                        var isActiveMember =
                            GetOverlappingActiveStatements(oldDeclaration, oldActiveStatements).Any() ||
                            IsStateMachineMethod(oldDeclaration) ||
                            ContainsLambda(oldDeclaration);
 
                        var syntaxMap = isActiveMember ? CreateSyntaxMapForEquivalentNodes(oldDeclaration, newDeclaration) : null;
 
                        // only trivia changed:
                        Contract.ThrowIfFalse(IsConstructorWithMemberInitializers(oldDeclaration) == IsConstructorWithMemberInitializers(newDeclaration));
                        Contract.ThrowIfFalse(IsDeclarationWithInitializer(oldDeclaration) == IsDeclarationWithInitializer(newDeclaration));
 
                        var isConstructorWithMemberInitializers = IsConstructorWithMemberInitializers(newDeclaration);
                        var isDeclarationWithInitializer = IsDeclarationWithInitializer(newDeclaration);
 
                        if (isConstructorWithMemberInitializers || isDeclarationWithInitializer)
                        {
                            // TODO: only create syntax map if any field initializers are active/contain lambdas or this is a partial type
                            syntaxMap ??= CreateSyntaxMapForEquivalentNodes(oldDeclaration, newDeclaration);
 
                            if (isConstructorWithMemberInitializers)
                            {
                                processedSymbols.Remove(newSymbol);
                            }
 
                            DeferConstructorEdit(oldContainingType, newContainingType, newDeclaration, syntaxMap, newSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits);
 
                            // Don't add a separate semantic edit.
                            // Updates of data members with initializers and constructors that emit initializers will be aggregated and added later.
                            continue;
                        }
 
                        ReportMemberBodyUpdateRudeEdits(diagnostics, newDeclaration, diagnosticSpan);
 
                        // updating generic methods and types
                        if (InGenericContext(oldSymbol, out var oldIsGenericMethod))
                        {
                            var rudeEdit = oldIsGenericMethod ? RudeEditKind.GenericMethodTriviaUpdate : RudeEditKind.GenericTypeTriviaUpdate;
                            diagnostics.Add(new RudeEditDiagnostic(rudeEdit, diagnosticSpan, newEditNode, new[] { GetDisplayName(newEditNode) }));
                            continue;
                        }
 
                        var symbolKey = SymbolKey.Create(newSymbol, cancellationToken);
                        semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Update, symbolKey, syntaxMap, syntaxMapTree: null,
                            IsPartialEdit(oldSymbol, newSymbol, editScript.Match.OldRoot.SyntaxTree, editScript.Match.NewRoot.SyntaxTree) ? symbolKey : null));
                    }
                }
 
                if (instanceConstructorEdits != null)
                {
                    AddConstructorEdits(
                        instanceConstructorEdits,
                        editScript.Match,
                        oldModel,
                        oldCompilation,
                        newCompilation,
                        processedSymbols,
                        capabilities,
                        isStatic: false,
                        semanticEdits,
                        diagnostics,
                        cancellationToken);
                }
 
                if (staticConstructorEdits != null)
                {
                    AddConstructorEdits(
                        staticConstructorEdits,
                        editScript.Match,
                        oldModel,
                        oldCompilation,
                        newCompilation,
                        processedSymbols,
                        capabilities,
                        isStatic: true,
                        semanticEdits,
                        diagnostics,
                        cancellationToken);
                }
            }
            finally
            {
                instanceConstructorEdits?.Free();
                staticConstructorEdits?.Free();
            }
 
            return semanticEdits.Distinct(SemanticEditInfoComparer.Instance).ToImmutableArray();
 
            // If the symbol has a single declaring reference use its syntax node for further analysis.
            // Some syntax edits may not be directly associated with the declarations.
            // For example, in VB an update to AsNew clause of a multi-variable field declaration results in update to multiple symbols associated 
            // with the variable declaration. But we need to analyse each symbol's modified identifier separately.
            (SyntaxNode? oldDeclaration, SyntaxNode? newDeclaration) GetSymbolDeclarationNodes(ISymbol? oldSymbol, ISymbol? newSymbol, SyntaxNode? oldNode, SyntaxNode? newNode)
            {
                return (
                    (oldSymbol != null && oldSymbol.DeclaringSyntaxReferences.Length == 1) ?
                        GetSymbolDeclarationSyntax(oldSymbol.DeclaringSyntaxReferences.Single(), cancellationToken) : oldNode,
                    (newSymbol != null && newSymbol.DeclaringSyntaxReferences.Length == 1) ?
                        GetSymbolDeclarationSyntax(newSymbol.DeclaringSyntaxReferences.Single(), cancellationToken) : newNode);
            }
        }
 
        /// <summary>
        /// Adds a delete and insert edit for the old and new symbols that have had a parameter inserted or deleted
        /// </summary>
        /// <param name="containingSymbol">The symbol that contains the parameter that has been added or deleted (either IMethodSymbol or IPropertySymbol)</param>
        /// <param name="otherModel">The semantic model from the old compilation, for parameter inserts, or new compilation, for deletes</param>
        /// <param name="notSupportedByRuntime">Whether the edit should be rejected because the runtime doesn't support inserting new methods. Otherwise a normal rude edit is appropriate.</param>
        /// <returns>Returns whether semantic edits were added, or if not then a rude edit should be created</returns>
        private bool TryAddParameterInsertOrDeleteEdits(ArrayBuilder<SemanticEditInfo> semanticEdits, ISymbol containingSymbol, SemanticModel? otherModel, EditAndContinueCapabilitiesGrantor capabilities, Func<SyntaxNode, SyntaxNode?>? syntaxMap, EditScript<SyntaxNode> editScript, HashSet<ISymbol> processedSymbols, CancellationToken cancellationToken, out bool notSupportedByRuntime)
        {
            Debug.Assert(containingSymbol is IPropertySymbol or IMethodSymbol);
 
            notSupportedByRuntime = false;
 
            // Since we're inserting (or deleting) a parameter node, oldSymbol (or newSymbol) would have been null,
            // and a symbolkey won't map to the other compilation because the parameters are different, so we have to go back to the edit map
            // to find the declaration that contains the parameter, and its partner, and then its symbol, so we need to be sure we can get
            // to syntax, and have a semantic model to get back to symbols.
            if (otherModel is null ||
                containingSymbol.DeclaringSyntaxReferences.Length != 1)
            {
                return false;
            }
 
            // We can ignore parameter inserts and deletes for partial method definitions, as we'll report them on the implementation.
            // We return true here so no rude edit is raised.
            if (containingSymbol is IMethodSymbol { IsPartialDefinition: true })
            {
                return true;
            }
 
            // We don't support delegate parameters
            if (containingSymbol.ContainingType.IsDelegateType())
            {
                return false;
            }
 
            // Find the node that matches this declaration
            SyntaxNode otherContainingNode;
            var containingNode = containingSymbol.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken);
            if (editScript.Match.TryGetOldNode(containingNode, out var oldNode))
            {
                otherContainingNode = oldNode;
            }
            else if (editScript.Match.TryGetNewNode(containingNode, out var newNode))
            {
                otherContainingNode = newNode;
            }
            else
            {
                return false;
            }
 
            var otherContainingSymbol = otherModel.GetDeclaredSymbol(otherContainingNode, cancellationToken);
            if (otherContainingSymbol is null || !AllowsDeletion(otherContainingSymbol))
            {
                return false;
            }
 
            // Now we can work out which is the old and which is the new, depending on which map we found
            // the match in
            var oldSymbol = (otherContainingNode == oldNode) ? otherContainingSymbol : containingSymbol;
            var newSymbol = (otherContainingNode == oldNode) ? containingSymbol : otherContainingSymbol;
 
            if (!CanAddNewMember(oldSymbol, capabilities, cancellationToken))
            {
                notSupportedByRuntime = true;
                return false;
            }
 
            var containingSymbolKey = SymbolKey.Create(oldSymbol.ContainingType, cancellationToken);
 
            AddMemberOrAssociatedMemberSemanticEdits(semanticEdits, SemanticEditKind.Delete, oldSymbol, containingSymbolKey, syntaxMap, partialType: null, processedSymbols, cancellationToken);
 
            var symbolKey = SymbolKey.Create(newSymbol, cancellationToken);
            AddMemberOrAssociatedMemberSemanticEdits(semanticEdits, SemanticEditKind.Insert, newSymbol, containingSymbolKey: null, syntaxMap,
                partialType: IsPartialEdit(oldSymbol, newSymbol, editScript.Match.OldRoot.SyntaxTree, editScript.Match.NewRoot.SyntaxTree) ? symbolKey : null, processedSymbols,
                cancellationToken);
 
            return true;
        }
 
        /// <summary>
        /// Returns whether or not the specified symbol can be deleted by the user. Normally deletes are a rude edit
        /// but for some kinds of symbols we allow deletes, and synthesize an update to an empty method body during
        /// emit.
        /// </summary>
        private static bool AllowsDeletion(ISymbol symbol)
        {
            // We don't currently allow deleting virtual or abstract methods, because if those are in the middle of
            // an inheritance chain then throwing a missing method exception is not expected
            if (symbol.GetSymbolModifiers() is not { IsVirtual: false, IsAbstract: false, IsOverride: false })
                return false;
 
            // Extern methods can't be deleted
            if (symbol.IsExtern)
                return false;
 
            // We don't allow deleting members from interfaces etc. only normal classes and structs
            if (symbol.ContainingType is not { TypeKind: TypeKind.Class or TypeKind.Struct })
                return false;
 
            // We store the containing symbol in NewSymbol of the edit for later use.
            if (symbol is IMethodSymbol
                {
                    MethodKind:
                        MethodKind.Ordinary or
                        MethodKind.Constructor or
                        MethodKind.EventAdd or
                        MethodKind.EventRemove or
                        MethodKind.EventRaise or
                        MethodKind.Conversion or
                        MethodKind.UserDefinedOperator or
                        MethodKind.PropertyGet or
                        MethodKind.PropertySet
                })
            {
                return true;
            }
 
            return symbol is IPropertySymbol or IEventSymbol;
        }
 
        /// <summary>
        /// Add semantic edits for the specified symbol, or the associated members of the specified symbol,
        /// for example, edits for each accessor if a property symbol is passed in.
        /// </summary>
        private static void AddMemberOrAssociatedMemberSemanticEdits(ArrayBuilder<SemanticEditInfo> semanticEdits, SemanticEditKind editKind, ISymbol symbol, SymbolKey? containingSymbolKey, Func<SyntaxNode, SyntaxNode?>? syntaxMap, SymbolKey? partialType, HashSet<ISymbol>? processedSymbols, CancellationToken cancellationToken)
        {
            Debug.Assert(symbol is IMethodSymbol or IPropertySymbol or IEventSymbol);
 
            // We store the containing symbol in NewSymbol of the edit for later use.
            if (symbol is IMethodSymbol)
            {
                AddEdit(symbol);
            }
            else if (symbol is IPropertySymbol propertySymbol)
            {
                if (editKind == SemanticEditKind.Delete)
                {
                    // When deleting a property, we delete the get and set method individually, because we actually just update them
                    // to be throwing
                    AddEdit(propertySymbol.GetMethod);
                    AddEdit(propertySymbol.SetMethod);
                }
                else
                {
                    Contract.ThrowIfNull(processedSymbols);
 
                    // When inserting a new property as part of a property change however, we need to insert the entire property, so
                    // that the field, property and method semantics metadata tables can all be updated if/as necessary
                    AddEdit(propertySymbol);
                    if (propertySymbol.GetMethod is not null)
                    {
                        processedSymbols.Add(propertySymbol.GetMethod);
                    }
 
                    if (propertySymbol.SetMethod is not null)
                    {
                        processedSymbols.Add(propertySymbol.SetMethod);
                    }
                }
            }
            else if (symbol is IEventSymbol eventSymbol)
            {
                if (editKind == SemanticEditKind.Delete)
                {
                    AddEdit(eventSymbol.AddMethod);
                    AddEdit(eventSymbol.RemoveMethod);
                    AddEdit(eventSymbol.RaiseMethod);
                }
                else
                {
                    Contract.ThrowIfNull(processedSymbols);
 
                    AddEdit(eventSymbol);
                    if (eventSymbol.AddMethod is not null)
                    {
                        processedSymbols.Add(eventSymbol.AddMethod);
                    }
 
                    if (eventSymbol.RemoveMethod is not null)
                    {
                        processedSymbols.Add(eventSymbol.RemoveMethod);
                    }
 
                    if (eventSymbol.RaiseMethod is not null)
                    {
                        processedSymbols.Add(eventSymbol.RaiseMethod);
                    }
                }
            }
 
            void AddEdit(ISymbol? symbol)
            {
                if (symbol is null)
                    return;
 
                var symbolKey = SymbolKey.Create(symbol, cancellationToken);
                semanticEdits.Add(new SemanticEditInfo(editKind, symbolKey, syntaxMap, syntaxMapTree: null, partialType, deletedSymbolContainer: containingSymbolKey));
            }
        }
 
        private ImmutableArray<(ISymbol? oldSymbol, ISymbol? newSymbol, EditKind editKind)> GetNamespaceSymbolEdits(
            SemanticModel oldModel,
            SemanticModel newModel,
            CancellationToken cancellationToken)
        {
            using var _1 = ArrayBuilder<(ISymbol? oldSymbol, ISymbol? newSymbol, EditKind editKind)>.GetInstance(out var builder);
 
            // Maps type name and arity to indices in builder array. Used to convert delete & insert edits to a move edit.
            // If multiple types with the same name and arity are deleted we match the inserted types in the order they were declared
            // and remove the index from the array, until no mathcing deleted type is found in the array of indices.
            using var _2 = PooledDictionary<(string name, int arity), ArrayBuilder<int>>.GetInstance(out var deletedTypes);
 
            // used to avoid duplicates due to partial declarations
            using var _3 = PooledHashSet<INamedTypeSymbol>.GetInstance(out var processedTypes);
 
            // Check that all top-level types declared in the old document are also declared in the new one.
            // Those that are not were either deleted or renamed.
 
            var oldRoot = oldModel.SyntaxTree.GetRoot(cancellationToken);
            foreach (var oldTypeDeclaration in GetTopLevelTypeDeclarations(oldRoot))
            {
                var oldType = (INamedTypeSymbol?)oldModel.GetDeclaredSymbol(oldTypeDeclaration, cancellationToken);
                Contract.ThrowIfNull(oldType);
 
                if (!processedTypes.Add(oldType))
                {
                    continue;
                }
 
                var newType = SymbolKey.Create(oldType, cancellationToken).Resolve(newModel.Compilation, ignoreAssemblyKey: true, cancellationToken).Symbol;
                if (newType == null)
                {
                    var key = (oldType.Name, oldType.Arity);
 
                    if (!deletedTypes.TryGetValue(key, out var indices))
                        deletedTypes.Add(key, indices = ArrayBuilder<int>.GetInstance());
 
                    indices.Add(builder.Count);
                    builder.Add((oldSymbol: oldType, newSymbol: null, EditKind.Delete));
                }
            }
 
            // reverse all indices:
            foreach (var (_, indices) in deletedTypes)
                indices.ReverseContents();
 
            processedTypes.Clear();
 
            // Check that all top-level types declared in the new document are also declared in the old one.
            // Those that are not were added.
 
            var newRoot = newModel.SyntaxTree.GetRoot(cancellationToken);
            foreach (var newTypeDeclaration in GetTopLevelTypeDeclarations(newRoot))
            {
                var newType = (INamedTypeSymbol?)newModel.GetDeclaredSymbol(newTypeDeclaration, cancellationToken);
                Contract.ThrowIfNull(newType);
 
                if (!processedTypes.Add(newType))
                {
                    continue;
                }
 
                var oldType = SymbolKey.Create(newType, cancellationToken).Resolve(oldModel.Compilation, ignoreAssemblyKey: true, cancellationToken).Symbol;
                if (oldType == null)
                {
                    // Check if a type with the same name and arity was also removed. If so treat it as a move.
                    if (deletedTypes.TryGetValue((newType.Name, newType.Arity), out var deletedTypeIndices) && deletedTypeIndices.Count > 0)
                    {
                        var deletedTypeIndex = deletedTypeIndices.Last();
                        deletedTypeIndices.RemoveLast();
 
                        builder[deletedTypeIndex] = (builder[deletedTypeIndex].oldSymbol, newType, EditKind.Move);
                    }
                    else
                    {
                        builder.Add((oldSymbol: null, newSymbol: newType, EditKind.Insert));
                    }
                }
            }
 
            // free all index array builders:
            foreach (var (_, indices) in deletedTypes)
                indices.Free();
 
            return builder.ToImmutable();
        }
 
        private static bool IsReloadable(INamedTypeSymbol type)
        {
            var current = type;
            while (current != null)
            {
                foreach (var attributeData in current.GetAttributes())
                {
                    // We assume that the attribute System.Runtime.CompilerServices.CreateNewOnMetadataUpdateAttribute, if it exists, is well formed.
                    // If not an error will be reported during EnC delta emit.
                    if (attributeData.AttributeClass is { Name: CreateNewOnMetadataUpdateAttributeName, ContainingNamespace: { Name: "CompilerServices", ContainingNamespace: { Name: "Runtime", ContainingNamespace.Name: "System" } } })
                    {
                        return true;
                    }
                }
 
                current = current.BaseType;
            }
 
            return false;
        }
 
        private sealed class SemanticEditInfoComparer : IEqualityComparer<SemanticEditInfo>
        {
            public static SemanticEditInfoComparer Instance = new();
 
            private static readonly IEqualityComparer<SymbolKey> s_symbolKeyComparer = SymbolKey.GetComparer();
 
            public bool Equals([AllowNull] SemanticEditInfo x, [AllowNull] SemanticEditInfo y)
            {
                // When we delete a symbol, it might have the same symbol key as the matching insert
                // edit that corresponds to it, for example if only the return type has changed, because
                // symbol key does not consider return types. To ensure that this doesn't break us
                // by incorrectly de-duping our two edits, we treat edits as equal only if their
                // deleted symbol containers are both null, or both not null.
                if (x.DeletedSymbolContainer is null != y.DeletedSymbolContainer is null)
                {
                    return false;
                }
 
                return s_symbolKeyComparer.Equals(x.Symbol, y.Symbol);
            }
 
            public int GetHashCode([DisallowNull] SemanticEditInfo obj)
                => obj.Symbol.GetHashCode();
        }
 
        private void ReportUpdatedSymbolDeclarationRudeEdits(
            ArrayBuilder<RudeEditDiagnostic> diagnostics,
            ISymbol oldSymbol,
            ISymbol newSymbol,
            SyntaxNode? newNode,
            Compilation newCompilation,
            EditAndContinueCapabilitiesGrantor capabilities,
            out bool hasGeneratedAttributeChange,
            out bool hasGeneratedReturnTypeAttributeChange,
            out bool hasParameterRename,
            out bool hasParameterTypeChange,
            out bool hasReturnTypeChange,
            CancellationToken cancellationToken)
        {
            var rudeEdit = RudeEditKind.None;
 
            hasGeneratedAttributeChange = false;
            hasGeneratedReturnTypeAttributeChange = false;
            hasParameterRename = false;
            hasParameterTypeChange = false;
            hasReturnTypeChange = false;
 
            if (oldSymbol.Kind != newSymbol.Kind)
            {
                rudeEdit = (oldSymbol.Kind == SymbolKind.Field || newSymbol.Kind == SymbolKind.Field) ? RudeEditKind.FieldKindUpdate : RudeEditKind.Update;
            }
            else if (oldSymbol.Name != newSymbol.Name)
            {
                if (oldSymbol is IParameterSymbol && newSymbol is IParameterSymbol)
                {
                    // We defer checking parameter renames until later, because if their types have also changed
                    // then we'll be emitting a new method, so it won't be a rename any more
                }
                else if (oldSymbol is IMethodSymbol oldMethod && newSymbol is IMethodSymbol newMethod)
                {
                    if (oldMethod.AssociatedSymbol != null && newMethod.AssociatedSymbol != null)
                    {
                        if (oldMethod.MethodKind != newMethod.MethodKind)
                        {
                            rudeEdit = RudeEditKind.AccessorKindUpdate;
                        }
                        else
                        {
                            // rude edit will be reported by the associated symbol
                            rudeEdit = RudeEditKind.None;
                        }
                    }
                    else if (oldMethod.MethodKind == MethodKind.Conversion)
                    {
                        rudeEdit = RudeEditKind.ModifiersUpdate;
                    }
                    else if (oldMethod.MethodKind == MethodKind.ExplicitInterfaceImplementation || newMethod.MethodKind == MethodKind.ExplicitInterfaceImplementation)
                    {
                        // Can't change from explicit to implicit interface implementation, or one interface to another
                        rudeEdit = RudeEditKind.Renamed;
                    }
                    else if (!AllowsDeletion(oldSymbol))
                    {
                        rudeEdit = RudeEditKind.Renamed;
                    }
                    else if (!CanAddNewMember(oldSymbol, capabilities, cancellationToken))
                    {
                        rudeEdit = RudeEditKind.RenamingNotSupportedByRuntime;
                    }
                }
                else if (oldSymbol is IPropertySymbol oldProperty && newSymbol is IPropertySymbol newProperty)
                {
                    if (!oldProperty.ExplicitInterfaceImplementations.IsEmpty || !newProperty.ExplicitInterfaceImplementations.IsEmpty)
                    {
                        // Can't change from explicit to implicit interface implementation, or one interface to another
                        rudeEdit = RudeEditKind.Renamed;
                    }
                    else if (!AllowsDeletion(oldSymbol))
                    {
                        rudeEdit = RudeEditKind.Renamed;
                    }
                    else if (!CanAddNewMember(oldSymbol, capabilities, cancellationToken))
                    {
                        rudeEdit = RudeEditKind.RenamingNotSupportedByRuntime;
                    }
                }
                else if (oldSymbol is IEventSymbol oldEvent && newSymbol is IEventSymbol newEvent)
                {
                    if (!oldEvent.ExplicitInterfaceImplementations.IsEmpty || !newEvent.ExplicitInterfaceImplementations.IsEmpty)
                    {
                        // Can't change from explicit to implicit interface implementation, or one interface to another
                        rudeEdit = RudeEditKind.Renamed;
                    }
                    else if (!AllowsDeletion(oldSymbol))
                    {
                        rudeEdit = RudeEditKind.Renamed;
                    }
                    else if (!CanAddNewMember(oldSymbol, capabilities, cancellationToken))
                    {
                        rudeEdit = RudeEditKind.RenamingNotSupportedByRuntime;
                    }
                }
                else
                {
                    rudeEdit = RudeEditKind.Renamed;
                }
            }
 
            if (oldSymbol.DeclaredAccessibility != newSymbol.DeclaredAccessibility)
            {
                rudeEdit = RudeEditKind.ChangingAccessibility;
            }
 
            if (oldSymbol.IsStatic != newSymbol.IsStatic ||
                oldSymbol.IsVirtual != newSymbol.IsVirtual ||
                oldSymbol.IsAbstract != newSymbol.IsAbstract ||
                oldSymbol.IsOverride != newSymbol.IsOverride ||
                oldSymbol.IsExtern != newSymbol.IsExtern)
            {
                // Do not report for accessors as the error will be reported on their associated symbol.
                if (oldSymbol is not IMethodSymbol { AssociatedSymbol: not null })
                {
                    rudeEdit = RudeEditKind.ModifiersUpdate;
                }
            }
 
            if (oldSymbol is IFieldSymbol oldField && newSymbol is IFieldSymbol newField)
            {
                if (oldField.IsConst != newField.IsConst ||
                    oldField.IsReadOnly != newField.IsReadOnly ||
                    oldField.IsVolatile != newField.IsVolatile)
                {
                    rudeEdit = RudeEditKind.ModifiersUpdate;
                }
 
                // Report rude edit for updating const fields and values of enums. 
                // The latter is only reported whne the enum underlying type does not change to avoid cascading rude edits.
                if (oldField.IsConst && newField.IsConst && !Equals(oldField.ConstantValue, newField.ConstantValue) &&
                    TypesEquivalent(oldField.ContainingType.EnumUnderlyingType, newField.ContainingType.EnumUnderlyingType, exact: false))
                {
                    rudeEdit = RudeEditKind.InitializerUpdate;
                }
 
                if (oldField.FixedSize != newField.FixedSize)
                {
                    rudeEdit = RudeEditKind.FixedSizeFieldUpdate;
                }
 
                AnalyzeType(oldField.Type, newField.Type, ref rudeEdit, ref hasGeneratedAttributeChange);
            }
            else if (oldSymbol is IMethodSymbol oldMethod && newSymbol is IMethodSymbol newMethod)
            {
                if (oldMethod.IsReadOnly != newMethod.IsReadOnly)
                {
                    rudeEdit = RudeEditKind.ModifiersUpdate;
                }
 
                if (oldMethod.IsInitOnly != newMethod.IsInitOnly)
                {
                    rudeEdit = RudeEditKind.AccessorKindUpdate;
                }
 
                // Consider: Generalize to compare P/Invokes regardless of how they are defined (using attribute or Declare)
                if (oldMethod.MethodKind == MethodKind.DeclareMethod || newMethod.MethodKind == MethodKind.DeclareMethod)
                {
                    var oldImportData = oldMethod.GetDllImportData();
                    var newImportData = newMethod.GetDllImportData();
                    if (oldImportData != null && newImportData != null)
                    {
                        // Declare method syntax can't change these.
                        Debug.Assert(oldImportData.BestFitMapping == newImportData.BestFitMapping ||
                                     oldImportData.CallingConvention == newImportData.CallingConvention ||
                                     oldImportData.ExactSpelling == newImportData.ExactSpelling ||
                                     oldImportData.SetLastError == newImportData.SetLastError ||
                                     oldImportData.ThrowOnUnmappableCharacter == newImportData.ThrowOnUnmappableCharacter);
 
                        if (oldImportData.ModuleName != newImportData.ModuleName)
                        {
                            rudeEdit = RudeEditKind.DeclareLibraryUpdate;
                        }
                        else if (oldImportData.EntryPointName != newImportData.EntryPointName)
                        {
                            rudeEdit = RudeEditKind.DeclareAliasUpdate;
                        }
                        else if (oldImportData.CharacterSet != newImportData.CharacterSet)
                        {
                            rudeEdit = RudeEditKind.ModifiersUpdate;
                        }
                    }
                    else if (oldImportData is null != newImportData is null)
                    {
                        rudeEdit = RudeEditKind.ModifiersUpdate;
                    }
                }
 
                // VB implements clause
                if (!oldMethod.ExplicitInterfaceImplementations.SequenceEqual(newMethod.ExplicitInterfaceImplementations, SymbolsEquivalent))
                {
                    rudeEdit = RudeEditKind.ImplementsClauseUpdate;
                }
 
                // VB handles clause
                if (!AreHandledEventsEqual(oldMethod, newMethod))
                {
                    rudeEdit = RudeEditKind.HandlesClauseUpdate;
                }
 
                // Check return type - do not report for accessors, their containing symbol will report the rude edits and attribute updates.
                if (rudeEdit == RudeEditKind.None && oldMethod.AssociatedSymbol == null && newMethod.AssociatedSymbol == null)
                {
                    AnalyzeReturnType(oldMethod, newMethod, capabilities, ref rudeEdit, ref hasGeneratedReturnTypeAttributeChange, ref hasReturnTypeChange, cancellationToken);
                }
            }
            else if (oldSymbol is INamedTypeSymbol oldType && newSymbol is INamedTypeSymbol newType)
            {
                if (oldType.TypeKind != newType.TypeKind ||
                    oldType.IsRecord != newType.IsRecord) // TODO: https://github.com/dotnet/roslyn/issues/51874
                {
                    rudeEdit = RudeEditKind.TypeKindUpdate;
                }
                else if (oldType.IsRefLikeType != newType.IsRefLikeType ||
                         oldType.IsReadOnly != newType.IsReadOnly)
                {
                    rudeEdit = RudeEditKind.ModifiersUpdate;
                }
 
                if (rudeEdit == RudeEditKind.None)
                {
                    AnalyzeBaseTypes(oldType, newType, ref rudeEdit, ref hasGeneratedAttributeChange);
 
                    if (oldType.DelegateInvokeMethod != null)
                    {
                        Contract.ThrowIfNull(newType.DelegateInvokeMethod);
                        AnalyzeReturnType(oldType.DelegateInvokeMethod, newType.DelegateInvokeMethod, capabilities, ref rudeEdit, ref hasGeneratedReturnTypeAttributeChange, ref hasReturnTypeChange, cancellationToken);
                    }
                }
            }
            else if (oldSymbol is IPropertySymbol oldProperty && newSymbol is IPropertySymbol newProperty)
            {
                AnalyzeReturnType(oldProperty, newProperty, capabilities, ref rudeEdit, ref hasGeneratedReturnTypeAttributeChange, ref hasReturnTypeChange, cancellationToken);
            }
            else if (oldSymbol is IEventSymbol oldEvent && newSymbol is IEventSymbol newEvent)
            {
                // "readonly" modifier can only be applied on the event itself, not on its accessors.
                if (oldEvent.AddMethod != null && newEvent.AddMethod != null && oldEvent.AddMethod.IsReadOnly != newEvent.AddMethod.IsReadOnly ||
                    oldEvent.RemoveMethod != null && newEvent.RemoveMethod != null && oldEvent.RemoveMethod.IsReadOnly != newEvent.RemoveMethod.IsReadOnly)
                {
                    rudeEdit = RudeEditKind.ModifiersUpdate;
                }
                else
                {
                    AnalyzeReturnType(oldEvent, newEvent, capabilities, ref rudeEdit, ref hasGeneratedReturnTypeAttributeChange, ref hasReturnTypeChange, cancellationToken);
                }
            }
            else if (oldSymbol is IParameterSymbol oldParameter && newSymbol is IParameterSymbol newParameter)
            {
                if (oldParameter.RefKind != newParameter.RefKind ||
                    oldParameter.IsParams != newParameter.IsParams ||
                    IsExtensionMethodThisParameter(oldParameter) != IsExtensionMethodThisParameter(newParameter))
                {
                    rudeEdit = RudeEditKind.ModifiersUpdate;
                }
                else if (oldParameter.HasExplicitDefaultValue != newParameter.HasExplicitDefaultValue ||
                         oldParameter.HasExplicitDefaultValue && !Equals(oldParameter.ExplicitDefaultValue, newParameter.ExplicitDefaultValue))
                {
                    rudeEdit = RudeEditKind.InitializerUpdate;
                }
                else
                {
                    AnalyzeParameterType(oldParameter, newParameter, capabilities, ref rudeEdit, ref hasGeneratedAttributeChange, ref hasParameterTypeChange, cancellationToken);
 
                    if (!hasParameterTypeChange && oldParameter.Name != newParameter.Name)
                    {
                        if (capabilities.Grant(EditAndContinueCapabilities.UpdateParameters))
                        {
                            hasParameterRename = true;
                        }
                        else
                        {
                            rudeEdit = RudeEditKind.RenamingNotSupportedByRuntime;
                        }
                    }
                }
            }
            else if (oldSymbol is ITypeParameterSymbol oldTypeParameter && newSymbol is ITypeParameterSymbol newTypeParameter)
            {
                AnalyzeTypeParameter(oldTypeParameter, newTypeParameter, ref rudeEdit, ref hasGeneratedAttributeChange);
            }
 
            // Do not report modifier update if type kind changed.
            if (rudeEdit == RudeEditKind.None && oldSymbol.IsSealed != newSymbol.IsSealed)
            {
                // Do not report for accessors as the error will be reported on their associated symbol.
                if (oldSymbol is not IMethodSymbol { AssociatedSymbol: not null })
                {
                    rudeEdit = RudeEditKind.ModifiersUpdate;
                }
            }
 
            if (rudeEdit != RudeEditKind.None)
            {
                ReportUpdateRudeEdit(diagnostics, rudeEdit, oldSymbol, newSymbol, newNode, newCompilation, cancellationToken);
            }
        }
 
        private static void AnalyzeType(ITypeSymbol oldType, ITypeSymbol newType, ref RudeEditKind rudeEdit, ref bool hasGeneratedAttributeChange, RudeEditKind rudeEditKind = RudeEditKind.TypeUpdate)
        {
            if (!TypesEquivalent(oldType, newType, exact: true))
            {
                if (TypesEquivalent(oldType, newType, exact: false))
                {
                    hasGeneratedAttributeChange = true;
                }
                else
                {
                    rudeEdit = rudeEditKind;
                }
            }
        }
 
        private static void AnalyzeBaseTypes(INamedTypeSymbol oldType, INamedTypeSymbol newType, ref RudeEditKind rudeEdit, ref bool hasGeneratedAttributeChange)
        {
            if (oldType.EnumUnderlyingType != null && newType.EnumUnderlyingType != null)
            {
                if (!TypesEquivalent(oldType.EnumUnderlyingType, newType.EnumUnderlyingType, exact: true))
                {
                    if (TypesEquivalent(oldType.EnumUnderlyingType, newType.EnumUnderlyingType, exact: false))
                    {
                        hasGeneratedAttributeChange = true;
                    }
                    else
                    {
                        rudeEdit = RudeEditKind.EnumUnderlyingTypeUpdate;
                    }
                }
            }
            else if (!BaseTypesEquivalent(oldType, newType, exact: true))
            {
                if (BaseTypesEquivalent(oldType, newType, exact: false))
                {
                    hasGeneratedAttributeChange = true;
                }
                else
                {
                    rudeEdit = RudeEditKind.BaseTypeOrInterfaceUpdate;
                }
            }
        }
 
        private void AnalyzeParameterType(
            IParameterSymbol oldParameter,
            IParameterSymbol newParameter,
            EditAndContinueCapabilitiesGrantor capabilities,
            ref RudeEditKind rudeEdit,
            ref bool hasGeneratedAttributeChange,
            ref bool hasParameterTypeChange,
            CancellationToken cancellationToken)
        {
            if (!ParameterTypesEquivalent(oldParameter, newParameter, exact: true))
            {
                if (ParameterTypesEquivalent(oldParameter, newParameter, exact: false))
                {
                    hasGeneratedAttributeChange = true;
                }
                else if (newParameter.ContainingType.IsDelegateType())
                {
                    // We don't allow changing parameter types in delegates
                    rudeEdit = RudeEditKind.TypeUpdate;
                }
                else if (AllowsDeletion(newParameter.ContainingSymbol))
                {
                    if (CanAddNewMember(newParameter.ContainingSymbol, capabilities, cancellationToken))
                    {
                        hasParameterTypeChange = true;
                    }
                    else
                    {
                        rudeEdit = RudeEditKind.ChangingTypeNotSupportedByRuntime;
                    }
                }
                else
                {
                    rudeEdit = RudeEditKind.TypeUpdate;
                }
            }
        }
 
        private static void AnalyzeTypeParameter(ITypeParameterSymbol oldParameter, ITypeParameterSymbol newParameter, ref RudeEditKind rudeEdit, ref bool hasGeneratedAttributeChange)
        {
            if (!TypeParameterConstraintsEquivalent(oldParameter, newParameter, exact: true))
            {
                if (TypeParameterConstraintsEquivalent(oldParameter, newParameter, exact: false))
                {
                    hasGeneratedAttributeChange = true;
                }
                else
                {
                    rudeEdit = (oldParameter.Variance != newParameter.Variance) ? RudeEditKind.VarianceUpdate : RudeEditKind.ChangingConstraints;
                }
            }
        }
 
        private void AnalyzeReturnType(IMethodSymbol oldMethod, IMethodSymbol newMethod, EditAndContinueCapabilitiesGrantor capabilities, ref RudeEditKind rudeEdit, ref bool hasGeneratedReturnTypeAttributeChange, ref bool hasReturnTypeChange, CancellationToken cancellationToken)
        {
            if (!ReturnTypesEquivalent(oldMethod, newMethod, exact: true))
            {
                if (ReturnTypesEquivalent(oldMethod, newMethod, exact: false))
                {
                    hasGeneratedReturnTypeAttributeChange = true;
                }
                else if (IsGlobalMain(oldMethod) || IsGlobalMain(newMethod))
                {
                    rudeEdit = RudeEditKind.ChangeImplicitMainReturnType;
                }
                else if (oldMethod.ContainingType.IsDelegateType())
                {
                    rudeEdit = RudeEditKind.TypeUpdate;
                }
                else if (AllowsDeletion(newMethod))
                {
                    if (CanAddNewMember(newMethod, capabilities, cancellationToken))
                    {
                        hasReturnTypeChange = true;
                    }
                    else
                    {
                        rudeEdit = RudeEditKind.ChangingTypeNotSupportedByRuntime;
                    }
                }
                else
                {
                    rudeEdit = RudeEditKind.TypeUpdate;
                }
            }
        }
 
        private void AnalyzeReturnType(IEventSymbol oldEvent, IEventSymbol newEvent, EditAndContinueCapabilitiesGrantor capabilities, ref RudeEditKind rudeEdit, ref bool hasGeneratedReturnTypeAttributeChange, ref bool hasReturnTypeChange, CancellationToken cancellationToken)
        {
            if (!ReturnTypesEquivalent(oldEvent, newEvent, exact: true))
            {
                if (ReturnTypesEquivalent(oldEvent, newEvent, exact: false))
                {
                    hasGeneratedReturnTypeAttributeChange = true;
                }
                else if (oldEvent.ContainingType.IsDelegateType())
                {
                    rudeEdit = RudeEditKind.TypeUpdate;
                }
                else if (AllowsDeletion(newEvent))
                {
                    if (CanAddNewMember(newEvent, capabilities, cancellationToken))
                    {
                        hasReturnTypeChange = true;
                    }
                    else
                    {
                        rudeEdit = RudeEditKind.ChangingTypeNotSupportedByRuntime;
                    }
                }
                else
                {
                    rudeEdit = RudeEditKind.TypeUpdate;
                }
            }
        }
 
        private void AnalyzeReturnType(IPropertySymbol oldProperty, IPropertySymbol newProperty, EditAndContinueCapabilitiesGrantor capabilities, ref RudeEditKind rudeEdit, ref bool hasGeneratedReturnTypeAttributeChange, ref bool hasReturnTypeChange, CancellationToken cancellationToken)
        {
            if (!ReturnTypesEquivalent(oldProperty, newProperty, exact: true))
            {
                if (ReturnTypesEquivalent(oldProperty, newProperty, exact: false))
                {
                    hasGeneratedReturnTypeAttributeChange = true;
                }
                else if (oldProperty.ContainingType.IsDelegateType())
                {
                    rudeEdit = RudeEditKind.TypeUpdate;
                }
                else if (AllowsDeletion(newProperty))
                {
                    if (CanAddNewMember(newProperty, capabilities, cancellationToken))
                    {
                        hasReturnTypeChange = true;
                    }
                    else
                    {
                        rudeEdit = RudeEditKind.ChangingTypeNotSupportedByRuntime;
                    }
                }
                else
                {
                    rudeEdit = RudeEditKind.TypeUpdate;
                }
            }
        }
 
        private static bool IsExtensionMethodThisParameter(IParameterSymbol parameter)
            => parameter is { Ordinal: 0, ContainingSymbol: IMethodSymbol { IsExtensionMethod: true } };
 
        private void AnalyzeSymbolUpdate(
            ISymbol oldSymbol,
            ISymbol newSymbol,
            SyntaxNode? newNode,
            Compilation newCompilation,
            Match<SyntaxNode> topMatch,
            EditAndContinueCapabilitiesGrantor capabilities,
            ArrayBuilder<RudeEditDiagnostic> diagnostics,
            ArrayBuilder<SemanticEditInfo> semanticEdits,
            Func<SyntaxNode, SyntaxNode?>? syntaxMap,
            HashSet<ISymbol>? processedSymbols,
            CancellationToken cancellationToken)
        {
            // TODO: fails in VB on delegate parameter https://github.com/dotnet/roslyn/issues/53337
            // Contract.ThrowIfFalse(newSymbol.IsImplicitlyDeclared == newDeclaration is null);
 
            ReportCustomAttributeRudeEdits(diagnostics, oldSymbol, newSymbol, newNode, newCompilation, capabilities, out var hasAttributeChange, out var hasReturnTypeAttributeChange, cancellationToken);
 
            ReportUpdatedSymbolDeclarationRudeEdits(diagnostics, oldSymbol, newSymbol, newNode, newCompilation, capabilities, out var hasGeneratedAttributeChange, out var hasGeneratedReturnTypeAttributeChange, out var hasParameterRename, out var hasParameterTypeChange, out var hasReturnTypeChange, cancellationToken);
            hasAttributeChange |= hasGeneratedAttributeChange;
            hasReturnTypeAttributeChange |= hasGeneratedReturnTypeAttributeChange;
 
            if (hasParameterRename || hasParameterTypeChange)
            {
                Debug.Assert(newSymbol is IParameterSymbol);
 
                // In VB, when the type of a custom event changes, the parameters on the add and remove handlers also change
                // but we can ignore them because we have already done what we need to the event declaration itself.
                if (newSymbol.ContainingSymbol is IMethodSymbol { AssociatedSymbol: IEventSymbol associatedSymbol } &&
                    processedSymbols?.Contains(associatedSymbol) == true)
                {
                    return;
                }
 
                AddParameterUpdateSemanticEdit(semanticEdits, (IParameterSymbol)oldSymbol, (IParameterSymbol)newSymbol, syntaxMap, reportDeleteAndInsertEdits: hasParameterTypeChange, processedSymbols, cancellationToken);
            }
            else if (hasReturnTypeChange)
            {
                var containingSymbolKey = SymbolKey.Create(oldSymbol.ContainingSymbol, cancellationToken);
                AddMemberOrAssociatedMemberSemanticEdits(semanticEdits, SemanticEditKind.Delete, oldSymbol, containingSymbolKey, syntaxMap, partialType: null, processedSymbols, cancellationToken);
                AddMemberOrAssociatedMemberSemanticEdits(semanticEdits, SemanticEditKind.Insert, newSymbol, containingSymbolKey: null, syntaxMap, partialType: null, processedSymbols, cancellationToken);
            }
            else if (hasAttributeChange || hasReturnTypeAttributeChange)
            {
                AddCustomAttributeSemanticEdits(semanticEdits, oldSymbol, newSymbol, topMatch, syntaxMap, hasAttributeChange, hasReturnTypeAttributeChange, cancellationToken);
            }
 
            // updating generic methods and types
            if (InGenericContext(oldSymbol, out var oldIsGenericMethod) || InGenericContext(newSymbol, out _))
            {
                var rudeEdit = oldIsGenericMethod ? RudeEditKind.GenericMethodUpdate : RudeEditKind.GenericTypeUpdate;
                ReportUpdateRudeEdit(diagnostics, rudeEdit, newSymbol, newNode, cancellationToken);
            }
        }
 
        private static void AddCustomAttributeSemanticEdits(
            ArrayBuilder<SemanticEditInfo> semanticEdits,
            ISymbol oldSymbol,
            ISymbol newSymbol,
            Match<SyntaxNode> topMatch,
            Func<SyntaxNode, SyntaxNode?>? syntaxMap,
            bool hasAttributeChange,
            bool hasReturnTypeAttributeChange,
            CancellationToken cancellationToken)
        {
            // Most symbol types will automatically have an edit added, so we just need to handle a few
            if (newSymbol is INamedTypeSymbol { DelegateInvokeMethod: not null and var newDelegateInvokeMethod } newDelegateType)
            {
                if (hasAttributeChange)
                {
                    semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Update, SymbolKey.Create(newDelegateType, cancellationToken), syntaxMap, syntaxMapTree: null, partialType: null));
                }
 
                if (hasReturnTypeAttributeChange)
                {
                    // attributes applied on return type of a delegate are applied to both Invoke and BeginInvoke methods
                    semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Update, SymbolKey.Create(newDelegateInvokeMethod, cancellationToken), syntaxMap, syntaxMapTree: null, partialType: null));
                    AddDelegateBeginInvokeEdit(semanticEdits, newDelegateType, syntaxMap, cancellationToken);
                }
            }
            else if (newSymbol is INamedTypeSymbol)
            {
                var symbolKey = SymbolKey.Create(newSymbol, cancellationToken);
                semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Update, symbolKey, syntaxMap, syntaxMapTree: null,
                    IsPartialEdit(oldSymbol, newSymbol, topMatch.OldRoot.SyntaxTree, topMatch.NewRoot.SyntaxTree) ? symbolKey : null));
            }
            else if (newSymbol is ITypeParameterSymbol)
            {
                var containingTypeSymbolKey = SymbolKey.Create(newSymbol.ContainingSymbol, cancellationToken);
                semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Update, containingTypeSymbolKey, syntaxMap, syntaxMapTree: null,
                    IsPartialEdit(oldSymbol.ContainingSymbol, newSymbol.ContainingSymbol, topMatch.OldRoot.SyntaxTree, topMatch.NewRoot.SyntaxTree) ? containingTypeSymbolKey : null));
            }
            else if (newSymbol is IFieldSymbol or IPropertySymbol or IEventSymbol)
            {
                semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Update, SymbolKey.Create(newSymbol, cancellationToken), syntaxMap, syntaxMapTree: null, partialType: null));
            }
            else if (newSymbol is IParameterSymbol newParameterSymbol)
            {
                AddParameterUpdateSemanticEdit(semanticEdits, (IParameterSymbol)oldSymbol, newParameterSymbol, syntaxMap, reportDeleteAndInsertEdits: false, processedSymbols: null, cancellationToken);
            }
        }
 
        private static void AddParameterUpdateSemanticEdit(ArrayBuilder<SemanticEditInfo> semanticEdits, IParameterSymbol oldParameterSymbol, IParameterSymbol newParameterSymbol, Func<SyntaxNode, SyntaxNode?>? syntaxMap, bool reportDeleteAndInsertEdits, HashSet<ISymbol>? processedSymbols, CancellationToken cancellationToken)
        {
            var newContainingSymbol = newParameterSymbol.ContainingSymbol;
 
            if (reportDeleteAndInsertEdits)
            {
                var oldContainingSymbol = oldParameterSymbol.ContainingSymbol;
                var containingSymbolKey = SymbolKey.Create(oldContainingSymbol.ContainingSymbol, cancellationToken);
                AddMemberOrAssociatedMemberSemanticEdits(semanticEdits, SemanticEditKind.Delete, oldContainingSymbol, containingSymbolKey, syntaxMap, partialType: null, processedSymbols, cancellationToken);
                AddMemberOrAssociatedMemberSemanticEdits(semanticEdits, SemanticEditKind.Insert, newContainingSymbol, containingSymbolKey: null, syntaxMap, partialType: null, processedSymbols, cancellationToken);
            }
            else
            {
                semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Update, SymbolKey.Create(newContainingSymbol, cancellationToken), syntaxMap, syntaxMapTree: null, partialType: null));
            }
 
            // attributes applied on parameters of a delegate are applied to both Invoke and BeginInvoke methods
            if (newContainingSymbol.ContainingSymbol is INamedTypeSymbol { TypeKind: TypeKind.Delegate } newContainingDelegateType)
            {
                Debug.Assert(reportDeleteAndInsertEdits == false);
                AddDelegateBeginInvokeEdit(semanticEdits, newContainingDelegateType, syntaxMap, cancellationToken);
            }
        }
 
        private static void AddDelegateBeginInvokeEdit(ArrayBuilder<SemanticEditInfo> semanticEdits, INamedTypeSymbol delegateType, Func<SyntaxNode, SyntaxNode?>? syntaxMap, CancellationToken cancellationToken)
        {
            Debug.Assert(semanticEdits != null);
 
            var beginInvokeMethod = delegateType.GetMembers("BeginInvoke").FirstOrDefault();
            if (beginInvokeMethod != null)
            {
                semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Update, SymbolKey.Create(beginInvokeMethod, cancellationToken), syntaxMap, syntaxMapTree: null, partialType: null));
            }
        }
 
        private void ReportCustomAttributeRudeEdits(
            ArrayBuilder<RudeEditDiagnostic> diagnostics,
            ISymbol oldSymbol,
            ISymbol newSymbol,
            SyntaxNode? newNode,
            Compilation newCompilation,
            EditAndContinueCapabilitiesGrantor capabilities,
            out bool hasAttributeChange,
            out bool hasReturnTypeAttributeChange,
            CancellationToken cancellationToken)
        {
            // This is the only case we care about whether to issue an edit or not, because this is the only case where types have their attributes checked
            // and types are the only things that would otherwise not have edits reported.
            hasAttributeChange = ReportCustomAttributeRudeEdits(diagnostics, oldSymbol.GetAttributes(), newSymbol.GetAttributes(), oldSymbol, newSymbol, newNode, newCompilation, capabilities, cancellationToken);
 
            hasReturnTypeAttributeChange = false;
 
            if (oldSymbol is IMethodSymbol oldMethod &&
                newSymbol is IMethodSymbol newMethod)
            {
                hasReturnTypeAttributeChange |= ReportCustomAttributeRudeEdits(diagnostics, oldMethod.GetReturnTypeAttributes(), newMethod.GetReturnTypeAttributes(), oldSymbol, newSymbol, newNode, newCompilation, capabilities, cancellationToken);
            }
            else if (oldSymbol is INamedTypeSymbol { DelegateInvokeMethod: not null and var oldInvokeMethod } &&
                     newSymbol is INamedTypeSymbol { DelegateInvokeMethod: not null and var newInvokeMethod })
            {
                hasReturnTypeAttributeChange |= ReportCustomAttributeRudeEdits(diagnostics, oldInvokeMethod.GetReturnTypeAttributes(), newInvokeMethod.GetReturnTypeAttributes(), oldSymbol, newSymbol, newNode, newCompilation, capabilities, cancellationToken);
            }
        }
 
        private bool ReportCustomAttributeRudeEdits(
            ArrayBuilder<RudeEditDiagnostic> diagnostics,
            ImmutableArray<AttributeData>? oldAttributes,
            ImmutableArray<AttributeData> newAttributes,
            ISymbol oldSymbol,
            ISymbol newSymbol,
            SyntaxNode? newNode,
            Compilation newCompilation,
            EditAndContinueCapabilitiesGrantor capabilities,
            CancellationToken cancellationToken)
        {
            using var _ = ArrayBuilder<AttributeData>.GetInstance(out var changedAttributes);
 
            FindChangedAttributes(oldAttributes, newAttributes, changedAttributes);
            if (oldAttributes.HasValue)
            {
                FindChangedAttributes(newAttributes, oldAttributes.Value, changedAttributes);
            }
 
            if (changedAttributes.Count == 0)
            {
                return false;
            }
 
            // If the runtime doesn't support changing attributes we don't need to check anything else
            if (!capabilities.Grant(EditAndContinueCapabilities.ChangeCustomAttributes))
            {
                ReportUpdateRudeEdit(diagnostics, RudeEditKind.ChangingAttributesNotSupportedByRuntime, oldSymbol, newSymbol, newNode, newCompilation, cancellationToken);
                return false;
            }
 
            // Even if the runtime supports attribute changes, only attributes stored in the CustomAttributes table are editable
            foreach (var attributeData in changedAttributes)
            {
                if (IsNonCustomAttribute(attributeData))
                {
                    var node = newNode ?? GetRudeEditDiagnosticNode(newSymbol, cancellationToken);
                    diagnostics.Add(new RudeEditDiagnostic(RudeEditKind.ChangingNonCustomAttribute, GetDiagnosticSpan(node, EditKind.Update), node, new[]
                    {
                        attributeData.AttributeClass!.Name,
                        GetDisplayName(newSymbol)
                    }));
 
                    return false;
                }
            }
 
            return true;
 
            static void FindChangedAttributes(ImmutableArray<AttributeData>? oldAttributes, ImmutableArray<AttributeData> newAttributes, ArrayBuilder<AttributeData> changedAttributes)
            {
                for (var i = 0; i < newAttributes.Length; i++)
                {
                    var newAttribute = newAttributes[i];
                    var oldAttribute = FindMatch(newAttribute, oldAttributes);
 
                    if (oldAttribute is null)
                    {
                        changedAttributes.Add(newAttribute);
                    }
                }
            }
 
            static AttributeData? FindMatch(AttributeData attribute, ImmutableArray<AttributeData>? oldAttributes)
            {
                if (!oldAttributes.HasValue)
                {
                    return null;
                }
 
                foreach (var match in oldAttributes.Value)
                {
                    if (SymbolEquivalenceComparer.Instance.Equals(match.AttributeClass, attribute.AttributeClass))
                    {
                        if (SymbolEquivalenceComparer.Instance.Equals(match.AttributeConstructor, attribute.AttributeConstructor) &&
                            match.ConstructorArguments.SequenceEqual(attribute.ConstructorArguments, TypedConstantComparer.Instance) &&
                            match.NamedArguments.SequenceEqual(attribute.NamedArguments, NamedArgumentComparer.Instance))
                        {
                            return match;
                        }
                    }
                }
 
                return null;
            }
 
            static bool IsNonCustomAttribute(AttributeData attribute)
            {
                return attribute.AttributeClass?.ToNameDisplayString() switch
                {
                    //
                    // This list comes from ShouldEmitAttribute in src\Compilers\CSharp\Portable\Symbols\Attributes\AttributeData.cs
                    // and src\Compilers\VisualBasic\Portable\Symbols\Attributes\AttributeData.vb
                    // TODO: Use a compiler API to get this information rather than hard coding a list: https://github.com/dotnet/roslyn/issues/53410
                    //
                    "System.CLSCompliantAttribute" => true,
                    "System.Diagnostics.CodeAnalysis.AllowNullAttribute" => true,
                    "System.Diagnostics.CodeAnalysis.DisallowNullAttribute" => true,
                    "System.Diagnostics.CodeAnalysis.MaybeNullAttribute" => true,
                    "System.Diagnostics.CodeAnalysis.NotNullAttribute" => true,
                    "System.NonSerializedAttribute" => true,
                    "System.Reflection.AssemblyAlgorithmIdAttribute" => true,
                    "System.Reflection.AssemblyCultureAttribute" => true,
                    "System.Reflection.AssemblyFlagsAttribute" => true,
                    "System.Reflection.AssemblyVersionAttribute" => true,
                    "System.Runtime.CompilerServices.DllImportAttribute" => true,       // Already covered by other rude edits, but included for completeness
                    "System.Runtime.CompilerServices.IndexerNameAttribute" => true,
                    "System.Runtime.CompilerServices.MethodImplAttribute" => true,
                    "System.Runtime.CompilerServices.SpecialNameAttribute" => true,
                    "System.Runtime.CompilerServices.TypeForwardedToAttribute" => true,
                    "System.Runtime.InteropServices.ComImportAttribute" => true,
                    "System.Runtime.InteropServices.DefaultParameterValueAttribute" => true,
                    "System.Runtime.InteropServices.FieldOffsetAttribute" => true,
                    "System.Runtime.InteropServices.InAttribute" => true,
                    "System.Runtime.InteropServices.MarshalAsAttribute" => true,
                    "System.Runtime.InteropServices.OptionalAttribute" => true,
                    "System.Runtime.InteropServices.OutAttribute" => true,
                    "System.Runtime.InteropServices.PreserveSigAttribute" => true,
                    "System.Runtime.InteropServices.StructLayoutAttribute" => true,
                    "System.Runtime.InteropServices.WindowsRuntime.WindowsRuntimeImportAttribute" => true,
                    "System.Security.DynamicSecurityMethodAttribute" => true,
                    "System.SerializableAttribute" => true,
 
                    //
                    // This list is not from the compiler, but included explicitly for Edit and Continue purposes
                    //
 
                    // Applying [AsyncMethodBuilder] changes the code that is emitted:
                    // * When the target is a method, for any await call to the method
                    // * When the target is a type, for any await call to a method that returns that type
                    //
                    // Therefore applying this attribute can cause unbounded changes to emitted code anywhere in a project
                    // which EnC wouldn't pick up, so we block it with a rude edit
                    "System.Runtime.CompilerServices.AsyncMethodBuilderAttribute" => true,
 
                    // Also security attributes
                    not null => IsSecurityAttribute(attribute.AttributeClass),
                    _ => false
                };
            }
 
            static bool IsSecurityAttribute(INamedTypeSymbol namedTypeSymbol)
            {
                // Security attributes are any attribute derived from System.Security.Permissions.SecurityAttribute, directly or indirectly
 
                var symbol = namedTypeSymbol;
                while (symbol is not null)
                {
                    if (symbol.ToNameDisplayString() == "System.Security.Permissions.SecurityAttribute")
                    {
                        return true;
                    }
 
                    symbol = symbol.BaseType;
                }
 
                return false;
            }
        }
 
        private bool CanAddNewMember(ISymbol newSymbol, EditAndContinueCapabilitiesGrantor capabilities, CancellationToken cancellationToken)
        {
            var requiredCapabilities = EditAndContinueCapabilities.None;
 
            if (newSymbol is IMethodSymbol or IEventSymbol or IPropertySymbol)
            {
                requiredCapabilities |= EditAndContinueCapabilities.AddMethodToExistingType;
            }
 
            if (newSymbol is IFieldSymbol || newSymbol is IPropertySymbol { DeclaringSyntaxReferences: [var syntaxRef] } && HasBackingField(syntaxRef.GetSyntax(cancellationToken)))
            {
                requiredCapabilities |= newSymbol.IsStatic ? EditAndContinueCapabilities.AddStaticFieldToExistingType : EditAndContinueCapabilities.AddInstanceFieldToExistingType;
            }
 
            return capabilities.Grant(requiredCapabilities);
        }
 
        private static void AddEditsForSynthesizedRecordMembers(Compilation compilation, INamedTypeSymbol recordType, ArrayBuilder<SemanticEditInfo> semanticEdits, CancellationToken cancellationToken)
        {
            foreach (var member in GetRecordUpdatedSynthesizedMembers(compilation, recordType))
            {
                var symbolKey = SymbolKey.Create(member, cancellationToken);
                semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Update, symbolKey, syntaxMap: null, syntaxMapTree: null, partialType: null));
            }
        }
 
        private static IEnumerable<ISymbol> GetRecordUpdatedSynthesizedMembers(Compilation compilation, INamedTypeSymbol record)
        {
            // All methods that are updated have well known names, and calling GetMembers(string) is
            // faster than enumerating.
 
            // When a new field or property is added the PrintMembers, Equals(R) and GetHashCode() methods are updated
            // We don't need to worry about Deconstruct because it only changes when a new positional parameter
            // is added, and those are rude edits (due to adding a constructor parameter).
            // We don't need to worry about the constructors as they are reported elsewhere.
            // We have to use SingleOrDefault and check IsImplicitlyDeclared because the user can provide their
            // own implementation of these methods, and edits to them are handled by normal processing.
            var result = record.GetMembers(WellKnownMemberNames.PrintMembersMethodName)
                .OfType<IMethodSymbol>()
                .SingleOrDefault(m =>
                    m.IsImplicitlyDeclared &&
                    m.Parameters.Length == 1 &&
                    SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, compilation.GetTypeByMetadataName(typeof(StringBuilder).FullName!)) &&
                    SymbolEqualityComparer.Default.Equals(m.ReturnType, compilation.GetTypeByMetadataName(typeof(bool).FullName!)));
            if (result is not null)
            {
                yield return result;
            }
 
            result = record.GetMembers(WellKnownMemberNames.ObjectEquals)
                .OfType<IMethodSymbol>()
                .SingleOrDefault(m =>
                    m.IsImplicitlyDeclared &&
                    m.Parameters.Length == 1 &&
                    SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType));
            if (result is not null)
            {
                yield return result;
            }
 
            result = record.GetMembers(WellKnownMemberNames.ObjectGetHashCode)
               .OfType<IMethodSymbol>()
               .SingleOrDefault(m =>
                    m.IsImplicitlyDeclared &&
                    m.Parameters.Length == 0);
            if (result is not null)
            {
                yield return result;
            }
        }
 
        private void ReportDeletedMemberRudeEdit(
            ArrayBuilder<RudeEditDiagnostic> diagnostics,
            ISymbol oldSymbol,
            Compilation newCompilation,
            RudeEditKind rudeEditKind,
            CancellationToken cancellationToken)
        {
            var newNode = GetDeleteRudeEditDiagnosticNode(oldSymbol, newCompilation, cancellationToken);
 
            diagnostics.Add(new RudeEditDiagnostic(
                rudeEditKind,
                GetDiagnosticSpan(newNode, EditKind.Delete),
                arguments: new[]
                {
                    string.Format(FeaturesResources.member_kind_and_name, GetDisplayName(oldSymbol), oldSymbol.ToDisplayString(s_unqualifiedMemberDisplayFormat))
                }));
        }
 
        private void ReportUpdateRudeEdit(ArrayBuilder<RudeEditDiagnostic> diagnostics, RudeEditKind rudeEdit, SyntaxNode newNode)
        {
            diagnostics.Add(new RudeEditDiagnostic(
                rudeEdit,
                GetDiagnosticSpan(newNode, EditKind.Update),
                newNode,
                new[] { GetDisplayName(newNode) }));
        }
 
        private void ReportUpdateRudeEdit(ArrayBuilder<RudeEditDiagnostic> diagnostics, RudeEditKind rudeEdit, ISymbol newSymbol, SyntaxNode? newNode, CancellationToken cancellationToken)
        {
            var node = newNode ?? GetRudeEditDiagnosticNode(newSymbol, cancellationToken);
            var span = (rudeEdit == RudeEditKind.ChangeImplicitMainReturnType) ? GetGlobalStatementDiagnosticSpan(node) : GetDiagnosticSpan(node, EditKind.Update);
 
            var arguments = rudeEdit switch
            {
                RudeEditKind.TypeKindUpdate or
                RudeEditKind.ChangeImplicitMainReturnType or
                RudeEditKind.GenericMethodUpdate or
                RudeEditKind.GenericTypeUpdate
                    => Array.Empty<string>(),
 
                RudeEditKind.ChangingReloadableTypeNotSupportedByRuntime
                    => new[] { CreateNewOnMetadataUpdateAttributeName },
 
                _ => new[] { GetDisplayName(newSymbol) }
            };
 
            diagnostics.Add(new RudeEditDiagnostic(rudeEdit, span, node, arguments));
        }
 
        private void ReportUpdateRudeEdit(ArrayBuilder<RudeEditDiagnostic> diagnostics, RudeEditKind rudeEdit, ISymbol oldSymbol, ISymbol newSymbol, SyntaxNode? newNode, Compilation newCompilation, CancellationToken cancellationToken)
        {
            if (newSymbol.IsImplicitlyDeclared)
            {
                ReportDeletedMemberRudeEdit(diagnostics, oldSymbol, newCompilation, rudeEdit, cancellationToken);
            }
            else
            {
                ReportUpdateRudeEdit(diagnostics, rudeEdit, newSymbol, newNode, cancellationToken);
            }
        }
 
        private static SyntaxNode GetRudeEditDiagnosticNode(ISymbol symbol, CancellationToken cancellationToken)
        {
            var container = symbol;
            while (container != null)
            {
                if (container.DeclaringSyntaxReferences.Length > 0)
                {
                    return container.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken);
                }
 
                container = container.ContainingSymbol;
            }
 
            throw ExceptionUtilities.Unreachable();
        }
 
        private static SyntaxNode GetDeleteRudeEditDiagnosticNode(ISymbol oldSymbol, Compilation newCompilation, CancellationToken cancellationToken)
        {
            var oldContainer = oldSymbol.ContainingSymbol;
            while (oldContainer != null)
            {
                var containerKey = SymbolKey.Create(oldContainer, cancellationToken);
                var newContainer = containerKey.Resolve(newCompilation, ignoreAssemblyKey: true, cancellationToken).Symbol;
                if (newContainer != null)
                {
                    return GetRudeEditDiagnosticNode(newContainer, cancellationToken);
                }
 
                oldContainer = oldContainer.ContainingSymbol;
            }
 
            throw ExceptionUtilities.Unreachable();
        }
 
        #region Type Layout Update Validation 
 
        internal void ReportTypeLayoutUpdateRudeEdits(
            ArrayBuilder<RudeEditDiagnostic> diagnostics,
            ISymbol newSymbol,
            SyntaxNode newSyntax,
            SemanticModel newModel,
            ref INamedTypeSymbol? lazyLayoutAttribute)
        {
            switch (newSymbol.Kind)
            {
                case SymbolKind.Field:
                    if (HasExplicitOrSequentialLayout(newSymbol.ContainingType, newModel, ref lazyLayoutAttribute))
                    {
                        ReportTypeLayoutUpdateRudeEdits(diagnostics, newSymbol, newSyntax);
                    }
 
                    break;
 
                case SymbolKind.Property:
                    if (HasBackingField(newSyntax) &&
                        HasExplicitOrSequentialLayout(newSymbol.ContainingType, newModel, ref lazyLayoutAttribute))
                    {
                        ReportTypeLayoutUpdateRudeEdits(diagnostics, newSymbol, newSyntax);
                    }
 
                    break;
 
                case SymbolKind.Event:
                    if (HasBackingField((IEventSymbol)newSymbol) &&
                        HasExplicitOrSequentialLayout(newSymbol.ContainingType, newModel, ref lazyLayoutAttribute))
                    {
                        ReportTypeLayoutUpdateRudeEdits(diagnostics, newSymbol, newSyntax);
                    }
 
                    break;
            }
        }
 
        private void ReportTypeLayoutUpdateRudeEdits(ArrayBuilder<RudeEditDiagnostic> diagnostics, ISymbol symbol, SyntaxNode syntax)
        {
            var intoStruct = symbol.ContainingType.TypeKind == TypeKind.Struct;
 
            diagnostics.Add(new RudeEditDiagnostic(
                intoStruct ? RudeEditKind.InsertIntoStruct : RudeEditKind.InsertIntoClassWithLayout,
                syntax.Span,
                syntax,
                new[]
                {
                    GetDisplayName(syntax, EditKind.Insert),
                    GetDisplayName(TryGetContainingTypeDeclaration(syntax)!, EditKind.Update)
                }));
        }
 
        private static bool HasBackingField(IEventSymbol @event)
        {
#nullable disable // https://github.com/dotnet/roslyn/issues/39288
            return @event.AddMethod.IsImplicitlyDeclared
#nullable enable
                && !@event.IsAbstract;
        }
 
        private static bool HasExplicitOrSequentialLayout(
            INamedTypeSymbol type,
            SemanticModel model,
            ref INamedTypeSymbol? lazyLayoutAttribute)
        {
            if (type.TypeKind == TypeKind.Struct)
            {
                return true;
            }
 
            if (type.TypeKind != TypeKind.Class)
            {
                return false;
            }
 
            // Fields can't be inserted into a class with explicit or sequential layout
            var attributes = type.GetAttributes();
            if (attributes.Length == 0)
            {
                return false;
            }
 
            lazyLayoutAttribute ??= model.Compilation.GetTypeByMetadataName(typeof(StructLayoutAttribute).FullName!);
            if (lazyLayoutAttribute == null)
            {
                return false;
            }
 
            foreach (var attribute in attributes)
            {
                RoslynDebug.Assert(attribute.AttributeClass is object);
                if (attribute.AttributeClass.Equals(lazyLayoutAttribute) && attribute.ConstructorArguments.Length == 1)
                {
                    var layoutValue = attribute.ConstructorArguments.Single().Value;
                    return (layoutValue is int ? (int)layoutValue :
                            layoutValue is short ? (short)layoutValue :
                            (int)LayoutKind.Auto) != (int)LayoutKind.Auto;
                }
            }
 
            return false;
        }
 
        #endregion
 
        private Func<SyntaxNode, SyntaxNode?> CreateSyntaxMapForEquivalentNodes(SyntaxNode oldDeclaration, SyntaxNode newDeclaration)
        {
            return newNode => newDeclaration.FullSpan.Contains(newNode.SpanStart) ?
                FindDeclarationBodyPartner(newDeclaration, oldDeclaration, newNode) : null;
        }
 
        private static Func<SyntaxNode, SyntaxNode?> CreateSyntaxMap(IReadOnlyDictionary<SyntaxNode, SyntaxNode> reverseMap)
            => newNode => reverseMap.TryGetValue(newNode, out var oldNode) ? oldNode : null;
 
        private Func<SyntaxNode, SyntaxNode?>? CreateAggregateSyntaxMap(
            IReadOnlyDictionary<SyntaxNode, SyntaxNode> reverseTopMatches,
            IReadOnlyDictionary<SyntaxNode, Func<SyntaxNode, SyntaxNode?>?> changedDeclarations)
        {
            return newNode =>
            {
                // containing declaration
                if (!TryFindMemberDeclaration(root: null, newNode, out var newDeclarations))
                {
                    return null;
                }
 
                foreach (var newDeclaration in newDeclarations)
                {
                    // The node is in a field, property or constructor declaration that has been changed:
                    if (changedDeclarations.TryGetValue(newDeclaration, out var syntaxMap))
                    {
                        // If syntax map is not available the declaration was either
                        // 1) updated but is not active
                        // 2) inserted
                        return syntaxMap?.Invoke(newNode);
                    }
 
                    // The node is in a declaration that hasn't been changed:
                    if (reverseTopMatches.TryGetValue(newDeclaration, out var oldDeclaration))
                    {
                        return FindDeclarationBodyPartner(newDeclaration, oldDeclaration, newNode);
                    }
                }
 
                return null;
            };
        }
 
        #region Constructors and Initializers
 
        /// <summary>
        /// Called when a body of a constructor or an initializer of a member is updated or inserted.
        /// </summary>
        private static void DeferConstructorEdit(
            INamedTypeSymbol oldType,
            INamedTypeSymbol newType,
            SyntaxNode? newDeclaration,
            Func<SyntaxNode, SyntaxNode?>? syntaxMap,
            bool isStatic,
            ref PooledDictionary<INamedTypeSymbol, ConstructorEdit>? instanceConstructorEdits,
            ref PooledDictionary<INamedTypeSymbol, ConstructorEdit>? staticConstructorEdits)
        {
            Dictionary<INamedTypeSymbol, ConstructorEdit> constructorEdits;
            if (isStatic)
            {
                constructorEdits = staticConstructorEdits ??= PooledDictionary<INamedTypeSymbol, ConstructorEdit>.GetInstance();
            }
            else
            {
                constructorEdits = instanceConstructorEdits ??= PooledDictionary<INamedTypeSymbol, ConstructorEdit>.GetInstance();
            }
 
            if (!constructorEdits.TryGetValue(newType, out var constructorEdit))
            {
                constructorEdits.Add(newType, constructorEdit = new ConstructorEdit(oldType));
            }
 
            if (newDeclaration != null && !constructorEdit.ChangedDeclarations.ContainsKey(newDeclaration))
            {
                constructorEdit.ChangedDeclarations.Add(newDeclaration, syntaxMap);
            }
        }
 
        private void AddConstructorEdits(
            IReadOnlyDictionary<INamedTypeSymbol, ConstructorEdit> updatedTypes,
            Match<SyntaxNode> topMatch,
            SemanticModel? oldModel,
            Compilation oldCompilation,
            Compilation newCompilation,
            Roslyn.Utilities.IReadOnlySet<ISymbol> processedSymbols,
            EditAndContinueCapabilitiesGrantor capabilities,
            bool isStatic,
            [Out] ArrayBuilder<SemanticEditInfo> semanticEdits,
            [Out] ArrayBuilder<RudeEditDiagnostic> diagnostics,
            CancellationToken cancellationToken)
        {
            var oldSyntaxTree = topMatch.OldRoot.SyntaxTree;
            var newSyntaxTree = topMatch.NewRoot.SyntaxTree;
 
            foreach (var (newType, updatesInCurrentDocument) in updatedTypes)
            {
                var oldType = updatesInCurrentDocument.OldType;
 
                var anyInitializerUpdatesInCurrentDocument = updatesInCurrentDocument.ChangedDeclarations.Keys.Any(IsDeclarationWithInitializer);
                var isPartialEdit = IsPartialEdit(oldType, newType, oldSyntaxTree, newSyntaxTree);
 
                // Create a syntax map that aggregates syntax maps of the constructor body and all initializers in this document.
                // Use syntax maps stored in update.ChangedDeclarations and fallback to 1:1 map for unchanged members.
                //
                // This aggregated map will be combined with similar maps capturing members declared in partial type declarations
                // located in other documents when the semantic edits are merged across all changed documents of the project.
                //
                // We will create an aggregate syntax map even in cases when we don't necessarily need it,
                // for example if none of the edited declarations are active. It's ok to have a map that we don't need.
                // This is simpler than detecting whether or not some of the initializers/constructors contain active statements.
                var aggregateSyntaxMap = CreateAggregateSyntaxMap(topMatch.ReverseMatches, updatesInCurrentDocument.ChangedDeclarations);
 
                bool? lazyOldTypeHasMemberInitializerContainingLambda = null;
 
                foreach (var newCtor in isStatic ? newType.StaticConstructors : newType.InstanceConstructors)
                {
                    if (processedSymbols.Contains(newCtor))
                    {
                        // we already have an edit for the new constructor
                        continue;
                    }
 
                    var newCtorKey = SymbolKey.Create(newCtor, cancellationToken);
 
                    var syntaxMapToUse = aggregateSyntaxMap;
 
                    SyntaxNode? newDeclaration = null;
                    ISymbol? oldCtor;
                    if (!newCtor.IsImplicitlyDeclared)
                    {
                        // Constructors have to have a single declaration syntax, they can't be partial
                        newDeclaration = GetSymbolDeclarationSyntax(newCtor.DeclaringSyntaxReferences.Single(), cancellationToken);
 
                        // Implicit record constructors are represented by the record declaration itself.
                        // https://github.com/dotnet/roslyn/issues/54403
                        var isPrimaryRecordConstructor = IsRecordDeclaration(newDeclaration);
 
                        // Constructor that doesn't contain initializers had a corresponding semantic edit produced previously 
                        // or was not edited. In either case we should not produce a semantic edit for it.
                        if (!isPrimaryRecordConstructor && !IsConstructorWithMemberInitializers(newDeclaration))
                        {
                            continue;
                        }
 
                        // If no initializer updates were made in the type we only need to produce semantic edits for constructors
                        // whose body has been updated, otherwise we need to produce edits for all constructors that include initializers.
                        // If changes were made to initializers or constructors of a partial type in another document they will be merged
                        // when aggregating semantic edits from all changed documents. Rude edits resulting from those changes, if any, will
                        // be reported in the document they were made in.
                        if (!isPrimaryRecordConstructor && !anyInitializerUpdatesInCurrentDocument && !updatesInCurrentDocument.ChangedDeclarations.ContainsKey(newDeclaration))
                        {
                            continue;
                        }
 
                        // To avoid costly SymbolKey resolution we first try to match the constructor in the current document
                        // and special case parameter-less constructor.
 
                        // In the case of records, newDeclaration will point to the record declaration, take the slow path.
                        if (!isPrimaryRecordConstructor && topMatch.TryGetOldNode(newDeclaration, out var oldDeclaration))
                        {
                            Contract.ThrowIfNull(oldModel);
                            oldCtor = oldModel.GetDeclaredSymbol(oldDeclaration, cancellationToken);
                            Contract.ThrowIfFalse(oldCtor is IMethodSymbol { MethodKind: MethodKind.Constructor or MethodKind.StaticConstructor });
                        }
                        else if (!isPrimaryRecordConstructor && newCtor.Parameters.Length == 0)
                        {
                            oldCtor = TryGetParameterlessConstructor(oldType, isStatic);
                        }
                        else
                        {
                            var resolution = newCtorKey.Resolve(oldCompilation, ignoreAssemblyKey: true, cancellationToken);
 
                            // There may be semantic errors in the compilation that result in multiple candidates.
                            // Pick the first candidate.
 
                            oldCtor = resolution.Symbol;
                        }
 
                        if (oldCtor == null && HasMemberInitializerContainingLambda(oldType, isStatic, ref lazyOldTypeHasMemberInitializerContainingLambda, cancellationToken))
                        {
                            // TODO (bug https://github.com/dotnet/roslyn/issues/2504)
                            // rude edit: Adding a constructor to a type with a field or property initializer that contains an anonymous function
                            diagnostics.Add(new RudeEditDiagnostic(RudeEditKind.InsertConstructorToTypeWithInitializersWithLambdas, GetDiagnosticSpan(newDeclaration, EditKind.Insert)));
                            continue;
                        }
 
                        // Report an error if the updated constructor's declaration is in the current document 
                        // and its body edit is disallowed (e.g. contains stackalloc).
                        if (oldCtor != null && newDeclaration.SyntaxTree == newSyntaxTree && anyInitializerUpdatesInCurrentDocument)
                        {
                            // attribute rude edit to one of the modified members
                            var firstSpan = updatesInCurrentDocument.ChangedDeclarations.Keys.Where(IsDeclarationWithInitializer).Aggregate(
                                (min: int.MaxValue, span: default(TextSpan)),
                                (accumulate, node) => (node.SpanStart < accumulate.min) ? (node.SpanStart, node.Span) : accumulate).span;
 
                            Contract.ThrowIfTrue(firstSpan.IsEmpty);
                            ReportMemberBodyUpdateRudeEdits(diagnostics, newDeclaration, firstSpan);
                        }
 
                        // When explicitly implementing the copy constructor of a record the parameter name if the runtime doesn't support
                        // updating parameters, otherwise the debugger would show the incorrect name in the autos/locals/watch window
                        if (oldCtor != null &&
                            !isPrimaryRecordConstructor &&
                            oldCtor.DeclaringSyntaxReferences.Length == 0 &&
                            newCtor.Parameters.Length == 1 &&
                            newType.IsRecord &&
                            oldCtor.GetParameters().First().Name != newCtor.GetParameters().First().Name &&
                            !capabilities.Grant(EditAndContinueCapabilities.UpdateParameters))
                        {
                            diagnostics.Add(new RudeEditDiagnostic(
                                RudeEditKind.ExplicitRecordMethodParameterNamesMustMatch,
                                GetDiagnosticSpan(newDeclaration, EditKind.Update),
                                arguments: new[] { oldCtor.ToDisplayString(SymbolDisplayFormats.NameFormat) }));
 
                            continue;
                        }
                    }
                    else
                    {
                        if (newCtor.Parameters.Length == 1)
                        {
                            // New constructor is implicitly declared with a parameter, so its the copy constructor of a record
                            Debug.Assert(oldType.IsRecord);
                            Debug.Assert(newType.IsRecord);
 
                            // We only need an edit for this if the number of properties or fields on the record has changed. Changes to
                            // initializers, or whether the property is part of the primary constructor, will still come through this code
                            // path because they need an edit to the other constructor, but not the copy construcor.
                            if (oldType.GetMembers().OfType<IPropertySymbol>().Count() == newType.GetMembers().OfType<IPropertySymbol>().Count() &&
                                oldType.GetMembers().OfType<IFieldSymbol>().Count() == newType.GetMembers().OfType<IFieldSymbol>().Count())
                            {
                                continue;
                            }
 
                            oldCtor = oldType.InstanceConstructors.Single(c => c.Parameters.Length == 1 && SymbolEqualityComparer.Default.Equals(c.Parameters[0].Type, c.ContainingType));
                            // The copy constructor does not have a syntax map
                            syntaxMapToUse = null;
                            // Since there is no syntax map, we don't need to handle anything special to merge them for partial types.
                            // The easiest way to do this is just to pretend this isn't a partial edit.
                            isPartialEdit = false;
                        }
                        else
                        {
                            // New constructor is implicitly declared so it must be parameterless.
                            //
                            // Instance constructor:
                            //   Its presence indicates there are no other instance constructors in the new type and therefore
                            //   there must be a single parameterless instance constructor in the old type (constructors with parameters can't be removed).
                            //
                            // Static constructor:
                            //    Static constructor is always parameterless and not implicitly generated if there are no static initializers.
                            oldCtor = TryGetParameterlessConstructor(oldType, isStatic);
                        }
 
                        Contract.ThrowIfFalse(isStatic || oldCtor != null);
                    }
 
                    if (oldCtor != null)
                    {
                        AnalyzeSymbolUpdate(oldCtor, newCtor, newDeclaration, newCompilation, topMatch, capabilities, diagnostics, semanticEdits, syntaxMapToUse, processedSymbols: null, cancellationToken);
 
                        semanticEdits.Add(new SemanticEditInfo(
                            SemanticEditKind.Update,
                            newCtorKey,
                            syntaxMapToUse,
                            syntaxMapTree: isPartialEdit ? newSyntaxTree : null,
                            partialType: isPartialEdit ? SymbolKey.Create(newType, cancellationToken) : null));
                    }
                    else
                    {
                        semanticEdits.Add(new SemanticEditInfo(
                            SemanticEditKind.Insert,
                            newCtorKey,
                            syntaxMap: null,
                            syntaxMapTree: null,
                            partialType: null));
                    }
                }
            }
        }
 
        private bool HasMemberInitializerContainingLambda(INamedTypeSymbol type, bool isStatic, ref bool? lazyHasMemberInitializerContainingLambda, CancellationToken cancellationToken)
        {
            // checking the old type for existing lambdas (it's ok for the new initializers to contain lambdas)
            lazyHasMemberInitializerContainingLambda ??= HasMemberInitializerContainingLambda(type, isStatic, cancellationToken);
 
            return lazyHasMemberInitializerContainingLambda.Value;
        }
 
        private bool HasMemberInitializerContainingLambda(INamedTypeSymbol type, bool isStatic, CancellationToken cancellationToken)
        {
            // checking the old type for existing lambdas (it's ok for the new initializers to contain lambdas)
 
            foreach (var member in type.GetMembers())
            {
                if (member.IsStatic == isStatic &&
                    (member.Kind == SymbolKind.Field || member.Kind == SymbolKind.Property) &&
                    member.DeclaringSyntaxReferences.Length > 0) // skip generated fields (e.g. VB auto-property backing fields)
                {
                    var syntax = GetSymbolDeclarationSyntax(member.DeclaringSyntaxReferences.Single(), cancellationToken);
                    if (IsDeclarationWithInitializer(syntax) && ContainsLambda(syntax))
                    {
                        return true;
                    }
                }
            }
 
            return false;
        }
 
        private static ISymbol? TryGetParameterlessConstructor(INamedTypeSymbol type, bool isStatic)
        {
            var oldCtors = isStatic ? type.StaticConstructors : type.InstanceConstructors;
            if (isStatic)
            {
                return type.StaticConstructors.FirstOrDefault();
            }
            else
            {
                return type.InstanceConstructors.FirstOrDefault(m => m.Parameters.Length == 0);
            }
        }
 
        private static bool IsPartialEdit(ISymbol? oldSymbol, ISymbol? newSymbol, SyntaxTree oldSyntaxTree, SyntaxTree newSyntaxTree)
        {
            // If any of the partial declarations of the new or the old type are in another document
            // the edit will need to be merged with other partial edits with matching partial type
            static bool IsNotInDocument(SyntaxReference reference, SyntaxTree syntaxTree)
                => reference.SyntaxTree != syntaxTree;
 
            return oldSymbol?.Kind == SymbolKind.NamedType && oldSymbol.DeclaringSyntaxReferences.Length > 1 && oldSymbol.DeclaringSyntaxReferences.Any(IsNotInDocument, oldSyntaxTree) ||
                   newSymbol?.Kind == SymbolKind.NamedType && newSymbol.DeclaringSyntaxReferences.Length > 1 && newSymbol.DeclaringSyntaxReferences.Any(IsNotInDocument, newSyntaxTree);
        }
 
        #endregion
 
        #region Lambdas and Closures
 
        private void ReportLambdaAndClosureRudeEdits(
            SemanticModel oldModel,
            SyntaxNode oldMemberBody,
            SemanticModel newModel,
            SyntaxNode newMemberBody,
            ISymbol newMember,
            IReadOnlyDictionary<SyntaxNode, LambdaInfo>? matchedLambdas,
            BidirectionalMap<SyntaxNode> map,
            EditAndContinueCapabilitiesGrantor capabilities,
            ArrayBuilder<RudeEditDiagnostic> diagnostics,
            out bool syntaxMapRequired,
            CancellationToken cancellationToken)
        {
            syntaxMapRequired = false;
 
            if (matchedLambdas != null)
            {
                var anySignatureErrors = false;
                foreach (var (oldLambdaBody, newLambdaInfo) in matchedLambdas)
                {
                    // Any unmatched lambdas would have contained an active statement and a rude edit would be reported in syntax analysis phase.
                    // Skip the rest of lambda and closure analysis if such lambdas are present.
                    if (newLambdaInfo.Match == null || newLambdaInfo.NewBody == null)
                    {
                        return;
                    }
 
                    ReportLambdaSignatureRudeEdits(diagnostics, oldModel, oldLambdaBody, newModel, newLambdaInfo.NewBody, capabilities, out var hasErrors, cancellationToken);
                    anySignatureErrors |= hasErrors;
                }
 
                ArrayBuilder<SyntaxNode>? lazyNewErroneousClauses = null;
                foreach (var (oldQueryClause, newQueryClause) in map.Forward)
                {
                    if (!QueryClauseLambdasTypeEquivalent(oldModel, oldQueryClause, newModel, newQueryClause, cancellationToken))
                    {
                        lazyNewErroneousClauses ??= ArrayBuilder<SyntaxNode>.GetInstance();
                        lazyNewErroneousClauses.Add(newQueryClause);
                    }
                }
 
                if (lazyNewErroneousClauses != null)
                {
                    foreach (var newQueryClause in from clause in lazyNewErroneousClauses
                                                   orderby clause.SpanStart
                                                   group clause by GetContainingQueryExpression(clause) into clausesByQuery
                                                   select clausesByQuery.First())
                    {
                        diagnostics.Add(new RudeEditDiagnostic(
                            RudeEditKind.ChangingQueryLambdaType,
                            GetDiagnosticSpan(newQueryClause, EditKind.Update),
                            newQueryClause,
                            new[] { GetDisplayName(newQueryClause, EditKind.Update) }));
                    }
 
                    lazyNewErroneousClauses.Free();
                    anySignatureErrors = true;
                }
 
                // only dig into captures if lambda signatures match
                if (anySignatureErrors)
                {
                    return;
                }
            }
 
            using var oldLambdaBodyEnumerator = GetLambdaBodies(oldMemberBody).GetEnumerator();
            using var newLambdaBodyEnumerator = GetLambdaBodies(newMemberBody).GetEnumerator();
            var oldHasLambdas = oldLambdaBodyEnumerator.MoveNext();
            var newHasLambdas = newLambdaBodyEnumerator.MoveNext();
 
            // Exit early if there are no lambdas in the method to avoid expensive data flow analysis:
            if (!oldHasLambdas && !newHasLambdas)
            {
                return;
            }
 
            var oldCaptures = GetCapturedVariables(oldModel, oldMemberBody);
            var newCaptures = GetCapturedVariables(newModel, newMemberBody);
 
            // { new capture index -> old capture index }
            using var _1 = ArrayBuilder<int>.GetInstance(newCaptures.Length, fillWithValue: 0, out var reverseCapturesMap);
 
            // { new capture index -> new closure scope or null for "this" }
            using var _2 = ArrayBuilder<SyntaxNode?>.GetInstance(newCaptures.Length, fillWithValue: null, out var newCapturesToClosureScopes);
 
            // Can be calculated from other maps but it's simpler to just calculate it upfront.
            // { old capture index -> old closure scope or null for "this" }
            using var _3 = ArrayBuilder<SyntaxNode?>.GetInstance(oldCaptures.Length, fillWithValue: null, out var oldCapturesToClosureScopes);
 
            CalculateCapturedVariablesMaps(
                oldCaptures,
                oldMemberBody,
                newCaptures,
                newMember,
                newMemberBody,
                map,
                reverseCapturesMap,
                newCapturesToClosureScopes,
                oldCapturesToClosureScopes,
                diagnostics,
                out var anyCaptureErrors,
                cancellationToken);
 
            if (anyCaptureErrors)
            {
                return;
            }
 
            // Every captured variable accessed in the new lambda has to be 
            // accessed in the old lambda as well and vice versa.
            //
            // An added lambda can only reference captured variables that 
            //
            // This requirement ensures that:
            // - Lambda methods are generated to the same frame as before, so they can be updated in-place.
            // - "Parent" links between closure scopes are preserved.
 
            using var _11 = PooledDictionary<ISymbol, int>.GetInstance(out var oldCapturesIndex);
            using var _12 = PooledDictionary<ISymbol, int>.GetInstance(out var newCapturesIndex);
 
            BuildIndex(oldCapturesIndex, oldCaptures);
            BuildIndex(newCapturesIndex, newCaptures);
 
            if (matchedLambdas != null)
            {
                var mappedLambdasHaveErrors = false;
                foreach (var (oldLambdaBody, newLambdaInfo) in matchedLambdas)
                {
                    var newLambdaBody = newLambdaInfo.NewBody;
 
                    // The map now contains only matched lambdas. Any unmatched ones would have contained an active statement and 
                    // a rude edit would be reported in syntax analysis phase.
                    RoslynDebug.Assert(newLambdaInfo.Match != null && newLambdaBody != null);
 
                    var accessedOldCaptures = GetAccessedCaptures(oldLambdaBody, oldModel, oldCaptures, oldCapturesIndex);
                    var accessedNewCaptures = GetAccessedCaptures(newLambdaBody, newModel, newCaptures, newCapturesIndex);
 
                    // Requirement: 
                    // (new(ReadInside) \/ new(WrittenInside)) /\ new(Captured) == (old(ReadInside) \/ old(WrittenInside)) /\ old(Captured)
                    for (var newCaptureIndex = 0; newCaptureIndex < newCaptures.Length; newCaptureIndex++)
                    {
                        var newAccessed = accessedNewCaptures[newCaptureIndex];
                        var oldAccessed = accessedOldCaptures[reverseCapturesMap[newCaptureIndex]];
 
                        if (newAccessed != oldAccessed)
                        {
                            var newCapture = newCaptures[newCaptureIndex];
 
                            var rudeEdit = newAccessed ? RudeEditKind.AccessingCapturedVariableInLambda : RudeEditKind.NotAccessingCapturedVariableInLambda;
                            var arguments = new[] { newCapture.Name, GetDisplayName(GetLambda(newLambdaBody)) };
 
                            if (newCapture.IsThisParameter() || oldAccessed)
                            {
                                // changed accessed to "this", or captured variable accessed in old lambda is not accessed in the new lambda
                                diagnostics.Add(new RudeEditDiagnostic(rudeEdit, GetDiagnosticSpan(GetLambda(newLambdaBody), EditKind.Update), null, arguments));
                            }
                            else if (newAccessed)
                            {
                                // captured variable accessed in new lambda is not accessed in the old lambda
                                var hasUseSites = false;
                                foreach (var useSite in GetVariableUseSites(GetLambdaBodyExpressionsAndStatements(newLambdaBody), newCapture, newModel, cancellationToken))
                                {
                                    hasUseSites = true;
                                    diagnostics.Add(new RudeEditDiagnostic(rudeEdit, useSite.Span, null, arguments));
                                }
 
                                Debug.Assert(hasUseSites);
                            }
 
                            mappedLambdasHaveErrors = true;
                        }
                    }
                }
 
                if (mappedLambdasHaveErrors)
                {
                    return;
                }
            }
 
            // Report rude edits for lambdas added to the method.
            // We already checked that no new captures are introduced or removed. 
            // We also need to make sure that no new parent frame links are introduced.
            // 
            // We could implement the same analysis as the compiler does when rewriting lambdas - 
            // to determine what closure scopes are connected at runtime via parent link, 
            // and then disallow adding a lambda that connects two previously unconnected 
            // groups of scopes.
            //
            // However even if we implemented that logic here, it would be challenging to 
            // present the result of the analysis to the user in a short comprehensible error message.
            // 
            // In practice, we believe the common scenarios are (in order of commonality):
            // 1) adding a static lambda
            // 2) adding a lambda that accesses only "this"
            // 3) adding a lambda that accesses variables from the same scope
            // 4) adding a lambda that accesses "this" and variables from a single scope
            // 5) adding a lambda that accesses variables from different scopes that are linked
            // 6) adding a lambda that accesses variables from unlinked scopes
            // 
            // We currently allow #1, #2, and #3 and report a rude edit for the other cases.
            // In future we might be able to enable more.
 
            var containingTypeDeclaration = TryGetContainingTypeDeclaration(newMemberBody);
            var isInInterfaceDeclaration = containingTypeDeclaration != null && IsInterfaceDeclaration(containingTypeDeclaration);
 
            var newHasLambdaBodies = newHasLambdas;
            while (newHasLambdaBodies)
            {
                var (newLambda, newLambdaBody1, newLambdaBody2) = newLambdaBodyEnumerator.Current;
 
                if (!map.Reverse.ContainsKey(newLambda))
                {
                    if (!CanAddNewLambda(newLambda, capabilities, matchedLambdas))
                    {
                        diagnostics.Add(new RudeEditDiagnostic(RudeEditKind.InsertNotSupportedByRuntime, GetDiagnosticSpan(newLambda, EditKind.Insert), newLambda, new string[] { GetDisplayName(newLambda, EditKind.Insert) }));
                    }
 
                    // TODO: https://github.com/dotnet/roslyn/issues/37128
                    // Local functions are emitted directly to the type containing the containing method.
                    // Although local functions are non-virtual the Core CLR currently does not support adding any method to an interface.
                    if (isInInterfaceDeclaration && IsLocalFunction(newLambda))
                    {
                        diagnostics.Add(new RudeEditDiagnostic(RudeEditKind.InsertLocalFunctionIntoInterfaceMethod, GetDiagnosticSpan(newLambda, EditKind.Insert), newLambda, new string[] { GetDisplayName(newLambda, EditKind.Insert) }));
                    }
 
                    ReportMultiScopeCaptures(newLambdaBody1, newModel, newCaptures, newCaptures, newCapturesToClosureScopes, newCapturesIndex, reverseCapturesMap, diagnostics, isInsert: true, cancellationToken: cancellationToken);
 
                    if (newLambdaBody2 != null)
                    {
                        ReportMultiScopeCaptures(newLambdaBody2, newModel, newCaptures, newCaptures, newCapturesToClosureScopes, newCapturesIndex, reverseCapturesMap, diagnostics, isInsert: true, cancellationToken: cancellationToken);
                    }
                }
 
                newHasLambdaBodies = newLambdaBodyEnumerator.MoveNext();
            }
 
            // Similarly for addition. We don't allow removal of lambda that has captures from multiple scopes.
 
            var oldHasMoreLambdas = oldHasLambdas;
            while (oldHasMoreLambdas)
            {
                var (oldLambda, oldLambdaBody1, oldLambdaBody2) = oldLambdaBodyEnumerator.Current;
 
                if (!map.Forward.ContainsKey(oldLambda))
                {
                    ReportMultiScopeCaptures(oldLambdaBody1, oldModel, oldCaptures, newCaptures, oldCapturesToClosureScopes, oldCapturesIndex, reverseCapturesMap, diagnostics, isInsert: false, cancellationToken: cancellationToken);
 
                    if (oldLambdaBody2 != null)
                    {
                        ReportMultiScopeCaptures(oldLambdaBody2, oldModel, oldCaptures, newCaptures, oldCapturesToClosureScopes, oldCapturesIndex, reverseCapturesMap, diagnostics, isInsert: false, cancellationToken: cancellationToken);
                    }
                }
 
                oldHasMoreLambdas = oldLambdaBodyEnumerator.MoveNext();
            }
 
            syntaxMapRequired = newHasLambdas;
        }
 
        private IEnumerable<(SyntaxNode lambda, SyntaxNode lambdaBody1, SyntaxNode? lambdaBody2)> GetLambdaBodies(SyntaxNode body)
        {
            foreach (var node in body.DescendantNodesAndSelf())
            {
                if (TryGetLambdaBodies(node, out var body1, out var body2))
                {
                    yield return (node, body1, body2);
                }
            }
        }
 
        private bool CanAddNewLambda(SyntaxNode newLambda, EditAndContinueCapabilitiesGrantor capabilities, IReadOnlyDictionary<SyntaxNode, LambdaInfo>? matchedLambdas)
        {
            // New local functions mean new methods in existing classes
            if (IsLocalFunction(newLambda))
            {
                return capabilities.Grant(EditAndContinueCapabilities.AddMethodToExistingType);
            }
 
            // New lambdas sometimes mean creating new helper classes, and sometimes mean new methods in exising helper classes
            // Unfortunately we are limited here in what we can do here. See: https://github.com/dotnet/roslyn/issues/52759
 
            // If there is already a lambda in the method then the new lambda would result in a new method in the existing helper class.
            // This check is redundant with the below, once the limitation in the referenced issue is resolved
            if (matchedLambdas is { Count: > 0 })
            {
                return capabilities.Grant(EditAndContinueCapabilities.AddMethodToExistingType);
            }
 
            // If there is already a lambda in the class then the new lambda would result in a new method in the existing helper class.
            // If there isn't already a lambda in the class then the new lambda would result in a new helper class.
            // Unfortunately right now we can't determine which of these is true so we have to just check both capabilities instead.
            return capabilities.Grant(EditAndContinueCapabilities.NewTypeDefinition) &&
                capabilities.Grant(EditAndContinueCapabilities.AddMethodToExistingType);
        }
 
        private void ReportMultiScopeCaptures(
            SyntaxNode lambdaBody,
            SemanticModel model,
            ImmutableArray<ISymbol> captures,
            ImmutableArray<ISymbol> newCaptures,
            ArrayBuilder<SyntaxNode?> newCapturesToClosureScopes,
            PooledDictionary<ISymbol, int> capturesIndex,
            ArrayBuilder<int> reverseCapturesMap,
            ArrayBuilder<RudeEditDiagnostic> diagnostics,
            bool isInsert,
            CancellationToken cancellationToken)
        {
            if (captures.Length == 0)
            {
                return;
            }
 
            var accessedCaptures = GetAccessedCaptures(lambdaBody, model, captures, capturesIndex);
 
            var firstAccessedCaptureIndex = -1;
            for (var i = 0; i < captures.Length; i++)
            {
                if (accessedCaptures[i])
                {
                    if (firstAccessedCaptureIndex == -1)
                    {
                        firstAccessedCaptureIndex = i;
                    }
                    else if (newCapturesToClosureScopes[firstAccessedCaptureIndex] != newCapturesToClosureScopes[i])
                    {
                        // the lambda accesses variables from two different scopes:
 
                        TextSpan errorSpan;
                        RudeEditKind rudeEdit;
                        if (isInsert)
                        {
                            if (captures[i].IsThisParameter())
                            {
                                errorSpan = GetDiagnosticSpan(GetLambda(lambdaBody), EditKind.Insert);
                            }
                            else
                            {
                                errorSpan = GetVariableUseSites(GetLambdaBodyExpressionsAndStatements(lambdaBody), captures[i], model, cancellationToken).First().Span;
                            }
 
                            rudeEdit = RudeEditKind.InsertLambdaWithMultiScopeCapture;
                        }
                        else
                        {
                            errorSpan = newCaptures[reverseCapturesMap.IndexOf(i)].Locations.Single().SourceSpan;
                            rudeEdit = RudeEditKind.DeleteLambdaWithMultiScopeCapture;
                        }
 
                        diagnostics.Add(new RudeEditDiagnostic(
                            rudeEdit,
                            errorSpan,
                            null,
                            new[] { GetDisplayName(GetLambda(lambdaBody)), captures[firstAccessedCaptureIndex].Name, captures[i].Name }));
 
                        break;
                    }
                }
            }
        }
 
        private BitVector GetAccessedCaptures(SyntaxNode lambdaBody, SemanticModel model, ImmutableArray<ISymbol> captures, PooledDictionary<ISymbol, int> capturesIndex)
        {
            var result = BitVector.Create(captures.Length);
 
            foreach (var expressionOrStatement in GetLambdaBodyExpressionsAndStatements(lambdaBody))
            {
                var dataFlow = model.AnalyzeDataFlow(expressionOrStatement);
                MarkVariables(ref result, dataFlow.ReadInside, capturesIndex);
                MarkVariables(ref result, dataFlow.WrittenInside, capturesIndex);
            }
 
            return result;
        }
 
        private static void MarkVariables(ref BitVector mask, ImmutableArray<ISymbol> variables, Dictionary<ISymbol, int> index)
        {
            foreach (var variable in variables)
            {
                if (index.TryGetValue(variable, out var newCaptureIndex))
                {
                    mask[newCaptureIndex] = true;
                }
            }
        }
 
        private static void BuildIndex<TKey>(Dictionary<TKey, int> index, ImmutableArray<TKey> array)
            where TKey : notnull
        {
            for (var i = 0; i < array.Length; i++)
            {
                index.Add(array[i], i);
            }
        }
 
        /// <summary>
        /// Returns node that represents a declaration of the symbol whose <paramref name="reference"/> is passed in.
        /// </summary>
        protected abstract SyntaxNode GetSymbolDeclarationSyntax(SyntaxReference reference, CancellationToken cancellationToken);
 
        private static TextSpan GetThisParameterDiagnosticSpan(ISymbol member)
            => member.Locations.First().SourceSpan;
 
        private static TextSpan GetVariableDiagnosticSpan(ISymbol local)
        {
            // Note that in VB implicit value parameter in property setter doesn't have a location.
            // In C# its location is the location of the setter.
            // See https://github.com/dotnet/roslyn/issues/14273
            return local.Locations.FirstOrDefault()?.SourceSpan ?? local.ContainingSymbol.Locations.First().SourceSpan;
        }
 
        private static (SyntaxNode? Node, int Ordinal) GetParameterKey(IParameterSymbol parameter, CancellationToken cancellationToken)
        {
            var containingLambda = parameter.ContainingSymbol as IMethodSymbol;
            if (containingLambda?.MethodKind is MethodKind.LambdaMethod or MethodKind.LocalFunction)
            {
                var oldContainingLambdaSyntax = containingLambda.DeclaringSyntaxReferences.Single().GetSyntax(cancellationToken);
                return (oldContainingLambdaSyntax, parameter.Ordinal);
            }
            else
            {
                return (Node: null, parameter.Ordinal);
            }
        }
 
        private static bool TryMapParameter((SyntaxNode? Node, int Ordinal) parameterKey, IReadOnlyDictionary<SyntaxNode, SyntaxNode> map, out (SyntaxNode? Node, int Ordinal) mappedParameterKey)
        {
            var containingLambdaSyntax = parameterKey.Node;
 
            if (containingLambdaSyntax == null)
            {
                // method parameter: no syntax, same ordinal (can't change since method signatures must match)
                mappedParameterKey = parameterKey;
                return true;
            }
 
            if (map.TryGetValue(containingLambdaSyntax, out var mappedContainingLambdaSyntax))
            {
                // parameter of an existing lambda: same ordinal (can't change since lambda signatures must match), 
                mappedParameterKey = (mappedContainingLambdaSyntax, parameterKey.Ordinal);
                return true;
            }
 
            // no mapping
            mappedParameterKey = default;
            return false;
        }
 
        private void CalculateCapturedVariablesMaps(
            ImmutableArray<ISymbol> oldCaptures,
            SyntaxNode oldMemberBody,
            ImmutableArray<ISymbol> newCaptures,
            ISymbol newMember,
            SyntaxNode newMemberBody,
            BidirectionalMap<SyntaxNode> map,
            [Out] ArrayBuilder<int> reverseCapturesMap,                  // {new capture index -> old capture index}
            [Out] ArrayBuilder<SyntaxNode?> newCapturesToClosureScopes,  // {new capture index -> new closure scope}
            [Out] ArrayBuilder<SyntaxNode?> oldCapturesToClosureScopes,  // {old capture index -> old closure scope}
            [Out] ArrayBuilder<RudeEditDiagnostic> diagnostics,
            out bool hasErrors,
            CancellationToken cancellationToken)
        {
            hasErrors = false;
 
            // Validate that all variables that are/were captured in the new/old body were captured in 
            // the old/new one and their type and scope haven't changed. 
            //
            // Frames are created based upon captured variables and their scopes. If the scopes haven't changed the frames won't either.
            // 
            // In future we can relax some of these limitations. 
            // - If a newly captured variable's scope is already a closure then it is ok to lift this variable to the existing closure,
            //   unless any lambda (or the containing member) that can access the variable is active. If it was active we would need 
            //   to copy the value of the local variable to the lifted field.
            //  
            //   Consider the following edit:
            //   Gen0                               Gen1
            //   ...                                ...
            //     {                                  {  
            //        int x = 1, y = 2;                  int x = 1, y = 2;
            //        F(() => x);                        F(() => x);
            //   AS-->W(y)                          AS-->W(y)
            //                                           F(() => y);
            //     }                                  }
            //   ...                                ...
            //
            // - If an "uncaptured" variable's scope still defines other captured variables it is ok to cease capturing the variable,
            //   unless any lambda (or the containing member) that can access the variable is active. If it was active we would need 
            //   to copy the value of the lifted field to the local variable (consider reverse edit in the example above).
            //
            // - While building the closure tree for the new version the compiler can recreate 
            //   the closure tree of the previous version and then map 
            //   closure scopes in the new version to the previous ones, keeping empty closures around.
 
            using var _1 = PooledDictionary<SyntaxNode, int>.GetInstance(out var oldLocalCapturesBySyntax);
            using var _2 = PooledDictionary<(SyntaxNode? Node, int Ordinal), int>.GetInstance(out var oldParameterCapturesByLambdaAndOrdinal);
 
            for (var i = 0; i < oldCaptures.Length; i++)
            {
                var oldCapture = oldCaptures[i];
 
                if (oldCapture.Kind == SymbolKind.Parameter)
                {
                    oldParameterCapturesByLambdaAndOrdinal.Add(GetParameterKey((IParameterSymbol)oldCapture, cancellationToken), i);
                }
                else
                {
                    oldLocalCapturesBySyntax.Add(oldCapture.DeclaringSyntaxReferences.Single().GetSyntax(cancellationToken), i);
                }
            }
 
            for (var newCaptureIndex = 0; newCaptureIndex < newCaptures.Length; newCaptureIndex++)
            {
                var newCapture = newCaptures[newCaptureIndex];
                int oldCaptureIndex;
 
                if (newCapture.Kind == SymbolKind.Parameter)
                {
                    var newParameterCapture = (IParameterSymbol)newCapture;
                    var newParameterKey = GetParameterKey(newParameterCapture, cancellationToken);
                    if (!TryMapParameter(newParameterKey, map.Reverse, out var oldParameterKey) ||
                        !oldParameterCapturesByLambdaAndOrdinal.TryGetValue(oldParameterKey, out oldCaptureIndex))
                    {
                        // parameter has not been captured prior the edit:
                        diagnostics.Add(new RudeEditDiagnostic(
                            RudeEditKind.CapturingVariable,
                            GetVariableDiagnosticSpan(newCapture),
                            null,
                            new[] { newCapture.Name }));
 
                        hasErrors = true;
                        continue;
                    }
 
                    // Remove the old parameter capture so that at the end we can use this hashset 
                    // to identify old captures that don't have a corresponding capture in the new version:
                    oldParameterCapturesByLambdaAndOrdinal.Remove(oldParameterKey);
                }
                else
                {
                    var newCaptureSyntax = newCapture.DeclaringSyntaxReferences.Single().GetSyntax(cancellationToken);
 
                    // variable doesn't exists in the old method or has not been captured prior the edit:
                    if (!map.Reverse.TryGetValue(newCaptureSyntax, out var mappedOldSyntax) ||
                        !oldLocalCapturesBySyntax.TryGetValue(mappedOldSyntax, out oldCaptureIndex))
                    {
                        diagnostics.Add(new RudeEditDiagnostic(
                            RudeEditKind.CapturingVariable,
                            newCapture.Locations.First().SourceSpan,
                            null,
                            new[] { newCapture.Name }));
 
                        hasErrors = true;
                        continue;
                    }
 
                    // Remove the old capture so that at the end we can use this hashset 
                    // to identify old captures that don't have a corresponding capture in the new version:
                    oldLocalCapturesBySyntax.Remove(mappedOldSyntax);
                }
 
                reverseCapturesMap[newCaptureIndex] = oldCaptureIndex;
 
                // the type and scope of parameters can't change
                if (newCapture.Kind == SymbolKind.Parameter)
                {
                    continue;
                }
 
                var oldCapture = oldCaptures[oldCaptureIndex];
 
                // Parameter capture can't be changed to local capture and vice versa
                // because parameters can't be introduced or deleted during EnC 
                // (we checked above for changes in lambda signatures).
                // Also range variables can't be mapped to other variables since they have 
                // different kinds of declarator syntax nodes.
                Debug.Assert(oldCapture.Kind == newCapture.Kind);
 
                // Range variables don't have types. Each transparent identifier (range variable use)
                // might have a different type. Changing these types is ok as long as the containing lambda
                // signatures remain unchanged, which we validate for all lambdas in general.
                // 
                // The scope of a transparent identifier is the containing lambda body. Since we verify that
                // each lambda body accesses the same captured variables (including range variables) 
                // the corresponding scopes are guaranteed to be preserved as well.
                if (oldCapture.Kind == SymbolKind.RangeVariable)
                {
                    continue;
                }
 
                // rename:
                // Note that the name has to match exactly even in VB, since we can't rename a field.
                // Consider: We could allow rename by emitting some special debug info for the field.
                if (newCapture.Name != oldCapture.Name)
                {
                    diagnostics.Add(new RudeEditDiagnostic(
                        RudeEditKind.RenamingCapturedVariable,
                        newCapture.Locations.First().SourceSpan,
                        null,
                        new[] { oldCapture.Name, newCapture.Name }));
 
                    hasErrors = true;
                    continue;
                }
 
                // type check
                var oldTypeOpt = GetType(oldCapture);
                var newTypeOpt = GetType(newCapture);
 
                if (!TypesEquivalent(oldTypeOpt, newTypeOpt, exact: false))
                {
                    diagnostics.Add(new RudeEditDiagnostic(
                        RudeEditKind.ChangingCapturedVariableType,
                        GetVariableDiagnosticSpan(newCapture),
                        null,
                        new[] { newCapture.Name, oldTypeOpt.ToDisplayString(ErrorDisplayFormat) }));
 
                    hasErrors = true;
                    continue;
                }
 
                // scope check:
                var oldScopeOpt = GetCapturedVariableScope(oldCapture, oldMemberBody, cancellationToken);
                var newScopeOpt = GetCapturedVariableScope(newCapture, newMemberBody, cancellationToken);
                if (!AreEquivalentClosureScopes(oldScopeOpt, newScopeOpt, map.Reverse))
                {
                    diagnostics.Add(new RudeEditDiagnostic(
                        RudeEditKind.ChangingCapturedVariableScope,
                        GetVariableDiagnosticSpan(newCapture),
                        null,
                        new[] { newCapture.Name }));
 
                    hasErrors = true;
                    continue;
                }
 
                newCapturesToClosureScopes[newCaptureIndex] = newScopeOpt;
                oldCapturesToClosureScopes[oldCaptureIndex] = oldScopeOpt;
            }
 
            // What's left in oldCapturesBySyntax are captured variables in the previous version
            // that have no corresponding captured variables in the new version. 
            // Report a rude edit for all such variables.
 
            if (oldParameterCapturesByLambdaAndOrdinal.Count > 0)
            {
                // syntax-less parameters are not included:
                var newMemberParametersWithSyntax = newMember.GetParameters();
 
                // uncaptured parameters:
                foreach (var ((oldContainingLambdaSyntax, ordinal), oldCaptureIndex) in oldParameterCapturesByLambdaAndOrdinal)
                {
                    var oldCapture = oldCaptures[oldCaptureIndex];
 
                    TextSpan span;
                    if (ordinal < 0)
                    {
                        // this parameter:
                        span = GetThisParameterDiagnosticSpan(newMember);
                    }
                    else if (oldContainingLambdaSyntax != null)
                    {
                        // lambda:
                        span = GetLambdaParameterDiagnosticSpan(oldContainingLambdaSyntax, ordinal);
                    }
                    else if (oldCapture.IsImplicitValueParameter())
                    {
                        // value parameter of a property/indexer setter, event adder/remover:
                        span = newMember.Locations.First().SourceSpan;
                    }
                    else
                    {
                        // method or property:
                        span = GetVariableDiagnosticSpan(newMemberParametersWithSyntax[ordinal]);
                    }
 
                    diagnostics.Add(new RudeEditDiagnostic(
                        RudeEditKind.NotCapturingVariable,
                        span,
                        null,
                        new[] { oldCapture.Name }));
                }
 
                hasErrors = true;
            }
 
            if (oldLocalCapturesBySyntax.Count > 0)
            {
                // uncaptured or deleted variables:
                foreach (var entry in oldLocalCapturesBySyntax)
                {
                    var oldCaptureNode = entry.Key;
                    var oldCaptureIndex = entry.Value;
                    var name = oldCaptures[oldCaptureIndex].Name;
                    if (map.Forward.TryGetValue(oldCaptureNode, out var newCaptureNode))
                    {
                        diagnostics.Add(new RudeEditDiagnostic(
                            RudeEditKind.NotCapturingVariable,
                            newCaptureNode.Span,
                            null,
                            new[] { name }));
                    }
                    else
                    {
                        diagnostics.Add(new RudeEditDiagnostic(
                            RudeEditKind.DeletingCapturedVariable,
                            GetDeletedNodeDiagnosticSpan(map.Forward, oldCaptureNode),
                            null,
                            new[] { name }));
                    }
                }
 
                hasErrors = true;
            }
        }
 
        private void ReportLambdaSignatureRudeEdits(
            ArrayBuilder<RudeEditDiagnostic> diagnostics,
            SemanticModel oldModel,
            SyntaxNode oldLambdaBody,
            SemanticModel newModel,
            SyntaxNode newLambdaBody,
            EditAndContinueCapabilitiesGrantor capabilities,
            out bool hasSignatureErrors,
            CancellationToken cancellationToken)
        {
            hasSignatureErrors = false;
 
            var newLambda = GetLambda(newLambdaBody);
            var oldLambda = GetLambda(oldLambdaBody);
 
            Debug.Assert(IsNestedFunction(newLambda) == IsNestedFunction(oldLambda));
 
            // queries are analyzed separately
            if (!IsNestedFunction(newLambda))
            {
                return;
            }
 
            if (IsLocalFunction(oldLambda) != IsLocalFunction(newLambda))
            {
                ReportUpdateRudeEdit(diagnostics, RudeEditKind.SwitchBetweenLambdaAndLocalFunction, newLambda);
                hasSignatureErrors = true;
                return;
            }
 
            var oldLambdaSymbol = GetLambdaExpressionSymbol(oldModel, oldLambda, cancellationToken);
            var newLambdaSymbol = GetLambdaExpressionSymbol(newModel, newLambda, cancellationToken);
 
            // signature validation:
            if (!ParameterTypesEquivalent(oldLambdaSymbol.Parameters, newLambdaSymbol.Parameters, exact: false))
            {
                ReportUpdateRudeEdit(diagnostics, RudeEditKind.ChangingLambdaParameters, newLambda);
                hasSignatureErrors = true;
            }
            else if (!ReturnTypesEquivalent(oldLambdaSymbol, newLambdaSymbol, exact: false))
            {
                ReportUpdateRudeEdit(diagnostics, RudeEditKind.ChangingLambdaReturnType, newLambda);
                hasSignatureErrors = true;
            }
            else if (!TypeParametersEquivalent(oldLambdaSymbol.TypeParameters, newLambdaSymbol.TypeParameters, exact: false))
            {
                ReportUpdateRudeEdit(diagnostics, RudeEditKind.ChangingTypeParameters, newLambda);
                hasSignatureErrors = true;
            }
 
            if (hasSignatureErrors)
            {
                return;
            }
 
            // custom attributes
 
            ReportCustomAttributeRudeEdits(diagnostics, oldLambdaSymbol, newLambdaSymbol, newLambda, newModel.Compilation, capabilities, out _, out _, cancellationToken);
 
            for (var i = 0; i < oldLambdaSymbol.Parameters.Length; i++)
            {
                ReportCustomAttributeRudeEdits(diagnostics, oldLambdaSymbol.Parameters[i], newLambdaSymbol.Parameters[i], newLambda, newModel.Compilation, capabilities, out _, out _, cancellationToken);
            }
 
            for (var i = 0; i < oldLambdaSymbol.TypeParameters.Length; i++)
            {
                ReportCustomAttributeRudeEdits(diagnostics, oldLambdaSymbol.TypeParameters[i], newLambdaSymbol.TypeParameters[i], newLambda, newModel.Compilation, capabilities, out _, out _, cancellationToken);
            }
        }
 
        private static ITypeSymbol GetType(ISymbol localOrParameter)
            => localOrParameter.Kind switch
            {
                SymbolKind.Parameter => ((IParameterSymbol)localOrParameter).Type,
                SymbolKind.Local => ((ILocalSymbol)localOrParameter).Type,
                _ => throw ExceptionUtilities.UnexpectedValue(localOrParameter.Kind),
            };
 
        private SyntaxNode GetCapturedVariableScope(ISymbol localOrParameter, SyntaxNode memberBody, CancellationToken cancellationToken)
        {
            Debug.Assert(localOrParameter.Kind != SymbolKind.RangeVariable);
 
            if (localOrParameter.Kind == SymbolKind.Parameter)
            {
                var member = localOrParameter.ContainingSymbol;
 
                // lambda parameters and C# constructor parameters are lifted to their own scope:
                if ((member as IMethodSymbol)?.MethodKind == MethodKind.AnonymousFunction || HasParameterClosureScope(member))
                {
                    var result = localOrParameter.DeclaringSyntaxReferences.Single().GetSyntax(cancellationToken);
                    Debug.Assert(IsLambda(result));
                    return result;
                }
 
                return memberBody;
            }
 
            var node = localOrParameter.DeclaringSyntaxReferences.Single().GetSyntax(cancellationToken);
            while (true)
            {
                RoslynDebug.Assert(node is object);
                if (IsClosureScope(node))
                {
                    return node;
                }
 
                node = node.Parent;
            }
        }
 
        private static bool AreEquivalentClosureScopes(SyntaxNode oldScopeOpt, SyntaxNode newScopeOpt, IReadOnlyDictionary<SyntaxNode, SyntaxNode> reverseMap)
        {
            if (oldScopeOpt == null || newScopeOpt == null)
            {
                return oldScopeOpt == newScopeOpt;
            }
 
            return reverseMap.TryGetValue(newScopeOpt, out var mappedScope) && mappedScope == oldScopeOpt;
        }
 
        #endregion
 
        #region State Machines
 
        private void ReportStateMachineRudeEdits(
            Compilation oldCompilation,
            ISymbol oldMember,
            SyntaxNode newBody,
            ArrayBuilder<RudeEditDiagnostic> diagnostics)
        {
            // Only methods, local functions and anonymous functions can be async/iterators machines, 
            // but don't assume so to be resiliant against errors in code.
            if (oldMember is not IMethodSymbol oldMethod)
            {
                return;
            }
 
            var stateMachineAttributeQualifiedName = oldMethod.IsAsync
                ? "System.Runtime.CompilerServices.AsyncStateMachineAttribute"
                : "System.Runtime.CompilerServices.IteratorStateMachineAttribute";
 
            // We assume that the attributes, if exist, are well formed.
            // If not an error will be reported during EnC delta emit.
 
            // Report rude edit if the type is not found in the compilation.
            // Consider: This diagnostic is cached in the document analysis,
            // so it could happen that the attribute type is added later to
            // the compilation and we continue to report the diagnostic.
            // We could report rude edit when adding these types or flush all
            // (or specific) document caches. This is not a common scenario though,
            // since the attribute has been long defined in the BCL.
            if (oldCompilation.GetTypeByMetadataName(stateMachineAttributeQualifiedName) == null)
            {
                diagnostics.Add(new RudeEditDiagnostic(
                    RudeEditKind.UpdatingStateMachineMethodMissingAttribute,
                    GetBodyDiagnosticSpan(newBody, EditKind.Update),
                    newBody,
                    new[] { stateMachineAttributeQualifiedName }));
            }
        }
 
        #endregion
 
        #endregion
 
        #region Helpers
 
        private static SyntaxNode? TryGetNode(SyntaxNode root, int position)
            => root.FullSpan.Contains(position) ? root.FindToken(position).Parent : null;
 
        internal static void AddNodes<T>(ArrayBuilder<SyntaxNode> nodes, SyntaxList<T> list)
            where T : SyntaxNode
        {
            foreach (var node in list)
            {
                nodes.Add(node);
            }
        }
 
        internal static void AddNodes<T>(ArrayBuilder<SyntaxNode> nodes, SeparatedSyntaxList<T>? list)
            where T : SyntaxNode
        {
            if (list.HasValue)
            {
                foreach (var node in list.Value)
                {
                    nodes.Add(node);
                }
            }
        }
 
        private sealed class TypedConstantComparer : IEqualityComparer<TypedConstant>
        {
            public static TypedConstantComparer Instance = new TypedConstantComparer();
 
            public bool Equals(TypedConstant x, TypedConstant y)
                => x.Kind.Equals(y.Kind) &&
                   x.IsNull.Equals(y.IsNull) &&
                   SymbolEquivalenceComparer.Instance.Equals(x.Type, y.Type) &&
                   x.Kind switch
                   {
                       TypedConstantKind.Array => x.Values.SequenceEqual(y.Values, TypedConstantComparer.Instance),
                       _ => object.Equals(x.Value, y.Value)
                   };
 
            public int GetHashCode(TypedConstant obj)
                => obj.GetHashCode();
        }
 
        private sealed class NamedArgumentComparer : IEqualityComparer<KeyValuePair<string, TypedConstant>>
        {
            public static NamedArgumentComparer Instance = new NamedArgumentComparer();
 
            public bool Equals(KeyValuePair<string, TypedConstant> x, KeyValuePair<string, TypedConstant> y)
                => x.Key.Equals(y.Key) &&
                   TypedConstantComparer.Instance.Equals(x.Value, y.Value);
 
            public int GetHashCode(KeyValuePair<string, TypedConstant> obj)
                 => obj.GetHashCode();
        }
 
        private static bool IsGlobalMain(ISymbol symbol)
            => symbol is IMethodSymbol { Name: WellKnownMemberNames.TopLevelStatementsEntryPointMethodName };
 
        private static bool InGenericContext(ISymbol symbol, out bool isGenericMethod)
        {
            var current = symbol;
 
            while (true)
            {
                if (current is IMethodSymbol { Arity: > 0 })
                {
                    isGenericMethod = true;
                    return true;
                }
 
                if (current is INamedTypeSymbol { Arity: > 0 })
                {
                    isGenericMethod = false;
                    return true;
                }
 
                current = current.ContainingSymbol;
                if (current == null)
                {
                    isGenericMethod = false;
                    return false;
                }
            }
        }
 
        #endregion
 
        #region Testing
 
        internal TestAccessor GetTestAccessor()
            => new(this);
 
        internal readonly struct TestAccessor
        {
            private readonly AbstractEditAndContinueAnalyzer _abstractEditAndContinueAnalyzer;
 
            public TestAccessor(AbstractEditAndContinueAnalyzer abstractEditAndContinueAnalyzer)
                => _abstractEditAndContinueAnalyzer = abstractEditAndContinueAnalyzer;
 
            internal void ReportTopLevelSyntacticRudeEdits(ArrayBuilder<RudeEditDiagnostic> diagnostics, EditScript<SyntaxNode> syntacticEdits, Dictionary<SyntaxNode, EditKind> editMap)
                => _abstractEditAndContinueAnalyzer.ReportTopLevelSyntacticRudeEdits(diagnostics, syntacticEdits, editMap);
 
            internal BidirectionalMap<SyntaxNode> ComputeMap(
                Match<SyntaxNode> bodyMatch,
                ArrayBuilder<ActiveNode> activeNodes,
                ref Dictionary<SyntaxNode, LambdaInfo>? lazyActiveOrMatchedLambdas,
                ArrayBuilder<RudeEditDiagnostic> diagnostics)
            {
                return _abstractEditAndContinueAnalyzer.ComputeMap(bodyMatch, activeNodes, ref lazyActiveOrMatchedLambdas, diagnostics);
            }
 
            internal Match<SyntaxNode> ComputeBodyMatch(
                SyntaxNode oldBody,
                SyntaxNode newBody,
                ActiveNode[] activeNodes,
                ArrayBuilder<RudeEditDiagnostic> diagnostics,
                out bool oldHasStateMachineSuspensionPoint,
                out bool newHasStateMachineSuspensionPoint)
            {
                return _abstractEditAndContinueAnalyzer.ComputeBodyMatch(oldBody, newBody, activeNodes, diagnostics, out oldHasStateMachineSuspensionPoint, out newHasStateMachineSuspensionPoint);
            }
        }
 
        #endregion
    }
}