-
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
Don't use symbolid in symbol completion #15688
Conversation
Tag @dotnet/roslyn-ide @CyrusNajmabadi for review |
// To get a Speculative SemanticModel (which is much faster), we need to | ||
// walk up to the node the DocumentationTrivia is attached to. | ||
var parentNode = token.Parent.FirstAncestorOrSelf<DocumentationCommentTriviaSyntax>()?.ParentTrivia.Token.Parent; | ||
_testSpeculativeNodeCallbackOpt?.Invoke(parentNode); |
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.
This entire method seems hugely complex. Can we not store more information in the item to make it so that recovery at this point is super simple to do?
@@ -10,6 +10,7 @@ | |||
using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery; | |||
using Microsoft.CodeAnalysis.Shared.Utilities; | |||
using Roslyn.Utilities; | |||
using Microsoft.CodeAnalysis.Text; |
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.
sorting.
|
||
var kind = SymbolCompletionItem.GetKind(item); | ||
|
||
var bestSymbols = symbols.Where(s => (kind != null && s.Kind == kind) && s.Name == name).ToImmutableArray(); |
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.
inner parens not needed. Also, you can do .WhereAsArray.
{ | ||
var position = SymbolCompletionItem.GetContextPosition(item); | ||
var name = SymbolCompletionItem.GetSymbolName(item); | ||
var semanticModel = await document.GetSemanticModelForSpanAsync(new TextSpan(position, 0), cancellationToken).ConfigureAwait(false); |
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.
wrap long lines.
public static CompletionItem CreateWithNameAndKind( | ||
string displayText, | ||
ISymbol symbol, | ||
int contextPosition = -1, |
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.
what does a -1 position mean. Why would we never not have a context position?
internal static SymbolKind? GetKind(CompletionItem item) | ||
{ | ||
string kind; | ||
if (item.Properties.TryGetValue("SymbolKind", out kind)) |
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.
out var.
internal static string GetSymbolName(CompletionItem item) | ||
{ | ||
string name; | ||
if (item.Properties.TryGetValue("SymbolName", out name)) |
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.
out var
|
||
namespace Microsoft.CodeAnalysis.Completion.Providers | ||
{ | ||
internal static partial class SymbolCompletionItem |
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.
why is this in a new part?
basically, i have no trust that something isn't busted at recovery time. That code should be exceptionally simple so that there's practically no way to get it wrong. |
Also tagging @mattwar |
Re: (2) the problem is that everything we store needs to be serializable to string. Since we're not storing the SymbolId any more, we actually have to get a new SemanticModel and look up symbols again. See what happens in AbstractRecommendationBasedCOmpletionProvider.cs--we create a SemanticModel at the same position as when completion was started and then the same Recommender API to get symbols again. For the cref providers, I think what's needed is a better refactoring of the symbol lookup code so that ProvideCompletionsAsync and GetDescriptionAsync actually call the same code. Does that seem reasonable? |
@CyrusNajmabadi Re: (1) I haven't taken a trace yet but this change removes the actual call the customers trace spent all of its time in. I'll verify this this week. |
👍 |
@CyrusNajmabadi cleaned things up if you want to take another look |
Yes. What i would expect to see would be nothing but extraction of a few values in the completion item, which we would then use to call into the same code that created the items in the first place. GetDescriptionAsync should be extremely simple. |
@@ -60,10 +67,29 @@ internal override bool IsInsertionTrigger(SourceText text, int characterPosition | |||
var options = context.Options; | |||
var cancellationToken = context.CancellationToken; | |||
|
|||
var (token, semanticModel, symbols) = await GetSymbols(document, position, cancellationToken).ConfigureAwait(false); | |||
var filteredSymbols = symbols.FilterToVisibleAndBrowsableSymbols(options.GetOption(CompletionOptions.HideAdvancedMembers, semanticModel.Language), semanticModel.Compilation); |
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.
why is FilterToVisibleAndBrowsableSymbols not in GetSymbols?
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 into and propagated editorvisible option which is apparently not consumed by the VB side.
@@ -312,8 +322,16 @@ private static TextSpan GetCompletionItemSpan(SourceText text, int position) | |||
rules: GetRules(insertionText)); | |||
} | |||
|
|||
protected override Task<CompletionDescription> GetDescriptionWorkerAsync(Document document, CompletionItem item, CancellationToken cancellationToken) | |||
=> SymbolCompletionItem.GetDescriptionAsync(item, document, cancellationToken); | |||
protected override async Task<CompletionDescription> GetDescriptionWorkerAsync(Document document, CompletionItem item, CancellationToken cancellationToken) |
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.
much better :)
filterText, matchPriority, supportedPlatforms, properties, tags, rules); | ||
} | ||
|
||
public static CompletionItem CreateWithSymbolId( |
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.
i don't see why we need both CreateWithSymbolId.
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.
There are lots of callers of both overloads: some providers wind up with a list of symbols collapsed into one item and some don't.
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.
that seems excessive. for the providers with just one symbol (really, that happens?) why not just call the above method putting the symbol into an immutable array.
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.
same comment applies.
filterText, matchPriority, supportedPlatforms, properties, tags, rules); | ||
} | ||
|
||
public static CompletionItem CreateWithNameAndKind( |
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.
why do we need both CreateWithNameAndKinds?
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.
There were only 2 callers of the single-symbol overload so I switched them to just calling the collection overload.
var workspace = document.Project.Solution.Workspace; | ||
|
||
var position = SymbolCompletionItem.GetDescriptionPosition(item); | ||
if (position == -1) |
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.
is this possible?
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.
I don't think so.
Dim tree = Await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(False) | ||
Dim token = tree.GetTargetToken(position, cancellationToken) | ||
|
||
|
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.
remove extra line.
Dim symbolName = SymbolCompletionItem.GetSymbolName(item) | ||
Dim symbolKind = SymbolCompletionItem.GetKind(item).GetValueOrDefault() | ||
Dim bestSymbols = symbols.Where(Function(s) s.Kind = symbolKind AndAlso s.Name = symbolName).ToImmutableArray() | ||
Return Await SymbolCompletionItem.GetDescriptionAsync(item, bestSymbols, document, cancellationToken).ConfigureAwait(False) |
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.
these 5 lines of code look like they were repeated elsewhere. Unify plz :)
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.
Done.
Looks much nicer. Still needs some work though. Thanks! |
var hideAdvancedMembers = options.GetOption(CompletionOptions.HideAdvancedMembers, semanticModel.Language); | ||
var serializedOptions = ImmutableDictionary<string, string>.Empty.Add(HideAdvancedMembers, hideAdvancedMembers.ToString()); | ||
|
||
var items = CreateCompletionItems(document.Project.Solution.Workspace, semanticModel, symbols, token, span, position, serializedOptions); |
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.
wrap long lines.
|
||
context.IsExclusive = true; | ||
var symbols = GetSymbols(token, semanticModel, cancellationToken) | ||
.FilterToVisibleAndBrowsableSymbols(options.GetOption(CompletionOptions.HideAdvancedMembers, semanticModel.Language), semanticModel.Compilation); |
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.
wrap long lines.
rules: GetRules(insertionText)); | ||
} | ||
|
||
protected override Task<CompletionDescription> GetDescriptionWorkerAsync(Document document, CompletionItem item, CancellationToken cancellationToken) | ||
=> SymbolCompletionItem.GetDescriptionAsync(item, document, cancellationToken); | ||
|
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.
too many newlines.
@@ -1,15 +1,16 @@ | |||
using System.Collections.Generic; |
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.
copyright.
@@ -100,5 +101,21 @@ private static int ComputeSymbolMatchPriority(ISymbol symbol) | |||
|
|||
return SymbolMatchPriority.PreferType; | |||
} | |||
|
|||
protected override async Task<CompletionDescription> GetDescriptionWorkerAsync(Document document, CompletionItem item, CancellationToken cancellationToken) |
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.
wrap long lines.
var name = SymbolCompletionItem.GetSymbolName(item); | ||
var semanticModel = await document.GetSemanticModelForSpanAsync(new TextSpan(position, 0), cancellationToken) | ||
.ConfigureAwait(false); | ||
var symbols = await Recommender.GetImmutableRecommendedSymbolsAtPositionAsync( |
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.
instead of taking an semantic model, workspace and options, could this not just take a document?
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.
I believe it takes a SemanticModel so that callers can produce SpeculativeSemanticModels as neccessary.
contextDocument = document.Project.Solution.GetDocument(contextId); | ||
} | ||
} | ||
Document contextDocument = FindAppropriateDocumentForDescriptionContext(document, supportedPlatforms); |
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.
var
Dim token = tree.GetTargetToken(position, cancellationToken) | ||
|
||
If IsCrefTypeParameterContext(token) Then | ||
Return (Nothing, Nothing, Nothing) |
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.
can just return 'nothing'.
displayText:=displayString, | ||
insertionText:=Nothing, | ||
symbol:=symbol, | ||
symbols:=ImmutableArray.Create(symbol), |
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.
i.e. like what you did here.
Looking better! :) |
@CyrusNajmabadi Any remaining thoughts? I had to add a bit of complexity in order to properly support linked files but it uses most of the existing infrastructure. |
@jasonmalinowski @DustinCampbell Do we have a branch we're targeting for QFE changes? |
I guess it would be microupdate? I'm not 100% sure though. @jasonmalinowski will know best. |
@rchande: you should target this against microupdate. |
} | ||
|
||
var options = document.Project.Solution.Workspace.Options | ||
.WithChangedOption(new OptionKey(CompletionOptions.HideAdvancedMembers, document.Project.Language), hideAdvancedMembers); |
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.
why are we changing options? why are we not just using the option as defined by the user?
Doing this actually overrides the user's choice here.
var kind = SymbolCompletionItem.GetKind(item); | ||
var relatedDocumentIds = document.Project.Solution.GetRelatedDocumentIds(document.Id).Concat(document.Id); | ||
var options = document.Project.Solution.Workspace.Options; | ||
var totalSymbols = await base.GetPerContextSymbols(document, position, options, relatedDocumentIds, preselect: false, cancellationToken: cancellationToken).ConfigureAwait(false); |
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.
why base.?
Use editorbrowsable options when computing cref descriptions Share cref description computation code between C#/VB Etc
23992e4
to
df29fa8
Compare
When creating SymbolCompletionItems, creators now have to specify if they want to encode a SymbolId for later use in documentation display. Providers that create a lot of items (Symbol completion, Cref compeltion) don't store a SymbolId. Instead, when trying to compute documentation for a symbol, they rereun their lookup logic and attempt to find a symbol with the same name and kind as the currently selected completion and display the documentation for that symbol.