// 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.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis; internal partial struct SymbolKey { private static class BodyLevelSymbolKey { public static ImmutableArray<Location?> GetBodyLevelSourceLocations(ISymbol symbol, CancellationToken cancellationToken) { Contract.ThrowIfFalse(IsBodyLevelSymbol(symbol)); Contract.ThrowIfTrue(symbol.DeclaringSyntaxReferences.IsEmpty && symbol.Locations.IsEmpty); using var _ = ArrayBuilder<Location?>.GetInstance(out var result); foreach (var location in symbol.Locations) { if (location.IsInSource) result.Add(location); } foreach (var syntaxRef in symbol.DeclaringSyntaxReferences) result.Add(syntaxRef.GetSyntax(cancellationToken).GetLocation()); return result.ToImmutableAndClear(); } public static void Create(ISymbol symbol, SymbolKeyWriter visitor) { var cancellationToken = visitor.CancellationToken; // Store the body level symbol in two forms. The first, a highly precise form that should find explicit // symbols for the case of resolving a symbol key back in the *same* solution snapshot it was created // from. The second, in a more query-oriented form that can allow the symbol to be found in some cases // even if the solution changed (which is a supported use case for SymbolKey). // // The first way just stores the location of the symbol, which we can then validate during resolution // maps back to the same symbol kind/name. // // The second determines the sequence of symbols of the same kind and same name in the file and keeps // track of our index in that sequence. That way, if trivial edits happen, or symbols with different // names/types are added/removed, we can still find what is likely to be this symbol after the edit. var kind = symbol.Kind; var localName = symbol.Name; visitor.WriteString(localName); visitor.WriteInteger((int)kind); // write out the locations for precision Contract.ThrowIfTrue(symbol.DeclaringSyntaxReferences.IsEmpty && symbol.Locations.IsEmpty); var locations = GetBodyLevelSourceLocations(symbol, cancellationToken); Contract.ThrowIfFalse(locations.All(loc => loc != null && loc.IsInSource)); visitor.WriteLocationArray(locations.Distinct()); // and the containingSymbol/ordinal for resilience var container = symbol.ContainingSymbol; visitor.WriteSymbolKey(container); visitor.WriteInteger(GetOrdinal()); return; int GetOrdinal() { var syntaxTree = locations[0]!.SourceTree; var compilation = ((ISourceAssemblySymbol)symbol.ContainingAssembly).Compilation; // See if we can find an appropriate container for this local and attempt to find this local's index // within it. var containerDeclaration = TryGetContainerDeclaration(container, syntaxTree, cancellationToken); // Ensure that the tree we're looking at is actually in this compilation. It may not be in the // compilation in the case of work done with a speculative model. if (containerDeclaration != null && TryGetSemanticModel(compilation, syntaxTree, out var semanticModel)) { foreach (var possibleSymbol in EnumerateSymbols(semanticModel, containerDeclaration, kind, localName, cancellationToken)) { if (possibleSymbol.symbol.Equals(symbol)) return possibleSymbol.ordinal; } } return int.MaxValue; } } private static SyntaxNode? TryGetContainerDeclaration(ISymbol container, SyntaxTree? syntaxTree, CancellationToken cancellationToken) { if (syntaxTree != null) { foreach (var reference in container.DeclaringSyntaxReferences) { if (reference.SyntaxTree == syntaxTree) return reference.GetSyntax(cancellationToken); } } return null; } private static bool TryGetSemanticModel( Compilation compilation, SyntaxTree? syntaxTree, [NotNullWhen(true)] out SemanticModel? semanticModel) { // Ensure that the tree we're looking at is actually in this compilation. It may not be in the // compilation in the case of work done with a speculative model. if (syntaxTree != null && Contains(compilation.SyntaxTrees, syntaxTree)) { semanticModel = compilation.GetSemanticModel(syntaxTree); return true; } semanticModel = null; return false; } public static SymbolKeyResolution Resolve(SymbolKeyReader reader, out string? failureReason) { var cancellationToken = reader.CancellationToken; var name = reader.ReadRequiredString(); var kind = (SymbolKind)reader.ReadInteger(); using var locations = reader.ReadLocationArray(out var locationsFailureReason); var containingSymbol = reader.ReadSymbolKey(contextualSymbol: null, out var containingSymbolFailureReason); var ordinal = reader.ReadInteger(); if (locationsFailureReason != null) { failureReason = $"({nameof(BodyLevelSymbolKey)} {nameof(locations)} failed -> {locationsFailureReason})"; return default; } // First check if we can recover the symbol just through the original location. string? totalFailureReason = null; for (var i = 0; i < locations.Count; i++) { var loc = locations[i]; if (loc is null) continue; if (!TryResolveLocation(loc, i, out var resolution, out var reason)) { totalFailureReason = totalFailureReason == null ? $"({reason})" : $"({totalFailureReason} -> {reason})"; continue; } failureReason = null; return resolution; } // Couldn't recover. See if we can still find a match across the textual drift. if (ordinal != int.MaxValue) { if (containingSymbolFailureReason != null) { var reason = $"({nameof(BodyLevelSymbolKey)} {nameof(containingSymbol)} failed -> {containingSymbolFailureReason})"; totalFailureReason = totalFailureReason == null ? $"({reason})" : $"({totalFailureReason} -> {reason})"; } else { var firstSourceTree = locations[0]?.SourceTree; var containerDeclaration = GetContainerDeclaration(firstSourceTree); if (containerDeclaration != null && TryGetSemanticModel(reader.Compilation, firstSourceTree, out var semanticModel)) { foreach (var symbol in EnumerateSymbols(semanticModel, containerDeclaration, kind, name, cancellationToken)) { if (symbol.ordinal == ordinal) { failureReason = null; return new SymbolKeyResolution(symbol.symbol); } } } } } failureReason = $"({nameof(BodyLevelSymbolKey)} '{name}' not found -> {totalFailureReason})"; return default; SyntaxNode? GetContainerDeclaration(SyntaxTree? syntaxTree) { if (syntaxTree != null) { foreach (var container in containingSymbol) { foreach (var reference in container.DeclaringSyntaxReferences) { if (reference.SyntaxTree == syntaxTree) return reference.GetSyntax(cancellationToken); } } } return null; } bool TryResolveLocation(Location loc, int index, out SymbolKeyResolution resolution, out string? reason) { var resolutionOpt = reader.ResolveLocation(loc); if (resolutionOpt == null) { reason = $"location {index} failed to resolve"; resolution = default; return false; } resolution = resolutionOpt.Value; var symbol = resolution.GetAnySymbol(); if (symbol == null) { reason = $"location {index} did not produce any symbol"; return false; } if (symbol.Kind != kind) { reason = $"location {index} did not match kind: {symbol.Kind} != {kind}"; return false; } if (!SymbolKey.Equals(reader.Compilation, name, symbol.Name)) { reason = $"location {index} did not match name: {symbol.Name} != {name}"; return false; } reason = null; return true; } } private static IEnumerable<(ISymbol symbol, int ordinal)> EnumerateSymbols( SemanticModel semanticModel, SyntaxNode containerDeclaration, SymbolKind kind, string localName, CancellationToken cancellationToken) { Contract.ThrowIfTrue(semanticModel.SyntaxTree != containerDeclaration.SyntaxTree); var ordinal = 0; foreach (var node in containerDeclaration.DescendantNodes()) { var symbol = semanticModel.GetDeclaredSymbol(node, cancellationToken); if (symbol?.Kind == kind && SymbolKey.Equals(semanticModel.Compilation, symbol.Name, localName)) { yield return (symbol, ordinal++); } } } private static bool Contains(IEnumerable<SyntaxTree> trees, SyntaxTree tree) { foreach (var current in trees) { if (current == tree) return true; } return false; } } } |