File: src\Workspaces\SharedUtilitiesAndExtensions\Workspace\Core\SymbolFinder\SymbolFinderInternal.cs
Web Access
Project: src\src\CodeStyle\Core\CodeFixes\Microsoft.CodeAnalysis.CodeStyle.Fixes.csproj (Microsoft.CodeAnalysis.CodeStyle.Fixes)
// 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.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.FindSymbols;
 
internal static class SymbolFinderInternal
{
    /// <inheritdoc cref="SymbolFinder.FindSourceDefinitionAsync"/>
    internal static ISymbol? FindSourceDefinition(
        ISymbol? symbol, Solution solution, CancellationToken cancellationToken)
    {
        if (symbol != null)
        {
            symbol = symbol.GetOriginalUnreducedDefinition();
            switch (symbol.Kind)
            {
                case SymbolKind.Event:
                case SymbolKind.Field:
                case SymbolKind.Method:
                case SymbolKind.Local:
                case SymbolKind.NamedType:
                case SymbolKind.Parameter:
                case SymbolKind.Property:
                case SymbolKind.TypeParameter:
                case SymbolKind.Namespace:
                    return FindSourceDefinitionWorker(symbol, solution, cancellationToken);
            }
        }
 
        return null;
    }
 
    private static ISymbol? FindSourceDefinitionWorker(
        ISymbol symbol,
        Solution solution,
        CancellationToken cancellationToken)
    {
        // If it's already in source, then we might already be done
        if (InSource(symbol))
        {
            // If our symbol doesn't have a containing assembly, there's nothing better we can do to map this
            // symbol somewhere else. The common case for this is a merged INamespaceSymbol that spans assemblies.
            if (symbol.ContainingAssembly == null)
                return symbol;
 
            // Just because it's a source symbol doesn't mean we have the final symbol we actually want. In retargeting cases,
            // the retargeted symbol is from "source" but isn't equal to the actual source definition in the other project. Thus,
            // we only want to return symbols from source here if it actually came from a project's compilation's assembly. If it isn't
            // then we have a retargeting scenario and want to take our usual path below as if it was a metadata reference
            foreach (var sourceProject in solution.Projects)
            {
                // If our symbol is actually a "regular" source symbol, then we know the compilation is holding the symbol alive
                // and thus TryGetCompilation is sufficient. For another example of this pattern, see Solution.GetProject(IAssemblySymbol)
                // which we happen to call below.
                if (sourceProject.TryGetCompilation(out var compilation) &&
                    symbol.ContainingAssembly.Equals(compilation.Assembly))
                {
                    return symbol;
                }
            }
        }
        else if (!symbol.Locations.Any(static loc => loc.IsInMetadata))
        {
            // We have a symbol that's neither in source nor metadata
            return null;
        }
 
        var project = solution.GetProject(symbol.ContainingAssembly, cancellationToken);
 
        // Note: if the assembly came from a particular project, then we should be able to get the compilation without
        // building it.  That's because once we create the compilation, we'll hold onto it for the lifetime of the
        // project, to avoid unnecessary recomputation.
        if (project?.TryGetCompilation(out var projectCompilation) is true)
        {
            var symbolId = symbol.GetSymbolKey(cancellationToken);
            var result = symbolId.Resolve(projectCompilation, ignoreAssemblyKey: true, cancellationToken: cancellationToken);
 
            return InSource(result.Symbol) ? result.Symbol : result.CandidateSymbols.FirstOrDefault(InSource);
        }
 
        return null;
 
        static bool InSource([NotNullWhen(true)] ISymbol? symbol)
           => symbol != null && symbol.Locations.Any(static loc => loc.IsInSource);
    }
}