-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix goto-def on partial methods in LSP #68559
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,11 +20,13 @@ public async Task<ImmutableArray<INavigableItem>> FindDefinitionsAsync( | |
var symbolService = document.GetRequiredLanguageService<IGoToDefinitionSymbolService>(); | ||
var (symbol, project, _) = await symbolService.GetSymbolProjectAndBoundSpanAsync(document, position, includeType: true, cancellationToken).ConfigureAwait(false); | ||
|
||
symbol = await SymbolFinder.FindSourceDefinitionAsync(symbol, project.Solution, cancellationToken).ConfigureAwait(false) ?? symbol; | ||
var solution = project.Solution; | ||
symbol = await SymbolFinder.FindSourceDefinitionAsync(symbol, solution, cancellationToken).ConfigureAwait(false) ?? symbol; | ||
symbol = await GoToDefinitionFeatureHelpers.TryGetPreferredSymbolAsync(solution, symbol, cancellationToken).ConfigureAwait(false); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this continues the long pain of having something like 3 different goto-def impls in roslyn, which do/don't use the same set of underlying helpers to get consistent results. This includes IGoToDefinitionService, IAsyncGoToDefinitionService, and IFindDefinitionService. Ideally we can get to a place where this all gets burned down. But it's woven so deeply into different systems that that's going to be very hard for a while. |
||
|
||
// Try to compute source definitions from symbol. | ||
return symbol != null | ||
? NavigableItemFactory.GetItemsFromPreferredSourceLocations(project.Solution, symbol, displayTaggedParts: FindUsagesHelpers.GetDisplayParts(symbol), cancellationToken: cancellationToken) | ||
? NavigableItemFactory.GetItemsFromPreferredSourceLocations(solution, symbol, displayTaggedParts: FindUsagesHelpers.GetDisplayParts(symbol), cancellationToken: cancellationToken) | ||
: ImmutableArray<INavigableItem>.Empty; | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
// 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.Linq; | ||
using System.Text; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.CodeAnalysis.FindSymbols; | ||
using Microsoft.CodeAnalysis.FindUsages; | ||
using Microsoft.CodeAnalysis.Navigation; | ||
using Microsoft.CodeAnalysis.PooledObjects; | ||
|
||
namespace Microsoft.CodeAnalysis.GoToDefinition | ||
{ | ||
internal static class GoToDefinitionFeatureHelpers | ||
{ | ||
public static async Task<ISymbol?> TryGetPreferredSymbolAsync( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. pure move. |
||
Solution solution, ISymbol? symbol, CancellationToken cancellationToken) | ||
{ | ||
// VB global import aliases have a synthesized SyntaxTree. | ||
// We can't go to the definition of the alias, so use the target type. | ||
|
||
var alias = symbol as IAliasSymbol; | ||
if (alias != null) | ||
{ | ||
if (alias.Target is INamespaceSymbol ns && ns.IsGlobalNamespace) | ||
return null; | ||
} | ||
|
||
// VB global import aliases have a synthesized SyntaxTree. | ||
// We can't go to the definition of the alias, so use the target type. | ||
|
||
if (alias != null) | ||
{ | ||
var sourceLocations = NavigableItemFactory.GetPreferredSourceLocations( | ||
solution, symbol, cancellationToken); | ||
|
||
if (sourceLocations.All(l => solution.GetDocument(l.SourceTree) == null)) | ||
symbol = alias.Target; | ||
} | ||
|
||
var definition = await SymbolFinder.FindSourceDefinitionAsync(symbol, solution, cancellationToken).ConfigureAwait(false); | ||
cancellationToken.ThrowIfCancellationRequested(); | ||
|
||
symbol = definition ?? symbol; | ||
|
||
// If it is a partial method declaration with no body, choose to go to the implementation | ||
// that has a method body. | ||
if (symbol is IMethodSymbol method) | ||
symbol = method.PartialImplementationPart ?? symbol; | ||
|
||
return symbol; | ||
} | ||
|
||
public static async Task<ImmutableArray<DefinitionItem>> GetDefinitionsAsync( | ||
ISymbol? symbol, | ||
Solution solution, | ||
bool thirdPartyNavigationAllowed, | ||
CancellationToken cancellationToken) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. pure move. |
||
{ | ||
symbol = await TryGetPreferredSymbolAsync(solution, symbol, cancellationToken).ConfigureAwait(false); | ||
if (symbol is null) | ||
return ImmutableArray.Create<DefinitionItem>(); | ||
|
||
using var _ = ArrayBuilder<DefinitionItem>.GetInstance(out var definitions); | ||
|
||
// Going to a symbol may end up actually showing the symbol in the Find-Usages window. | ||
// This happens when there is more than one location for the symbol (i.e. for partial | ||
// symbols) and we don't know the best place to take you to. | ||
// | ||
// The FindUsages window supports showing the classified text for an item. It does this | ||
// in two ways. Either the item can pass along its classified text (and the window will | ||
// defer to that), or the item will have no classified text, and the window will compute | ||
// it in the BG. | ||
// | ||
// Passing along the classified information is valuable for OOP scenarios where we want | ||
// all that expensive computation done on the OOP side and not in the VS side. | ||
// | ||
// However, Go To Definition is all in-process, and is also synchronous. So we do not | ||
// want to fetch the classifications here. It slows down the command and leads to a | ||
// measurable delay in our perf tests. | ||
// | ||
// So, if we only have a single location to go to, this does no unnecessary work. And, | ||
// if we do have multiple locations to show, it will just be done in the BG, unblocking | ||
// this command thread so it can return the user faster. | ||
var definitionItem = symbol.ToNonClassifiedDefinitionItem(solution, includeHiddenLocations: true); | ||
|
||
if (thirdPartyNavigationAllowed) | ||
{ | ||
var factory = solution.Services.GetService<IDefinitionsAndReferencesFactory>(); | ||
if (factory != null) | ||
{ | ||
var thirdPartyItem = await factory.GetThirdPartyDefinitionItemAsync(solution, definitionItem, cancellationToken).ConfigureAwait(false); | ||
definitions.AddIfNotNull(thirdPartyItem); | ||
} | ||
} | ||
|
||
definitions.Add(definitionItem); | ||
return definitions.ToImmutable(); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moved to common location. not tied to 'EditorFeatures' (e.g. VS) impl of goto-def.