|
// 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);
}
}
|