private static async Task AddImplicitConflictsAsync( ISymbol renamedSymbol, ISymbol originalSymbol, IEnumerable <ReferenceLocation> implicitReferenceLocations, SemanticModel semanticModel, Location originalDeclarationLocation, int newDeclarationLocationStartingPosition, ConflictResolution conflictResolution, CancellationToken cancellationToken) { { var renameRewriterService = conflictResolution.NewSolution.Workspace.Services.GetLanguageServices(renamedSymbol.Language).GetService <IRenameRewriterLanguageService>(); var implicitUsageConflicts = renameRewriterService.ComputePossibleImplicitUsageConflicts(renamedSymbol, semanticModel, originalDeclarationLocation, newDeclarationLocationStartingPosition, cancellationToken); foreach (var implicitUsageConflict in implicitUsageConflicts) { conflictResolution.AddOrReplaceRelatedLocation(new RelatedLocation(implicitUsageConflict.SourceSpan, conflictResolution.OldSolution.GetDocument(implicitUsageConflict.SourceTree).Id, RelatedLocationType.UnresolvableConflict)); } } if (implicitReferenceLocations.IsEmpty()) { return; } foreach (var implicitReferenceLocationsPerLanguage in implicitReferenceLocations.GroupBy(loc => loc.Document.Project.Language)) { // the location of the implicit reference defines the language rules to check. // E.g. foreach in C# using a MoveNext in VB that is renamed to MOVENEXT (within VB) var renameRewriterService = implicitReferenceLocationsPerLanguage.First().Document.Project.LanguageServices.GetService <IRenameRewriterLanguageService>(); var implicitConflicts = await renameRewriterService.ComputeImplicitReferenceConflictsAsync( originalSymbol, renamedSymbol, implicitReferenceLocationsPerLanguage, cancellationToken).ConfigureAwait(false); foreach (var implicitConflict in implicitConflicts) { conflictResolution.AddRelatedLocation(new RelatedLocation(implicitConflict.SourceSpan, conflictResolution.OldSolution.GetDocument(implicitConflict.SourceTree).Id, RelatedLocationType.UnresolvableConflict)); } } }
/// <summary> /// Computes an adds conflicts relating to declarations, which are independent of /// location-based checks. Examples of these types of conflicts include renaming a member to /// the same name as another member of a type: binding doesn't change (at least from the /// perspective of find all references), but we still need to track it. /// </summary> internal static async Task AddDeclarationConflictsAsync( ISymbol renamedSymbol, ISymbol renameSymbol, IEnumerable <ISymbol> referencedSymbols, ConflictResolution conflictResolution, IDictionary <Location, Location> reverseMappedLocations, CancellationToken cancellationToken) { if (renamedSymbol.ContainingSymbol is INamedTypeSymbol) { var otherThingsNamedTheSame = renamedSymbol.ContainingType.GetMembers(renamedSymbol.Name) .Where(s => !s.Equals(renamedSymbol) && string.Equals(s.MetadataName, renamedSymbol.MetadataName, StringComparison.Ordinal) && (s.Kind != SymbolKind.Method || renamedSymbol.Kind != SymbolKind.Method)).ToList(); AddConflictingSymbolLocations(otherThingsNamedTheSame, conflictResolution, reverseMappedLocations); } // Some types of symbols (namespaces, cref stuff, etc) might not have ContainingAssemblies if (renamedSymbol.ContainingAssembly != null) { var project = conflictResolution.NewSolution.GetProject(renamedSymbol.ContainingAssembly, cancellationToken); // There also might be language specific rules we need to include var languageRenameService = project.LanguageServices.GetService <IRenameRewriterLanguageService>(); var languageConflicts = await languageRenameService.ComputeDeclarationConflictsAsync( conflictResolution.ReplacementText, renamedSymbol, renameSymbol, referencedSymbols, conflictResolution.OldSolution, conflictResolution.NewSolution, reverseMappedLocations, cancellationToken).ConfigureAwait(false); foreach (var languageConflict in languageConflicts) { conflictResolution.AddOrReplaceRelatedLocation(new RelatedLocation(languageConflict.SourceSpan, conflictResolution.OldSolution.GetDocument(languageConflict.SourceTree).Id, RelatedLocationType.UnresolvableConflict)); } } }
private static void AddConflictingSymbolLocations(IEnumerable <ISymbol> conflictingSymbols, ConflictResolution conflictResolution, IDictionary <Location, Location> reverseMappedLocations) { foreach (var newSymbol in conflictingSymbols) { foreach (var newLocation in newSymbol.Locations) { if (newLocation.IsInSource) { if (reverseMappedLocations.TryGetValue(newLocation, out var oldLocation)) { conflictResolution.AddOrReplaceRelatedLocation(new RelatedLocation(oldLocation.SourceSpan, conflictResolution.OldSolution.GetDocument(oldLocation.SourceTree).Id, RelatedLocationType.UnresolvableConflict)); } } } } }
/// <summary> /// Computes an adds conflicts relating to declarations, which are independent of /// location-based checks. Examples of these types of conflicts include renaming a member to /// the same name as another member of a type: binding doesn't change (at least from the /// perspective of find all references), but we still need to track it. /// </summary> internal static async Task AddDeclarationConflictsAsync( ISymbol renamedSymbol, ISymbol renameSymbol, IEnumerable <SymbolAndProjectId> referencedSymbols, ConflictResolution conflictResolution, IDictionary <Location, Location> reverseMappedLocations, CancellationToken cancellationToken) { try { var project = conflictResolution.NewSolution.GetProject(renamedSymbol.ContainingAssembly, cancellationToken); if (renamedSymbol.ContainingSymbol.IsKind(SymbolKind.NamedType)) { var otherThingsNamedTheSame = renamedSymbol.ContainingType.GetMembers(renamedSymbol.Name) .Where(s => !s.Equals(renamedSymbol) && string.Equals(s.MetadataName, renamedSymbol.MetadataName, StringComparison.Ordinal)); IEnumerable <ISymbol> otherThingsNamedTheSameExcludeMethodAndParameterizedProperty; // Possibly overloaded symbols are excluded here and handled elsewhere var semanticFactsService = project.LanguageServices.GetService <ISemanticFactsService>(); if (semanticFactsService.SupportsParameterizedProperties) { otherThingsNamedTheSameExcludeMethodAndParameterizedProperty = otherThingsNamedTheSame .Where(s => !s.MatchesKind(SymbolKind.Method, SymbolKind.Property) || !renamedSymbol.MatchesKind(SymbolKind.Method, SymbolKind.Property)); } else { otherThingsNamedTheSameExcludeMethodAndParameterizedProperty = otherThingsNamedTheSame .Where(s => s.Kind != SymbolKind.Method || renamedSymbol.Kind != SymbolKind.Method); } AddConflictingSymbolLocations(otherThingsNamedTheSameExcludeMethodAndParameterizedProperty, conflictResolution, reverseMappedLocations); } if (renamedSymbol.IsKind(SymbolKind.Namespace) && renamedSymbol.ContainingSymbol.IsKind(SymbolKind.Namespace)) { var otherThingsNamedTheSame = ((INamespaceSymbol)renamedSymbol.ContainingSymbol).GetMembers(renamedSymbol.Name) .Where(s => !s.Equals(renamedSymbol) && !s.IsKind(SymbolKind.Namespace) && string.Equals(s.MetadataName, renamedSymbol.MetadataName, StringComparison.Ordinal)); AddConflictingSymbolLocations(otherThingsNamedTheSame, conflictResolution, reverseMappedLocations); } if (renamedSymbol.IsKind(SymbolKind.NamedType) && renamedSymbol.ContainingSymbol is INamespaceOrTypeSymbol) { var otherThingsNamedTheSame = ((INamespaceOrTypeSymbol)renamedSymbol.ContainingSymbol).GetMembers(renamedSymbol.Name) .Where(s => !s.Equals(renamedSymbol) && string.Equals(s.MetadataName, renamedSymbol.MetadataName, StringComparison.Ordinal)); var conflictingSymbolLocations = otherThingsNamedTheSame.Where(s => !s.IsKind(SymbolKind.Namespace)); if (otherThingsNamedTheSame.Any(s => s.IsKind(SymbolKind.Namespace))) { conflictingSymbolLocations = conflictingSymbolLocations.Concat(renamedSymbol); } AddConflictingSymbolLocations(conflictingSymbolLocations, conflictResolution, reverseMappedLocations); } // Some types of symbols (namespaces, cref stuff, etc) might not have ContainingAssemblies if (renamedSymbol.ContainingAssembly != null) { // There also might be language specific rules we need to include var languageRenameService = project.LanguageServices.GetService <IRenameRewriterLanguageService>(); var languageConflicts = await languageRenameService.ComputeDeclarationConflictsAsync( conflictResolution.ReplacementText, renamedSymbol, renameSymbol, referencedSymbols, conflictResolution.OldSolution, conflictResolution.NewSolution, reverseMappedLocations, cancellationToken).ConfigureAwait(false); foreach (var languageConflict in languageConflicts) { conflictResolution.AddOrReplaceRelatedLocation(new RelatedLocation(languageConflict.SourceSpan, conflictResolution.OldSolution.GetDocument(languageConflict.SourceTree).Id, RelatedLocationType.UnresolvableConflict)); } } } catch (Exception e) when(FatalError.ReportUnlessCanceled(e)) { // A NullReferenceException is happening in this method, but the dumps do not // contain information about this stack frame because this method is async and // therefore the exception filter in IdentifyConflictsAsync is insufficient. // See https://devdiv.visualstudio.com/DevDiv/_workitems?_a=edit&id=378642 throw ExceptionUtilities.Unreachable; } }
private static bool IsRenameValid(ConflictResolution conflictResolution, ISymbol renamedSymbol) { // if we rename an identifier and it now binds to a symbol from metadata this should be treated as // an invalid rename. return(conflictResolution.ReplacementTextValid && renamedSymbol != null && renamedSymbol.Locations.Any(loc => loc.IsInSource)); }
private async Task DebugVerifyNoErrorsAsync(ConflictResolution 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.GetDocument(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>(); ignoreErrorCodes.Add("BC30420"); // BC30420 - Sub Main missing in VB Project ignoreErrorCodes.Add("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] && _hasConflictCallback == null) { await conflictResolution.NewSolution.GetDocument(documentId).VerifyNoErrorsAsync("Rename introduced errors in error-free code", _cancellationToken, ignoreErrorCodes).ConfigureAwait(false); } } } }
/// <summary> /// Find conflicts in the new solution /// </summary> private async Task <bool> IdentifyConflictsAsync( HashSet <DocumentId> documentIdsForConflictResolution, IEnumerable <DocumentId> allDocumentIdsInProject, ProjectId projectId, ConflictResolution conflictResolution) { 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.NewSolution.GetDocument(documentId); var syntaxRoot = await newDocument.GetSyntaxRootAsync(_cancellationToken).ConfigureAwait(false); var nodesOrTokensWithConflictCheckAnnotations = GetNodesOrTokensToCheckForConflicts(documentId, syntaxRoot); foreach (var nodeOrToken in nodesOrTokensWithConflictCheckAnnotations) { if (nodeOrToken.annotation.IsRenameLocation) { conflictResolution.AddRelatedLocation(new RelatedLocation( nodeOrToken.annotation.OriginalSpan, documentId, RelatedLocationType.UnresolvedConflict)); } } } return(false); } var reverseMappedLocations = new Dictionary <Location, Location>(); foreach (var documentId in documentIdsForConflictResolution) { var newDocument = conflictResolution.NewSolution.GetDocument(documentId); var syntaxRoot = await newDocument.GetSyntaxRootAsync(_cancellationToken).ConfigureAwait(false); var baseDocument = conflictResolution.OldSolution.GetDocument(documentId); var baseSyntaxTree = await baseDocument.GetSyntaxTreeAsync(_cancellationToken).ConfigureAwait(false); var baseRoot = await baseDocument.GetSyntaxRootAsync(_cancellationToken).ConfigureAwait(false); SemanticModel newDocumentSemanticModel = null; var syntaxFactsService = newDocument.Project.LanguageServices.GetService <ISyntaxFactsService>(); // Get all tokens that need conflict check var nodesOrTokensWithConflictCheckAnnotations = GetNodesOrTokensToCheckForConflicts(documentId, syntaxRoot); var complexifiedLocationSpanForThisDocument = _conflictLocations .Where(t => t.DocumentId == documentId) .Select(t => t.OriginalIdentifierSpan).ToSet(); foreach (var nodeAndAnnotation in nodesOrTokensWithConflictCheckAnnotations) { var tokenOrNode = nodeAndAnnotation.syntax; var conflictAnnotation = nodeAndAnnotation.annotation; reverseMappedLocations[tokenOrNode.GetLocation()] = baseSyntaxTree.GetLocation(conflictAnnotation.OriginalSpan); var originalLocation = conflictAnnotation.OriginalSpan; IEnumerable <ISymbol> newReferencedSymbols = null; var hasConflict = _renameAnnotations.HasAnnotation(tokenOrNode, RenameInvalidIdentifierAnnotation.Instance); if (!hasConflict) { newDocumentSemanticModel ??= await newDocument.GetSemanticModelAsync(_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 = _hasConflictCallback?.Invoke(newReferencedSymbols) ?? await CheckForConflictAsync(conflictResolution, renamedSymbolInNewSolution, newDocument, 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.NewSolution.GetDocument(unprocessedDocumentIdWithPotentialDeclarationConflicts); var syntaxRoot = await newDocument.GetSyntaxRootAsync(_cancellationToken).ConfigureAwait(false); var baseDocument = conflictResolution.OldSolution.GetDocument(unprocessedDocumentIdWithPotentialDeclarationConflicts); var baseSyntaxTree = await baseDocument.GetSyntaxTreeAsync(_cancellationToken).ConfigureAwait(false); var nodesOrTokensWithConflictCheckAnnotations = GetNodesOrTokensToCheckForConflicts(unprocessedDocumentIdWithPotentialDeclarationConflicts, syntaxRoot); foreach (var nodeAndAnnotation in nodesOrTokensWithConflictCheckAnnotations) { var tokenOrNode = nodeAndAnnotation.syntax; var conflictAnnotation = nodeAndAnnotation.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.ReportUnlessCanceled(e)) { throw ExceptionUtilities.Unreachable; } }
// The method which performs rename, resolves the conflict locations and returns the result of the rename operation public async Task<ConflictResolution> ResolveConflictsAsync() { try { await FindDocumentsAndPossibleNameConflicts().ConfigureAwait(false); var baseSolution = _renameLocationSet.Solution; // 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), _cancellationToken); var renamedSpansTracker = new RenamedSpansTracker(); var conflictResolution = new ConflictResolution(baseSolution, renamedSpansTracker, _replacementText, _replacementTextValid); foreach (var documentsByProject in documentsGroupedByTopologicallySortedProjectId) { var documentIdsThatGetsAnnotatedAndRenamed = new HashSet<DocumentId>(documentsByProject); using (baseSolution.Services.CacheService?.EnableCaching(documentsByProject.Key)) { // Rename is going to be in 4 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. for (int 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.NewSolution, documentIdsThatGetsAnnotatedAndRenamed, _renameLocationSet.Locations, renamedSpansTracker, _replacementTextValid).ConfigureAwait(false)); // Step 2: Check for conflicts in the renamed solution bool foundResolvableConflicts = await IdentifyConflictsAsync( documentIdsForConflictResolution: documentIdsThatGetsAnnotatedAndRenamed, allDocumentIdsInProject: documentsByProject, projectId: documentsByProject.Key, conflictResolution: conflictResolution).ConfigureAwait(false); if (!foundResolvableConflicts || phase == 3) { break; } if (phase == 0) { _conflictLocations = conflictResolution.RelatedLocations .Where(loc => (documentIdsThatGetsAnnotatedAndRenamed.Contains(loc.DocumentId) && loc.Type == RelatedLocationType.PossiblyResolvableConflict && loc.IsReference)) .Select(loc => new ConflictLocationInfo(loc)) .ToSet(); // 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.Concat(conflictResolution.RelatedLocations .Where(loc => documentIdsThatGetsAnnotatedAndRenamed.Contains(loc.DocumentId) && loc.Type == RelatedLocationType.PossiblyResolvableConflict) .Select(loc => new ConflictLocationInfo(loc))) .ToSet(); } // 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))).ToSet(); } // 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.NewSolution, documentsByProject, _replacementTextValid, _renameAnnotations, _cancellationToken).ConfigureAwait(false)); conflictResolution.RemoveAllRenameAnnotations(documentsByProject, _renameAnnotations, _cancellationToken); } } // This rename could break implicit references of this symbol (e.g. rename MoveNext on a collection like type in a // foreach/for each statement ISymbol renamedSymbolInNewSolution = await GetRenamedSymbolInCurrentSolutionAsync(conflictResolution).ConfigureAwait(false); if (IsRenameValid(conflictResolution, renamedSymbolInNewSolution)) { AddImplicitConflicts( renamedSymbolInNewSolution, _renameLocationSet.Symbol, _renameLocationSet.ImplicitLocations, await conflictResolution.NewSolution.GetDocument(_documentIdOfRenameSymbolDeclaration).GetSemanticModelAsync(_cancellationToken).ConfigureAwait(false), _renameSymbolDeclarationLocation, renamedSpansTracker.GetAdjustedPosition(_renameSymbolDeclarationLocation.SourceSpan.Start, _documentIdOfRenameSymbolDeclaration), conflictResolution, _cancellationToken); } foreach (var relatedLocation in conflictResolution.RelatedLocations) { if (relatedLocation.Type == RelatedLocationType.PossiblyResolvableConflict) { relatedLocation.Type = RelatedLocationType.UnresolvedConflict; } } #if DEBUG DebugVerifyNoErrors(conflictResolution, _documentsIdsToBeCheckedForConflict); #endif return conflictResolution; } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) { throw ExceptionUtilities.Unreachable; } }
private async Task<bool> CheckForConflictAsync( ConflictResolution conflictResolution, ISymbol renamedSymbolInNewSolution, Document newDocument, RenameActionAnnotation conflictAnnotation, IEnumerable<ISymbol> newReferencedSymbols) { try { bool hasConflict; var solution = conflictResolution.NewSolution; 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)).Where(loc => loc != null && 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.Count() == 1) { // an ambiguous situation was resolved through rename in non reference locations hasConflict = false; } else if (newReferencedSymbols.Count() != conflictAnnotation.RenameDeclarationLocationReferences.Length) { // Don't show conflicts for errors in the old solution that now bind in the new solution. if (newReferencedSymbols.Count() != 0 && conflictAnnotation.RenameDeclarationLocationReferences.Length == 0) { hasConflict = false; } else { hasConflict = true; } } else { hasConflict = false; int symbolIndex = 0; foreach (var symbol in newReferencedSymbols) { if (conflictAnnotation.RenameDeclarationLocationReferences[symbolIndex].SymbolLocationsCount != symbol.Locations.Count()) { 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.GetDocument(newLocation.SourceTree), newLocation.SourceSpan.Start, cancellationToken: _cancellationToken).ConfigureAwait(false); if (overridingSymbol != null && renamedSymbolInNewSolution != overridingSymbol) { if (!overridingSymbol.IsOverride) { hasConflict = true; break; } else { var overriddenSymbol = overridingSymbol.OverriddenMember(); 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.ReportUnlessCanceled(e)) { throw ExceptionUtilities.Unreachable; } }
private async Task<ISymbol> GetRenamedSymbolInCurrentSolutionAsync(ConflictResolution conflictResolution) { // get the renamed symbol in complexified new solution int start = _documentOfRenameSymbolHasBeenRenamed ? conflictResolution.RenamedSpansTracker.GetAdjustedPosition(_renameSymbolDeclarationLocation.SourceSpan.Start, _documentIdOfRenameSymbolDeclaration) : _renameSymbolDeclarationLocation.SourceSpan.Start; var document = conflictResolution.NewSolution.GetDocument(_documentIdOfRenameSymbolDeclaration); var newSymbol = await SymbolFinder.FindSymbolAtPositionAsync(document, start, cancellationToken: _cancellationToken).ConfigureAwait(false); return newSymbol; }
private async Task <bool> CheckForConflictAsync( ConflictResolution conflictResolution, ISymbol renamedSymbolInNewSolution, Document newDocument, RenameActionAnnotation conflictAnnotation, IEnumerable <ISymbol> newReferencedSymbols) { bool hasConflict; var solution = conflictResolution.NewSolution; if (conflictAnnotation.IsNamespaceDeclarationReference) { hasConflict = false; } else if (!conflictAnnotation.IsRenameLocation && conflictAnnotation.IsOriginalTextLocation && conflictAnnotation.RenameDeclarationLocationReferences.Length > 1 && newReferencedSymbols.Count() == 1) { // an ambiguous situation was resolved through rename in non reference locations hasConflict = false; } else if (newReferencedSymbols.Count() != conflictAnnotation.RenameDeclarationLocationReferences.Length) { // Don't show conflicts for errors in the old solution that now bind in the new solution. if (newReferencedSymbols.Count() != 0 && conflictAnnotation.RenameDeclarationLocationReferences.Length == 0) { hasConflict = false; } else { hasConflict = true; } } else { hasConflict = false; int symbolIndex = 0; foreach (var symbol in newReferencedSymbols) { if (conflictAnnotation.RenameDeclarationLocationReferences[symbolIndex].SymbolLocationsCount != symbol.Locations.Count()) { 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.GetDocument(newLocation.SourceTree), newLocation.SourceSpan.Start, cancellationToken : _cancellationToken).ConfigureAwait(false); if (overridingSymbol != null && renamedSymbolInNewSolution != overridingSymbol) { if (!overridingSymbol.IsOverride) { hasConflict = true; break; } else { var overriddenSymbol = overridingSymbol.OverriddenMember(); 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); }
public TestAccessor(ConflictResolution conflictResolution) { _conflictResolution = conflictResolution; }
public InlineRenameReplacementInfo(ConflictResolution conflicts) { _conflicts = conflicts; }
private async Task<bool> CheckForConflictAsync( ConflictResolution conflictResolution, ISymbol renamedSymbolInNewSolution, Document newDocument, RenameActionAnnotation conflictAnnotation, IEnumerable<ISymbol> newReferencedSymbols) { bool hasConflict; var solution = conflictResolution.NewSolution; if (conflictAnnotation.IsNamespaceDeclarationReference) { hasConflict = false; } else if (!conflictAnnotation.IsRenameLocation && conflictAnnotation.IsOriginalTextLocation && conflictAnnotation.RenameDeclarationLocationReferences.Length > 1 && newReferencedSymbols.Count() == 1) { // an ambiguous situation was resolved through rename in non reference locations hasConflict = false; } else if (newReferencedSymbols.Count() != conflictAnnotation.RenameDeclarationLocationReferences.Length) { // Don't show conflicts for errors in the old solution that now bind in the new solution. if (newReferencedSymbols.Count() != 0 && conflictAnnotation.RenameDeclarationLocationReferences.Length == 0) { hasConflict = false; } else { hasConflict = true; } } else { hasConflict = false; int symbolIndex = 0; foreach (var symbol in newReferencedSymbols) { if (conflictAnnotation.RenameDeclarationLocationReferences[symbolIndex].SymbolLocationsCount != symbol.Locations.Count()) { 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.GetDocument(newLocation.SourceTree), newLocation.SourceSpan.Start, cancellationToken: cancellationToken).ConfigureAwait(false); if (overridingSymbol != null && renamedSymbolInNewSolution != overridingSymbol) { if (!overridingSymbol.IsOverride) { hasConflict = true; break; } else { var overriddenSymbol = overridingSymbol.OverriddenMember(); if (overriddenSymbol == null || !overriddenSymbol.Locations.All(loc => loc.IsInMetadata)) { hasConflict = true; break; } } } } } else { var newMetadataName = symbol.ToDisplayString(metadataSymbolDisplayFormat); var oldMetadataName = conflictAnnotation.RenameDeclarationLocationReferences[symbolIndex].Name; if (newLocation.IsInSource || !HeuristicMetadataNameEquivalenceCheck( oldMetadataName, newMetadataName, originalText, replacementText)) { hasConflict = true; break; } } symbolIndex++; } } return hasConflict; }
private void DebugVerifyNoErrors(ConflictResolution 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.GetDocument(documentId); documentIdErrorStateLookup.Add(documentId, originalDoc.HasAnyErrors(_cancellationToken).WaitAndGetResult(_cancellationToken)); } // We want to ignore few error message introduced by rename because the user is wantedly doing it. var ignoreErrorCodes = new List<string>(); ignoreErrorCodes.Add("BC30420"); // BC30420 - Sub Main missing in VB Project ignoreErrorCodes.Add("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) if (!documentIdErrorStateLookup[documentId]) { conflictResolution.NewSolution.GetDocument(documentId).VerifyNoErrorsAsync("Rename introduced errors in error-free code", _cancellationToken, ignoreErrorCodes).Wait(_cancellationToken); } } } }
/// <summary> /// Find conflicts in the new solution /// </summary> private async Task<bool> IdentifyConflictsAsync( HashSet<DocumentId> documentIdsForConflictResolution, IEnumerable<DocumentId> allDocumentIdsInProject, ProjectId projectId, ConflictResolution conflictResolution) { try { _documentOfRenameSymbolHasBeenRenamed |= documentIdsForConflictResolution.Contains(_documentIdOfRenameSymbolDeclaration); // Get the renamed symbol in complexified new solution ISymbol 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.NewSolution.GetDocument(documentId); var syntaxRoot = await newDocument.GetSyntaxRootAsync(_cancellationToken).ConfigureAwait(false); var nodesOrTokensWithConflictCheckAnnotations = GetNodesOrTokensToCheckForConflicts(documentId, syntaxRoot); foreach (var nodeOrToken in nodesOrTokensWithConflictCheckAnnotations) { if (nodeOrToken.Item2.IsRenameLocation) { conflictResolution.AddRelatedLocation(new RelatedLocation(nodeOrToken.Item2.OriginalSpan, documentId, RelatedLocationType.UnresolvedConflict)); } } } return false; } Dictionary<Location, Location> reverseMappedLocations = new Dictionary<Location, Location>(); foreach (var documentId in documentIdsForConflictResolution) { var newDocument = conflictResolution.NewSolution.GetDocument(documentId); var syntaxRoot = await newDocument.GetSyntaxRootAsync(_cancellationToken).ConfigureAwait(false); var baseDocument = conflictResolution.OldSolution.GetDocument(documentId); var baseSyntaxTree = await baseDocument.GetSyntaxTreeAsync(_cancellationToken).ConfigureAwait(false); var baseRoot = await baseDocument.GetSyntaxRootAsync(_cancellationToken).ConfigureAwait(false); SemanticModel newDocumentSemanticModel = null; var syntaxFactsService = newDocument.Project.LanguageServices.GetService<ISyntaxFactsService>(); // Get all tokens that need conflict check var nodesOrTokensWithConflictCheckAnnotations = GetNodesOrTokensToCheckForConflicts(documentId, syntaxRoot); var complexifiedLocationSpanForThisDocument = _conflictLocations .Where(t => t.DocumentId == documentId) .Select(t => t.OriginalIdentifierSpan).ToSet(); foreach (var nodeAndAnnotation in nodesOrTokensWithConflictCheckAnnotations) { var tokenOrNode = nodeAndAnnotation.Item1; var conflictAnnotation = nodeAndAnnotation.Item2; reverseMappedLocations[tokenOrNode.GetLocation()] = baseSyntaxTree.GetLocation(conflictAnnotation.OriginalSpan); var originalLocation = conflictAnnotation.OriginalSpan; IEnumerable<ISymbol> newReferencedSymbols = null; var hasConflict = _renameAnnotations.HasAnnotation(tokenOrNode, RenameInvalidIdentifierAnnotation.Instance); if (!hasConflict) { newDocumentSemanticModel = newDocumentSemanticModel ?? await newDocument.GetSemanticModelAsync(_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 = await CheckForConflictAsync(conflictResolution, renamedSymbolInNewSolution, newDocument, 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(TextSpan))); } } } // 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.NewSolution.GetDocument(unprocessedDocumentIdWithPotentialDeclarationConflicts); var syntaxRoot = await newDocument.GetSyntaxRootAsync(_cancellationToken).ConfigureAwait(false); var baseDocument = conflictResolution.OldSolution.GetDocument(unprocessedDocumentIdWithPotentialDeclarationConflicts); var baseSyntaxTree = await baseDocument.GetSyntaxTreeAsync(_cancellationToken).ConfigureAwait(false); var nodesOrTokensWithConflictCheckAnnotations = GetNodesOrTokensToCheckForConflicts(unprocessedDocumentIdWithPotentialDeclarationConflicts, syntaxRoot); foreach (var nodeAndAnnotation in nodesOrTokensWithConflictCheckAnnotations) { var tokenOrNode = nodeAndAnnotation.Item1; var conflictAnnotation = nodeAndAnnotation.Item2; reverseMappedLocations[tokenOrNode.GetLocation()] = baseSyntaxTree.GetLocation(conflictAnnotation.OriginalSpan); } } IEnumerable<ISymbol> referencedSymbols = _renameLocationSet.ReferencedSymbols; ISymbol 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.ReportUnlessCanceled(e)) { throw ExceptionUtilities.Unreachable; } }
private static bool IsRenameValid(ConflictResolution conflictResolution, ISymbol renamedSymbol) { // if we rename an identifier and it now binds to a symbol from metadata this should be treated as // an invalid rename. return conflictResolution.ReplacementTextValid && renamedSymbol != null && renamedSymbol.Locations.Any(loc => loc.IsInSource); }
private async Task<ISymbol> GetRenamedSymbolInCurrentSolutionAsync(ConflictResolution conflictResolution) { try { // get the renamed symbol in complexified new solution int start = _documentOfRenameSymbolHasBeenRenamed ? conflictResolution.RenamedSpansTracker.GetAdjustedPosition(_renameSymbolDeclarationLocation.SourceSpan.Start, _documentIdOfRenameSymbolDeclaration) : _renameSymbolDeclarationLocation.SourceSpan.Start; var document = conflictResolution.NewSolution.GetDocument(_documentIdOfRenameSymbolDeclaration); var newSymbol = await SymbolFinder.FindSymbolAtPositionAsync(document, start, cancellationToken: _cancellationToken).ConfigureAwait(false); return newSymbol; } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) { throw ExceptionUtilities.Unreachable; } }
private static void AddImplicitConflicts( ISymbol renamedSymbol, ISymbol originalSymbol, IEnumerable<ReferenceLocation> implicitReferenceLocations, SemanticModel semanticModel, Location originalDeclarationLocation, int newDeclarationLocationStartingPosition, ConflictResolution conflictResolution, CancellationToken cancellationToken) { { var renameRewriterService = conflictResolution.NewSolution.Workspace.Services.GetLanguageServices(renamedSymbol.Language).GetService<IRenameRewriterLanguageService>(); var implicitUsageConflicts = renameRewriterService.ComputePossibleImplicitUsageConflicts(renamedSymbol, semanticModel, originalDeclarationLocation, newDeclarationLocationStartingPosition, cancellationToken); foreach (var implicitUsageConflict in implicitUsageConflicts) { conflictResolution.AddOrReplaceRelatedLocation(new RelatedLocation(implicitUsageConflict.SourceSpan, conflictResolution.OldSolution.GetDocument(implicitUsageConflict.SourceTree).Id, RelatedLocationType.UnresolvableConflict)); } } if (implicitReferenceLocations.IsEmpty()) { return; } foreach (var implicitReferenceLocationsPerLanguage in implicitReferenceLocations.GroupBy(loc => loc.Document.Project.Language)) { // the location of the implicit reference defines the language rules to check. // E.g. foreach in C# using a MoveNext in VB that is renamed to MOVENEXT (within VB) var renameRewriterService = implicitReferenceLocationsPerLanguage.First().Document.Project.LanguageServices.GetService<IRenameRewriterLanguageService>(); var implicitConflicts = renameRewriterService.ComputeImplicitReferenceConflicts( originalSymbol, renamedSymbol, implicitReferenceLocationsPerLanguage, cancellationToken); foreach (var implicitConflict in implicitConflicts) { conflictResolution.AddRelatedLocation(new RelatedLocation(implicitConflict.SourceSpan, conflictResolution.OldSolution.GetDocument(implicitConflict.SourceTree).Id, RelatedLocationType.UnresolvableConflict)); } } }
// The method which performs rename, resolves the conflict locations and returns the result of the rename operation public async Task <ConflictResolution> ResolveConflictsAsync() { try { await FindDocumentsAndPossibleNameConflicts().ConfigureAwait(false); var baseSolution = _renameLocationSet.Solution; // 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), _cancellationToken); var renamedSpansTracker = new RenamedSpansTracker(); var conflictResolution = new ConflictResolution(baseSolution, renamedSpansTracker, _replacementText, _replacementTextValid); foreach (var documentsByProject in documentsGroupedByTopologicallySortedProjectId) { var documentIdsThatGetsAnnotatedAndRenamed = new HashSet <DocumentId>(documentsByProject); using (baseSolution.Services.CacheService?.EnableCaching(documentsByProject.Key)) { // 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.NewSolution, documentIdsThatGetsAnnotatedAndRenamed, _renameLocationSet.Locations, renamedSpansTracker, _replacementTextValid).ConfigureAwait(false)); // Step 2: Check for conflicts in the renamed solution var foundResolvableConflicts = await IdentifyConflictsAsync( documentIdsForConflictResolution : documentIdsThatGetsAnnotatedAndRenamed, allDocumentIdsInProject : documentsByProject, projectId : documentsByProject.Key, conflictResolution : conflictResolution).ConfigureAwait(false); if (!foundResolvableConflicts || phase == 3) { break; } if (phase == 0) { _conflictLocations = conflictResolution.RelatedLocations .Where(loc => (documentIdsThatGetsAnnotatedAndRenamed.Contains(loc.DocumentId) && loc.Type == RelatedLocationType.PossiblyResolvableConflict && loc.IsReference)) .Select(loc => new ConflictLocationInfo(loc)) .ToSet(); // 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.Concat(conflictResolution.RelatedLocations .Where(loc => documentIdsThatGetsAnnotatedAndRenamed.Contains(loc.DocumentId) && loc.Type == RelatedLocationType.PossiblyResolvableConflict) .Select(loc => new ConflictLocationInfo(loc))) .ToSet(); } // 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))).ToSet(); } // 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.NewSolution, documentsByProject, _replacementTextValid, _renameAnnotations, _cancellationToken).ConfigureAwait(false)); await conflictResolution.RemoveAllRenameAnnotationsAsync(documentsByProject, _renameAnnotations, _cancellationToken).ConfigureAwait(false); } } // 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.NewSolution.GetDocument(_documentIdOfRenameSymbolDeclaration).GetSemanticModelAsync(_cancellationToken).ConfigureAwait(false), _renameSymbolDeclarationLocation, renamedSpansTracker.GetAdjustedPosition(_renameSymbolDeclarationLocation.SourceSpan.Start, _documentIdOfRenameSymbolDeclaration), conflictResolution, _cancellationToken).ConfigureAwait(false); } foreach (var relatedLocation in conflictResolution.RelatedLocations) { if (relatedLocation.Type == RelatedLocationType.PossiblyResolvableConflict) { relatedLocation.Type = RelatedLocationType.UnresolvedConflict; } } #if DEBUG await DebugVerifyNoErrorsAsync(conflictResolution, _documentsIdsToBeCheckedForConflict).ConfigureAwait(false); #endif // Step 5: Rename declaration files if (_optionSet.GetOption(RenameOptions.RenameFile)) { var definitionLocations = _renameLocationSet.Symbol.Locations; var definitionDocuments = definitionLocations .Select(l => conflictResolution.OldSolution.GetDocument(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.ReportUnlessCanceled(e)) { throw ExceptionUtilities.Unreachable; } }
/// <summary> /// Computes an adds conflicts relating to declarations, which are independent of /// location-based checks. Examples of these types of conflicts include renaming a member to /// the same name as another member of a type: binding doesn't change (at least from the /// perspective of find all references), but we still need to track it. /// </summary> internal static async Task AddDeclarationConflictsAsync( ISymbol renamedSymbol, ISymbol renameSymbol, IEnumerable<ISymbol> referencedSymbols, ConflictResolution conflictResolution, IDictionary<Location, Location> reverseMappedLocations, CancellationToken cancellationToken) { if (renamedSymbol.ContainingSymbol is INamedTypeSymbol) { var otherThingsNamedTheSame = renamedSymbol.ContainingType.GetMembers(renamedSymbol.Name) .Where(s => !s.Equals(renamedSymbol) && string.Equals(s.MetadataName, renamedSymbol.MetadataName, StringComparison.Ordinal) && (s.Kind != SymbolKind.Method || renamedSymbol.Kind != SymbolKind.Method)).ToList(); AddConflictingSymbolLocations(otherThingsNamedTheSame, conflictResolution, reverseMappedLocations); } // Some types of symbols (namespaces, cref stuff, etc) might not have ContainingAssemblies if (renamedSymbol.ContainingAssembly != null) { var project = conflictResolution.NewSolution.GetProject(renamedSymbol.ContainingAssembly, cancellationToken); // There also might be language specific rules we need to include var languageRenameService = project.LanguageServices.GetService<IRenameRewriterLanguageService>(); var languageConflicts = await languageRenameService.ComputeDeclarationConflictsAsync( conflictResolution.ReplacementText, renamedSymbol, renameSymbol, referencedSymbols, conflictResolution.OldSolution, conflictResolution.NewSolution, reverseMappedLocations, cancellationToken).ConfigureAwait(false); foreach (var languageConflict in languageConflicts) { conflictResolution.AddOrReplaceRelatedLocation(new RelatedLocation(languageConflict.SourceSpan, conflictResolution.OldSolution.GetDocument(languageConflict.SourceTree).Id, RelatedLocationType.UnresolvableConflict)); } } }
private async Task <bool> CheckForConflictAsync( ConflictResolution conflictResolution, ISymbol renamedSymbolInNewSolution, Document newDocument, RenameActionAnnotation conflictAnnotation, IEnumerable <ISymbol> newReferencedSymbols) { try { bool hasConflict; var solution = conflictResolution.NewSolution; 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)).Where(loc => loc != null && 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.Count() == 1) { // an ambiguous situation was resolved through rename in non reference locations hasConflict = false; } else if (newReferencedSymbols.Count() != conflictAnnotation.RenameDeclarationLocationReferences.Length) { // Don't show conflicts for errors in the old solution that now bind in the new solution. if (newReferencedSymbols.Count() != 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.GetDocument(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.OverriddenMember(); 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.ReportUnlessCanceled(e)) { throw ExceptionUtilities.Unreachable; } }
private static void AddConflictingSymbolLocations(IEnumerable<ISymbol> conflictingSymbols, ConflictResolution conflictResolution, IDictionary<Location, Location> reverseMappedLocations) { foreach (var newSymbol in conflictingSymbols) { foreach (var newLocation in newSymbol.Locations) { if (newLocation.IsInSource) { Location oldLocation; if (reverseMappedLocations.TryGetValue(newLocation, out oldLocation)) { conflictResolution.AddOrReplaceRelatedLocation(new RelatedLocation(oldLocation.SourceSpan, conflictResolution.OldSolution.GetDocument(oldLocation.SourceTree).Id, RelatedLocationType.UnresolvableConflict)); } } } } }