File: Rename\ConflictEngine\ConflictResolver.Session.cs
Web Access
Project: src\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// 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.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeCleanup;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Rename.ConflictEngine;
 
internal static partial class ConflictResolver
{
    /// <summary>
    /// Helper class to track the state necessary for finding/resolving conflicts in a 
    /// rename session.
    /// </summary>
    private sealed class Session
    {
        // Set of All Locations that will be renamed (does not include non-reference locations that need to be checked for conflicts)
        private readonly SymbolicRenameLocations _renameLocationSet;
 
        // Rename Symbol's Source Location
        private readonly Location _renameSymbolDeclarationLocation;
        private readonly DocumentId _documentIdOfRenameSymbolDeclaration;
        private readonly string _originalText;
        private readonly string _replacementText;
        private readonly ImmutableArray<SymbolKey> _nonConflictSymbolKeys;
        private readonly CancellationToken _cancellationToken;
 
        private readonly RenameAnnotation _renamedSymbolDeclarationAnnotation = new();
 
        private readonly AnnotationTable<RenameAnnotation> _renameAnnotations;
 
        private bool _replacementTextValid;
        private bool _documentOfRenameSymbolHasBeenRenamed;
 
        public Session(
            SymbolicRenameLocations renameLocationSet,
            Location renameSymbolDeclarationLocation,
            string replacementText,
            ImmutableArray<SymbolKey> nonConflictSymbolKeys,
            CancellationToken cancellationToken)
        {
            _renameLocationSet = renameLocationSet;
            _renameSymbolDeclarationLocation = renameSymbolDeclarationLocation;
            _originalText = renameLocationSet.Symbol.Name;
            _replacementText = replacementText;
            _nonConflictSymbolKeys = nonConflictSymbolKeys;
            _cancellationToken = cancellationToken;
 
            _replacementTextValid = true;
 
            // only process documents which possibly contain the identifiers.
            _documentIdOfRenameSymbolDeclaration = renameLocationSet.Solution.GetRequiredDocument(renameSymbolDeclarationLocation.SourceTree!).Id;
 
            _renameAnnotations = new AnnotationTable<RenameAnnotation>(RenameAnnotation.Kind);
        }
 
        private SymbolRenameOptions RenameOptions => _renameLocationSet.Options;
 
        private readonly struct ConflictLocationInfo
        {
            // The span of the Node that needs to be complexified 
            public readonly TextSpan ComplexifiedSpan;
            public readonly DocumentId DocumentId;
 
            // The identifier span that needs to be checked for conflict
            public readonly TextSpan OriginalIdentifierSpan;
 
            public ConflictLocationInfo(RelatedLocation location)
            {
                Debug.Assert(location.ComplexifiedTargetSpan.Contains(location.ConflictCheckSpan) || location.Type == RelatedLocationType.UnresolvableConflict);
                this.ComplexifiedSpan = location.ComplexifiedTargetSpan;
                this.DocumentId = location.DocumentId;
                this.OriginalIdentifierSpan = location.ConflictCheckSpan;
            }
        }
 
        // The method which performs rename, resolves the conflict locations and returns the result of the rename operation
        public async Task<MutableConflictResolution> ResolveConflictsAsync()
        {
            try
            {
                var (documentsIdsToBeCheckedForConflict, possibleNameConflicts) = await FindDocumentsAndPossibleNameConflictsAsync().ConfigureAwait(false);
                var baseSolution = _renameLocationSet.Solution;
 
                var dependencyGraph = baseSolution.GetProjectDependencyGraph();
                var topologicallySortedProjects = dependencyGraph.GetTopologicallySortedProjects(_cancellationToken).ToList();
 
                // Process rename one project at a time to improve caching and reduce syntax tree serialization.
                var documentsGroupedByTopologicallySortedProjectId = documentsIdsToBeCheckedForConflict
                    .GroupBy(d => d.ProjectId)
                    .OrderBy(g => topologicallySortedProjects.IndexOf(g.Key));
 
                _replacementTextValid = IsIdentifierValid_Worker(baseSolution, _replacementText, documentsGroupedByTopologicallySortedProjectId.Select(g => g.Key));
                var renamedSpansTracker = new RenamedSpansTracker();
                var conflictResolution = new MutableConflictResolution(baseSolution, renamedSpansTracker, _replacementText, _replacementTextValid);
 
                var intermediateSolution = conflictResolution.OldSolution;
                foreach (var documentsByProject in documentsGroupedByTopologicallySortedProjectId)
                {
                    var conflictLocations = ImmutableHashSet<ConflictLocationInfo>.Empty;
 
                    var documentIdsThatGetsAnnotatedAndRenamed = new HashSet<DocumentId>(documentsByProject);
                    // Rename is going to be in 5 phases.
                    // 1st phase - Does a simple token replacement
                    // If the 1st phase results in conflict then we perform then:
                    //      2nd phase is to expand and simplify only the reference locations with conflicts
                    //      3rd phase is to expand and simplify all the conflict locations (both reference and non-reference)
                    // If there are unresolved Conflicts after the 3rd phase then in 4th phase, 
                    //      We complexify and resolve locations that were resolvable and for the other locations we perform the normal token replacement like the first the phase.
                    // If the OptionSet has RenameFile to true, we rename files with the type declaration
                    for (var phase = 0; phase < 4; phase++)
                    {
                        // Step 1:
                        // The rename process and annotation for the bookkeeping is performed in one-step
                        // The Process in short is,
                        // 1. If renaming a token which is no conflict then replace the token and make a map of the oldspan to the newspan
                        // 2. If we encounter a node that has to be expanded( because there was a conflict in previous phase), we expand it.
                        //    If the node happens to contain a token that needs to be renamed then we annotate it and rename it after expansion else just expand and proceed
                        // 3. Through the whole process we maintain a map of the oldspan to newspan. In case of expansion & rename, we map the expanded node and the renamed token
                        conflictResolution.UpdateCurrentSolution(await AnnotateAndRename_WorkerAsync(
                            baseSolution,
                            conflictResolution.CurrentSolution,
                            documentIdsThatGetsAnnotatedAndRenamed,
                            _renameLocationSet.Locations,
                            renamedSpansTracker,
                            _replacementTextValid,
                            conflictLocations,
                            possibleNameConflicts).ConfigureAwait(false));
 
                        // Step 2: Check for conflicts in the renamed solution
                        var foundResolvableConflicts = await IdentifyConflictsAsync(
                            documentIdsForConflictResolution: documentIdsThatGetsAnnotatedAndRenamed,
                            allDocumentIdsInProject: documentsByProject,
                            projectId: documentsByProject.Key,
                            conflictResolution,
                            conflictLocations).ConfigureAwait(false);
 
                        if (!foundResolvableConflicts || phase == 3)
                        {
                            break;
                        }
 
                        if (phase == 0)
                        {
                            Contract.ThrowIfTrue(conflictLocations.Count != 0, "We're the first phase, so we should have no conflict locations yet");
 
                            conflictLocations = conflictResolution.RelatedLocations
                                .Where(loc => documentIdsThatGetsAnnotatedAndRenamed.Contains(loc.DocumentId) && loc.Type == RelatedLocationType.PossiblyResolvableConflict && loc.IsReference)
                                .Select(loc => new ConflictLocationInfo(loc))
                                .ToImmutableHashSet();
 
                            // If there were no conflicting locations in references, then the first conflict phase has to be skipped.
                            if (conflictLocations.Count == 0)
                            {
                                phase++;
                            }
                        }
 
                        if (phase == 1)
                        {
                            conflictLocations =
                            [
                                .. conflictLocations,
                                .. conflictResolution.RelatedLocations
                                    .Where(loc => documentIdsThatGetsAnnotatedAndRenamed.Contains(loc.DocumentId) && loc.Type == RelatedLocationType.PossiblyResolvableConflict)
                                    .Select(loc => new ConflictLocationInfo(loc)),
                            ];
                        }
 
                        // Set the documents with conflicts that need to be processed in the next phase.
                        // Note that we need to get the conflictLocations here since we're going to remove some locations below if phase == 2
                        documentIdsThatGetsAnnotatedAndRenamed = new HashSet<DocumentId>(conflictLocations.Select(l => l.DocumentId));
 
                        if (phase == 2)
                        {
                            // After phase 2, if there are still conflicts then remove the conflict locations from being expanded
                            var unresolvedLocations = conflictResolution.RelatedLocations
                                .Where(l => (l.Type & RelatedLocationType.UnresolvedConflict) != 0)
                                .Select(l => Tuple.Create(l.ComplexifiedTargetSpan, l.DocumentId)).Distinct();
 
                            conflictLocations = conflictLocations
                                .Where(l => !unresolvedLocations.Any(c => c.Item2 == l.DocumentId && c.Item1.Contains(l.OriginalIdentifierSpan)))
                                .ToImmutableHashSet();
                        }
 
                        // Clean up side effects from rename before entering the next phase
                        conflictResolution.ClearDocuments(documentIdsThatGetsAnnotatedAndRenamed);
                    }
 
                    // Step 3: Simplify the project
                    conflictResolution.UpdateCurrentSolution(await renamedSpansTracker.SimplifyAsync(conflictResolution.CurrentSolution, documentsByProject, _replacementTextValid, _renameAnnotations, _cancellationToken).ConfigureAwait(false));
                    intermediateSolution = await conflictResolution.RemoveAllRenameAnnotationsAsync(
                        intermediateSolution, documentsByProject, _renameAnnotations, _cancellationToken).ConfigureAwait(false);
                    conflictResolution.UpdateCurrentSolution(intermediateSolution);
                }
 
                // This rename could break implicit references of this symbol (e.g. rename MoveNext on a collection like type in a 
                // foreach/for each statement
                var renamedSymbolInNewSolution = await GetRenamedSymbolInCurrentSolutionAsync(conflictResolution).ConfigureAwait(false);
 
                if (IsRenameValid(conflictResolution, renamedSymbolInNewSolution))
                {
                    await AddImplicitConflictsAsync(
                        renamedSymbolInNewSolution,
                        _renameLocationSet.Symbol,
                        _renameLocationSet.ImplicitLocations,
                        await conflictResolution.CurrentSolution.GetRequiredDocument(_documentIdOfRenameSymbolDeclaration).GetRequiredSemanticModelAsync(_cancellationToken).ConfigureAwait(false),
                        _renameSymbolDeclarationLocation,
                        renamedSpansTracker.GetAdjustedPosition(_renameSymbolDeclarationLocation.SourceSpan.Start, _documentIdOfRenameSymbolDeclaration),
                        conflictResolution,
                        _cancellationToken).ConfigureAwait(false);
                }
 
                for (var i = 0; i < conflictResolution.RelatedLocations.Count; i++)
                {
                    var relatedLocation = conflictResolution.RelatedLocations[i];
                    if (relatedLocation.Type == RelatedLocationType.PossiblyResolvableConflict)
                        conflictResolution.RelatedLocations[i] = relatedLocation.WithType(RelatedLocationType.UnresolvedConflict);
                }
 
#if DEBUG
                await DebugVerifyNoErrorsAsync(conflictResolution, documentsIdsToBeCheckedForConflict).ConfigureAwait(false);
#endif
 
                // Step 5: Rename declaration files
                if (_replacementTextValid && RenameOptions.RenameFile)
                {
                    var definitionLocations = _renameLocationSet.Symbol.Locations;
                    var definitionDocuments = definitionLocations
                        .Select(l => conflictResolution.OldSolution.GetRequiredDocument(l.SourceTree!))
                        .Distinct();
 
                    if (definitionDocuments.Count() == 1)
                    {
                        // At the moment, only single document renaming is allowed
                        conflictResolution.RenameDocumentToMatchNewSymbol(definitionDocuments.Single());
                    }
                }
 
                return conflictResolution;
            }
            catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, ErrorSeverity.Critical))
            {
                throw ExceptionUtilities.Unreachable();
            }
        }
 
#if DEBUG
        private async Task DebugVerifyNoErrorsAsync(MutableConflictResolution conflictResolution, IEnumerable<DocumentId> documents)
        {
            var documentIdErrorStateLookup = new Dictionary<DocumentId, bool>();
 
            // we only check for the documentIds we add annotations to, which is a subset of the ones we're going 
            // to change the syntax in.
            foreach (var documentId in documents)
            {
                // remember if there were issues in the document prior to renaming it.
                var originalDoc = conflictResolution.OldSolution.GetRequiredDocument(documentId);
                documentIdErrorStateLookup.Add(documentId, await originalDoc.HasAnyErrorsAsync(_cancellationToken).ConfigureAwait(false));
            }
 
            // We want to ignore few error message introduced by rename because the user is wantedly doing it.
            var ignoreErrorCodes = new List<string>
            {
                "BC30420", // BC30420 - Sub Main missing in VB Project
                "CS5001" // CS5001 - Missing Main in C# Project
            };
 
            // only check if rename thinks it was successful
            if (conflictResolution.ReplacementTextValid && conflictResolution.RelatedLocations.All(loc => (loc.Type & RelatedLocationType.UnresolvableConflict) == 0))
            {
                foreach (var documentId in documents)
                {
                    // only check documents that had no errors before rename (we might have 
                    // fixed them because of rename).  Also, don't bother checking if a custom
                    // callback was provided.  The caller might be ok with a rename that introduces
                    // errors.
                    if (!documentIdErrorStateLookup[documentId] && _nonConflictSymbolKeys.IsDefault)
                    {
                        await conflictResolution.CurrentSolution.GetRequiredDocument(documentId).VerifyNoErrorsAsync("Rename introduced errors in error-free code", _cancellationToken, ignoreErrorCodes).ConfigureAwait(false);
                    }
                }
            }
        }
#endif
 
        /// <summary>
        /// Find conflicts in the new solution 
        /// </summary>
        private async Task<bool> IdentifyConflictsAsync(
            HashSet<DocumentId> documentIdsForConflictResolution,
            IEnumerable<DocumentId> allDocumentIdsInProject,
            ProjectId projectId,
            MutableConflictResolution conflictResolution,
            ImmutableHashSet<ConflictLocationInfo> conflictLocations)
        {
            try
            {
                _documentOfRenameSymbolHasBeenRenamed |= documentIdsForConflictResolution.Contains(_documentIdOfRenameSymbolDeclaration);
 
                // Get the renamed symbol in complexified new solution
                var renamedSymbolInNewSolution = await GetRenamedSymbolInCurrentSolutionAsync(conflictResolution).ConfigureAwait(false);
 
                // if the text replacement is invalid, we just did a simple token replacement.
                // Therefore we don't need more mapping information and can skip the rest of 
                // the loop body.
                if (!IsRenameValid(conflictResolution, renamedSymbolInNewSolution))
                {
                    foreach (var documentId in documentIdsForConflictResolution)
                    {
                        var newDocument = conflictResolution.CurrentSolution.GetRequiredDocument(documentId);
                        var syntaxRoot = await newDocument.GetRequiredSyntaxRootAsync(_cancellationToken).ConfigureAwait(false);
 
                        var nodesOrTokensWithConflictCheckAnnotations = GetNodesOrTokensToCheckForConflicts(syntaxRoot);
                        foreach (var (syntax, annotation) in nodesOrTokensWithConflictCheckAnnotations)
                        {
                            if (annotation.IsRenameLocation)
                            {
                                conflictResolution.AddRelatedLocation(new RelatedLocation(
                                    annotation.OriginalSpan, documentId, RelatedLocationType.UnresolvedConflict));
                            }
                        }
                    }
 
                    return false;
                }
 
                var reverseMappedLocations = new Dictionary<Location, Location>();
 
                // If we were giving any non-conflict-symbols then ensure that we know what those symbols are in
                // the current project post after our edits so far.
                var currentProject = conflictResolution.CurrentSolution.GetRequiredProject(projectId);
                var nonConflictSymbols = await GetNonConflictSymbolsAsync(currentProject).ConfigureAwait(false);
 
                foreach (var documentId in documentIdsForConflictResolution)
                {
                    var newDocument = conflictResolution.CurrentSolution.GetRequiredDocument(documentId);
                    var syntaxRoot = await newDocument.GetRequiredSyntaxRootAsync(_cancellationToken).ConfigureAwait(false);
                    var baseDocument = conflictResolution.OldSolution.GetRequiredDocument(documentId);
                    var baseSyntaxTree = await baseDocument.GetRequiredSyntaxTreeAsync(_cancellationToken).ConfigureAwait(false);
                    var baseRoot = await baseDocument.GetRequiredSyntaxRootAsync(_cancellationToken).ConfigureAwait(false);
                    SemanticModel? newDocumentSemanticModel = null;
                    var syntaxFactsService = newDocument.Project.Services.GetRequiredService<ISyntaxFactsService>();
 
                    // Get all tokens that need conflict check
                    var nodesOrTokensWithConflictCheckAnnotations = GetNodesOrTokensToCheckForConflicts(syntaxRoot);
 
                    var complexifiedLocationSpanForThisDocument = conflictLocations
                        .Where(t => t.DocumentId == documentId)
                        .Select(t => t.OriginalIdentifierSpan)
                        .ToSet();
 
                    foreach (var (syntax, annotation) in nodesOrTokensWithConflictCheckAnnotations)
                    {
                        var tokenOrNode = syntax;
                        var conflictAnnotation = annotation;
                        reverseMappedLocations[tokenOrNode.GetLocation()!] = baseSyntaxTree.GetLocation(conflictAnnotation.OriginalSpan);
                        var originalLocation = conflictAnnotation.OriginalSpan;
                        ImmutableArray<ISymbol> newReferencedSymbols = default;
 
                        var hasConflict = _renameAnnotations.HasAnnotation(tokenOrNode, RenameInvalidIdentifierAnnotation.Instance);
                        if (!hasConflict)
                        {
                            newDocumentSemanticModel ??= await newDocument.GetRequiredSemanticModelAsync(_cancellationToken).ConfigureAwait(false);
                            newReferencedSymbols = GetSymbolsInNewSolution(newDocument, newDocumentSemanticModel, conflictAnnotation, tokenOrNode);
 
                            // The semantic correctness, after rename, for each token of interest in the
                            // rename context is performed by getting the symbol pointed by each token 
                            // and obtain the Symbol's First Ordered Location's  Span-Start and check to
                            // see if it is the same as before from the base solution. During rename, 
                            // the spans would have been modified and so we need to adjust the old position
                            // to the new position for which we use the renameSpanTracker, which was tracking
                            // & mapping the old span -> new span during rename
                            hasConflict =
                                !IsConflictFreeChange(newReferencedSymbols, nonConflictSymbols) &&
                                await CheckForConflictAsync(conflictResolution, renamedSymbolInNewSolution, conflictAnnotation, newReferencedSymbols).ConfigureAwait(false);
 
                            if (!hasConflict && !conflictAnnotation.IsInvocationExpression)
                                hasConflict = LocalVariableConflictPerLanguage((SyntaxToken)tokenOrNode, newDocument, newReferencedSymbols);
                        }
 
                        if (!hasConflict)
                        {
                            if (conflictAnnotation.IsRenameLocation)
                            {
                                conflictResolution.AddRelatedLocation(
                                    new RelatedLocation(originalLocation,
                                    documentId,
                                    complexifiedLocationSpanForThisDocument.Contains(originalLocation) ? RelatedLocationType.ResolvedReferenceConflict : RelatedLocationType.NoConflict,
                                    IsReference: true));
                            }
                            else
                            {
                                // if a complexified location was not a reference location, then it was a resolved conflict of a non reference location
                                if (!conflictAnnotation.IsOriginalTextLocation && complexifiedLocationSpanForThisDocument.Contains(originalLocation))
                                {
                                    conflictResolution.AddRelatedLocation(
                                        new RelatedLocation(originalLocation,
                                        documentId,
                                        RelatedLocationType.ResolvedNonReferenceConflict,
                                        IsReference: false));
                                }
                            }
                        }
                        else
                        {
                            var baseToken = baseRoot.FindToken(conflictAnnotation.OriginalSpan.Start, true);
                            var complexifiedTarget = GetExpansionTargetForLocationPerLanguage(baseToken, baseDocument);
                            conflictResolution.AddRelatedLocation(new RelatedLocation(
                                originalLocation,
                                documentId,
                                complexifiedTarget != null ? RelatedLocationType.PossiblyResolvableConflict : RelatedLocationType.UnresolvableConflict,
                                IsReference: conflictAnnotation.IsRenameLocation,
                                ComplexifiedTargetSpan: complexifiedTarget != null ? complexifiedTarget.Span : default));
                        }
                    }
                }
 
                // there are more conflicts that cannot be identified by checking if the tokens still reference the same
                // symbol. These conflicts are mostly language specific. A good example is a member with the same name
                // as the parent (yes I know, this is a simplification).
                if (_documentIdOfRenameSymbolDeclaration.ProjectId == projectId)
                {
                    // Calculating declaration conflicts may require location mapping in documents
                    // that were not otherwise being processed in the current rename phase, so add
                    // the annotated spans in these documents to reverseMappedLocations.
                    foreach (var unprocessedDocumentIdWithPotentialDeclarationConflicts in allDocumentIdsInProject.Where(d => !documentIdsForConflictResolution.Contains(d)))
                    {
                        var newDocument = conflictResolution.CurrentSolution.GetRequiredDocument(unprocessedDocumentIdWithPotentialDeclarationConflicts);
                        var syntaxRoot = await newDocument.GetRequiredSyntaxRootAsync(_cancellationToken).ConfigureAwait(false);
                        var baseDocument = conflictResolution.OldSolution.GetRequiredDocument(unprocessedDocumentIdWithPotentialDeclarationConflicts);
                        var baseSyntaxTree = await baseDocument.GetRequiredSyntaxTreeAsync(_cancellationToken).ConfigureAwait(false);
 
                        var nodesOrTokensWithConflictCheckAnnotations = GetNodesOrTokensToCheckForConflicts(syntaxRoot);
                        foreach (var (syntax, annotation) in nodesOrTokensWithConflictCheckAnnotations)
                        {
                            var tokenOrNode = syntax;
                            var conflictAnnotation = annotation;
                            reverseMappedLocations[tokenOrNode.GetLocation()!] = baseSyntaxTree.GetLocation(conflictAnnotation.OriginalSpan);
                        }
                    }
 
                    var referencedSymbols = _renameLocationSet.ReferencedSymbols;
                    var renameSymbol = _renameLocationSet.Symbol;
                    await AddDeclarationConflictsAsync(
                        renamedSymbolInNewSolution, renameSymbol, referencedSymbols, conflictResolution, reverseMappedLocations, _cancellationToken).ConfigureAwait(false);
                }
 
                return conflictResolution.RelatedLocations.Any(r => r.Type == RelatedLocationType.PossiblyResolvableConflict);
            }
            catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, ErrorSeverity.Critical))
            {
                throw ExceptionUtilities.Unreachable();
            }
        }
 
        private async Task<ImmutableHashSet<ISymbol>?> GetNonConflictSymbolsAsync(Project currentProject)
        {
            if (_nonConflictSymbolKeys.IsDefault)
                return null;
 
            var compilation = await currentProject.GetRequiredCompilationAsync(_cancellationToken).ConfigureAwait(false);
            return ImmutableHashSet.CreateRange(
                _nonConflictSymbolKeys.Select(s => s.Resolve(compilation).GetAnySymbol()).WhereNotNull());
        }
 
        private static bool IsConflictFreeChange(
            ImmutableArray<ISymbol> symbols, ImmutableHashSet<ISymbol>? nonConflictSymbols)
        {
            if (nonConflictSymbols != null)
            {
                foreach (var symbol in symbols)
                {
                    // Reference not points at a symbol in the conflict-free list.  This is a conflict-free change.
                    if (nonConflictSymbols.Contains(symbol))
                        return true;
                }
            }
 
            // Just do the default check.
            return false;
        }
 
        /// <summary>
        /// Gets the list of the nodes that were annotated for a conflict check 
        /// </summary>
        private IEnumerable<(SyntaxNodeOrToken syntax, RenameActionAnnotation annotation)> GetNodesOrTokensToCheckForConflicts(
            SyntaxNode syntaxRoot)
        {
            foreach (var nodeOrToken in syntaxRoot.GetAnnotatedNodesAndTokens(RenameAnnotation.Kind))
            {
                var annotation = _renameAnnotations.GetAnnotations<RenameActionAnnotation>(nodeOrToken).FirstOrDefault();
 
                if (annotation != null)
                    yield return (nodeOrToken, annotation);
            }
        }
 
        private async Task<bool> CheckForConflictAsync(
            MutableConflictResolution conflictResolution,
            ISymbol renamedSymbolInNewSolution,
            RenameActionAnnotation conflictAnnotation,
            ImmutableArray<ISymbol> newReferencedSymbols)
        {
            try
            {
                bool hasConflict;
                var solution = conflictResolution.CurrentSolution;
 
                if (conflictAnnotation.IsNamespaceDeclarationReference)
                {
                    hasConflict = false;
                }
                else if (conflictAnnotation.IsMemberGroupReference)
                {
                    if (!conflictAnnotation.RenameDeclarationLocationReferences.Any())
                    {
                        hasConflict = false;
                    }
                    else
                    {
                        // Ensure newReferencedSymbols contains at least one of the original referenced
                        // symbols, and allow any new symbols to be added to the set of references.
 
                        hasConflict = true;
 
                        var newLocationTasks = newReferencedSymbols.Select(async symbol => await GetSymbolLocationAsync(solution, symbol, _cancellationToken).ConfigureAwait(false));
                        var newLocations = (await Task.WhenAll(newLocationTasks).ConfigureAwait(false)).WhereNotNull().Where(loc => loc.IsInSource);
                        foreach (var originalReference in conflictAnnotation.RenameDeclarationLocationReferences.Where(loc => loc.IsSourceLocation))
                        {
                            var adjustedStartPosition = conflictResolution.GetAdjustedTokenStartingPosition(originalReference.TextSpan.Start, originalReference.DocumentId);
                            if (newLocations.Any(loc => loc.SourceSpan.Start == adjustedStartPosition))
                            {
                                hasConflict = false;
                                break;
                            }
                        }
                    }
                }
                else if (!conflictAnnotation.IsRenameLocation && conflictAnnotation.IsOriginalTextLocation && conflictAnnotation.RenameDeclarationLocationReferences.Length > 1 && newReferencedSymbols.Length == 1)
                {
                    // an ambiguous situation was resolved through rename in non reference locations
                    hasConflict = false;
                }
                else if (newReferencedSymbols.Length != conflictAnnotation.RenameDeclarationLocationReferences.Length)
                {
                    // Don't show conflicts for errors in the old solution that now bind in the new solution.
                    if (newReferencedSymbols.Length != 0 && conflictAnnotation.RenameDeclarationLocationReferences.Length == 0)
                    {
                        hasConflict = false;
                    }
                    else
                    {
                        hasConflict = true;
                    }
                }
                else
                {
                    hasConflict = false;
                    var symbolIndex = 0;
                    foreach (var symbol in newReferencedSymbols)
                    {
                        if (conflictAnnotation.RenameDeclarationLocationReferences[symbolIndex].SymbolLocationsCount != symbol.Locations.Length)
                        {
                            hasConflict = true;
                            break;
                        }
 
                        var newLocation = await GetSymbolLocationAsync(solution, symbol, _cancellationToken).ConfigureAwait(false);
 
                        if (newLocation != null && conflictAnnotation.RenameDeclarationLocationReferences[symbolIndex].IsSourceLocation)
                        {
                            // location was in source before, but not after rename
                            if (!newLocation.IsInSource)
                            {
                                hasConflict = true;
                                break;
                            }
 
                            var renameDeclarationLocationReference = conflictAnnotation.RenameDeclarationLocationReferences[symbolIndex];
                            var newAdjustedStartPosition = conflictResolution.GetAdjustedTokenStartingPosition(renameDeclarationLocationReference.TextSpan.Start, renameDeclarationLocationReference.DocumentId);
                            if (newAdjustedStartPosition != newLocation.SourceSpan.Start)
                            {
                                hasConflict = true;
                                break;
                            }
 
                            if (conflictAnnotation.RenameDeclarationLocationReferences[symbolIndex].IsOverriddenFromMetadata)
                            {
                                var overridingSymbol = await SymbolFinder.FindSymbolAtPositionAsync(solution.GetRequiredDocument(newLocation.SourceTree), newLocation.SourceSpan.Start, cancellationToken: _cancellationToken).ConfigureAwait(false);
                                if (overridingSymbol != null && !Equals(renamedSymbolInNewSolution, overridingSymbol))
                                {
                                    if (!overridingSymbol.IsOverride)
                                    {
                                        hasConflict = true;
                                        break;
                                    }
                                    else
                                    {
                                        var overriddenSymbol = overridingSymbol.GetOverriddenMember();
                                        if (overriddenSymbol == null || !overriddenSymbol.Locations.All(loc => loc.IsInMetadata))
                                        {
                                            hasConflict = true;
                                            break;
                                        }
                                    }
                                }
                            }
                        }
                        else
                        {
                            var newMetadataName = symbol.ToDisplayString(s_metadataSymbolDisplayFormat);
                            var oldMetadataName = conflictAnnotation.RenameDeclarationLocationReferences[symbolIndex].Name;
                            if (newLocation == null ||
                                newLocation.IsInSource ||
                                !HeuristicMetadataNameEquivalenceCheck(
                                    oldMetadataName,
                                    newMetadataName,
                                    _originalText,
                                    _replacementText))
                            {
                                hasConflict = true;
                                break;
                            }
                        }
 
                        symbolIndex++;
                    }
                }
 
                return hasConflict;
            }
            catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, ErrorSeverity.Critical))
            {
                throw ExceptionUtilities.Unreachable();
            }
        }
 
        private ImmutableArray<ISymbol> GetSymbolsInNewSolution(Document newDocument, SemanticModel newDocumentSemanticModel, RenameActionAnnotation conflictAnnotation, SyntaxNodeOrToken tokenOrNode)
        {
            var newReferencedSymbols = RenameUtilities.GetSymbolsTouchingPosition(tokenOrNode.Span.Start, newDocumentSemanticModel, newDocument.Project.Solution.Services, _cancellationToken);
 
            if (conflictAnnotation.IsInvocationExpression)
            {
                if (tokenOrNode.IsNode)
                {
                    var invocationReferencedSymbols = SymbolsForEnclosingInvocationExpressionWorker((SyntaxNode)tokenOrNode!, newDocumentSemanticModel, _cancellationToken);
                    if (!invocationReferencedSymbols.IsDefault)
                        newReferencedSymbols = invocationReferencedSymbols;
                }
            }
 
            // if there are more than one symbol, then remove the alias symbols.
            // When using (not declaring) an alias, the alias symbol and the target symbol are returned
            // by GetSymbolsTouchingPosition
            if (newReferencedSymbols.Length >= 2)
                newReferencedSymbols = newReferencedSymbols.WhereAsArray(a => a.Kind != SymbolKind.Alias);
 
            return newReferencedSymbols;
        }
 
        private async Task<ISymbol> GetRenamedSymbolInCurrentSolutionAsync(MutableConflictResolution conflictResolution)
        {
            try
            {
                // get the renamed symbol in complexified new solution
                var start = _documentOfRenameSymbolHasBeenRenamed
                    ? conflictResolution.GetAdjustedTokenStartingPosition(_renameSymbolDeclarationLocation.SourceSpan.Start, _documentIdOfRenameSymbolDeclaration)
                    : _renameSymbolDeclarationLocation.SourceSpan.Start;
 
                var document = conflictResolution.CurrentSolution.GetRequiredDocument(_documentIdOfRenameSymbolDeclaration);
                var newSymbol = await SymbolFinder.FindSymbolAtPositionAsync(document, start, cancellationToken: _cancellationToken).ConfigureAwait(false);
                return newSymbol;
            }
            catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, ErrorSeverity.Critical))
            {
                throw ExceptionUtilities.Unreachable();
            }
        }
 
        /// <summary>
        /// The method determines the set of documents that need to be processed for Rename and also determines
        /// the possible set of names that need to be checked for conflicts.
        /// The list will contains Strings like Bar -> BarAttribute ; Property Bar -> Bar , get_Bar, set_Bar
        /// </summary>
        private async Task<(ImmutableHashSet<DocumentId> documentIds, ImmutableArray<string> possibleNameConflicts)> FindDocumentsAndPossibleNameConflictsAsync()
        {
            try
            {
                var documentIds = new HashSet<DocumentId>();
                var possibleNameConflictsList = new List<string>();
 
                var symbol = _renameLocationSet.Symbol;
                var solution = _renameLocationSet.Solution;
 
                var allRenamedDocuments = _renameLocationSet.Locations.Select(loc => loc.Location.SourceTree!).Distinct().Select(solution.GetRequiredDocument);
                documentIds.AddRange(allRenamedDocuments.Select(d => d.Id));
 
                var documentsFromAffectedProjects = RenameUtilities.GetDocumentsAffectedByRename(symbol, solution, _renameLocationSet.Locations);
                foreach (var language in documentsFromAffectedProjects.Select(d => d.Project.Language).Distinct())
                {
                    solution.Services.GetLanguageServices(language).GetService<IRenameRewriterLanguageService>()
                        ?.TryAddPossibleNameConflicts(symbol, _replacementText, possibleNameConflictsList);
                }
 
                var possibleNameConflicts = possibleNameConflictsList.ToImmutableArray();
                await AddDocumentsWithPotentialConflictsAsync(documentsFromAffectedProjects, documentIds, possibleNameConflicts).ConfigureAwait(false);
                return (documentIds.ToImmutableHashSet(), possibleNameConflicts);
            }
            catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, ErrorSeverity.Critical))
            {
                throw ExceptionUtilities.Unreachable();
            }
        }
 
        private async Task AddDocumentsWithPotentialConflictsAsync(
            IEnumerable<Document> documents,
            HashSet<DocumentId> documentIds,
            ImmutableArray<string> possibleNameConflicts)
        {
            try
            {
                foreach (var document in documents)
                {
                    if (documentIds.Contains(document.Id))
                        continue;
 
                    if (!document.SupportsSyntaxTree)
                        continue;
 
                    var info = await SyntaxTreeIndex.GetRequiredIndexAsync(document, _cancellationToken).ConfigureAwait(false);
                    if (info.ProbablyContainsEscapedIdentifier(_originalText))
                    {
                        documentIds.Add(document.Id);
                        continue;
                    }
 
                    if (info.ProbablyContainsIdentifier(_replacementText))
                    {
                        documentIds.Add(document.Id);
                        continue;
                    }
 
                    foreach (var replacementName in possibleNameConflicts)
                    {
                        if (info.ProbablyContainsIdentifier(replacementName))
                        {
                            documentIds.Add(document.Id);
                            break;
                        }
                    }
                }
            }
            catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, ErrorSeverity.Critical))
            {
                throw ExceptionUtilities.Unreachable();
            }
        }
 
        // The rename process and annotation for the bookkeeping is performed in one-step
        private async Task<Solution> AnnotateAndRename_WorkerAsync(
            Solution originalSolution,
            Solution partiallyRenamedSolution,
            HashSet<DocumentId> documentIdsToRename,
            ImmutableArray<RenameLocation> renameLocations,
            RenamedSpansTracker renameSpansTracker,
            bool replacementTextValid,
            ISet<ConflictLocationInfo> conflictLocations,
            ImmutableArray<string> possibleNameConflicts)
        {
            try
            {
                foreach (var documentId in documentIdsToRename.ToList())
                {
                    _cancellationToken.ThrowIfCancellationRequested();
 
                    var document = originalSolution.GetRequiredDocument(documentId);
                    var semanticModel = await document.GetRequiredSemanticModelAsync(_cancellationToken).ConfigureAwait(false);
                    var originalSyntaxRoot = await semanticModel.SyntaxTree.GetRootAsync(_cancellationToken).ConfigureAwait(false);
 
                    // Get all rename locations for the current document.
                    var allTextSpansInSingleSourceTree = renameLocations
                        .Where(l => l.DocumentId == documentId && ShouldIncludeLocation(renameLocations, l))
                        .ToImmutableDictionary(l => l.Location.SourceSpan);
 
                    // All textspan in the document documentId, that requires rename in String or Comment
                    var stringAndCommentTextSpansInSingleSourceTree = renameLocations
                        .Where(l => l.DocumentId == documentId && l.IsRenameInStringOrComment)
                        .GroupBy(l => l.ContainingLocationForStringOrComment)
                        .ToImmutableDictionary(
                            g => g.Key,
                            g => GetSubSpansToRenameInStringAndCommentTextSpans(g.Key, g));
 
                    var conflictLocationSpans = conflictLocations
                        .Where(t => t.DocumentId == documentId)
                        .Select(t => t.ComplexifiedSpan)
                        .ToImmutableHashSet();
 
                    // Annotate all nodes with a RenameLocation annotations to record old locations & old referenced symbols.
                    // Also annotate nodes that should get complexified (nodes for rename locations + conflict locations)
                    var parameters = new RenameRewriterParameters(
                        _renamedSymbolDeclarationAnnotation,
                        document,
                        semanticModel,
                        originalSyntaxRoot,
                        _replacementText,
                        _originalText,
                        possibleNameConflicts,
                        allTextSpansInSingleSourceTree,
                        stringAndCommentTextSpansInSingleSourceTree,
                        conflictLocationSpans,
                        originalSolution,
                        _renameLocationSet.Symbol,
                        replacementTextValid,
                        renameSpansTracker,
                        RenameOptions.RenameInStrings,
                        RenameOptions.RenameInComments,
                        _renameAnnotations,
                        _cancellationToken);
 
                    var renameRewriterLanguageService = document.GetRequiredLanguageService<IRenameRewriterLanguageService>();
                    var newRoot = renameRewriterLanguageService.AnnotateAndRename(parameters);
 
                    if (newRoot == originalSyntaxRoot)
                    {
                        // Update the list for the current phase, some files with strings containing the original or replacement
                        // text may have been filtered out.
                        documentIdsToRename.Remove(documentId);
                    }
                    else
                    {
                        partiallyRenamedSolution = partiallyRenamedSolution.WithDocumentSyntaxRoot(documentId, newRoot, PreservationMode.PreserveIdentity);
                    }
                }
 
                return partiallyRenamedSolution;
            }
            catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e))
            {
                throw ExceptionUtilities.Unreachable();
            }
        }
 
        /// We try to rewrite all locations that are invalid candidate locations. If there is only
        /// one location it must be the correct one (the symbol is ambiguous to something else)
        /// and we always try to rewrite it.  If there are multiple locations, we only allow it
        /// if the candidate reason allows for it).
        private static bool ShouldIncludeLocation(ImmutableArray<RenameLocation> renameLocations, RenameLocation location)
        {
            if (location.IsRenameInStringOrComment)
            {
                return false;
            }
 
            if (renameLocations.Length == 1)
            {
                return true;
            }
 
            return RenameLocation.ShouldRename(location);
        }
 
        /// <summary>
        /// We try to compute the sub-spans to rename within the given <paramref name="containingLocationForStringOrComment"/>.
        /// If we are renaming within a string, the locations to rename are always within this containing string location
        /// and we can identify these sub-spans.
        /// However, if we are renaming within a comment, the rename locations can be anywhere in trivia,
        /// so we return null and the rename rewriter will perform a complete regex match within comment trivia
        /// and rename all matches instead of specific matches.
        /// </summary>
        private static ImmutableSortedSet<TextSpan>? GetSubSpansToRenameInStringAndCommentTextSpans(
            TextSpan containingLocationForStringOrComment,
            IEnumerable<RenameLocation> locationsToRename)
        {
            var builder = ImmutableSortedSet.CreateBuilder<TextSpan>();
            foreach (var renameLocation in locationsToRename)
            {
                if (!containingLocationForStringOrComment.Contains(renameLocation.Location.SourceSpan))
                {
                    // We found a location outside the 'containingLocationForStringOrComment',
                    // which is likely in trivia.
                    // Bail out from computing specific sub-spans and let the rename rewriter
                    // do a full regex match and replace.
                    return null;
                }
 
                // Compute the sub-span within 'containingLocationForStringOrComment' that needs to be renamed.
                var offset = renameLocation.Location.SourceSpan.Start - containingLocationForStringOrComment.Start;
                var length = renameLocation.Location.SourceSpan.Length;
                var subSpan = new TextSpan(offset, length);
                builder.Add(subSpan);
            }
 
            return builder.ToImmutable();
        }
    }
}