Skip to content
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

Merged
merged 9 commits into from
Jan 4, 2017

Conversation

rchande
Copy link
Contributor

@rchande rchande commented Dec 5, 2016

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.

@rchande rchande changed the title Don't use symbolid in symbl completion Don't use symbolid in symbol completion Dec 5, 2016
@rchande
Copy link
Contributor Author

rchande commented Dec 5, 2016

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);
Copy link
Member

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;
Copy link
Member

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();
Copy link
Member

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);
Copy link
Member

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,
Copy link
Member

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))
Copy link
Member

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))
Copy link
Member

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
Copy link
Member

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?

@CyrusNajmabadi
Copy link
Member

  1. Have we verified the perf benefit here? (it totally seems reasonable, i just want to make sure).
  2. I really think we can simplify this by just encoding the data we had when the completion items were created, thus obviating the need for so much complexity at recovery time.

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.

@Pilchie
Copy link
Member

Pilchie commented Dec 5, 2016

Also tagging @mattwar

@rchande
Copy link
Contributor Author

rchande commented Dec 5, 2016

@CyrusNajmabadi

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?

@rchande
Copy link
Contributor Author

rchande commented Dec 5, 2016

@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.

@mattwar
Copy link
Contributor

mattwar commented Dec 5, 2016

👍

@rchande
Copy link
Contributor Author

rchande commented Dec 5, 2016

@CyrusNajmabadi cleaned things up if you want to take another look

@CyrusNajmabadi
Copy link
Member

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?

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);
Copy link
Member

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?

Copy link
Contributor Author

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)
Copy link
Member

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(
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Member

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(
Copy link
Member

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?

Copy link
Contributor Author

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this possible?

Copy link
Contributor Author

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)


Copy link
Member

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)
Copy link
Member

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 :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@CyrusNajmabadi
Copy link
Member

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);
Copy link
Member

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);
Copy link
Member

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

Copy link
Member

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;
Copy link
Member

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)
Copy link
Member

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(
Copy link
Member

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?

Copy link
Contributor Author

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);
Copy link
Member

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)
Copy link
Member

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),
Copy link
Member

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.

@CyrusNajmabadi
Copy link
Member

Looking better! :)

@rchande
Copy link
Contributor Author

rchande commented Dec 7, 2016

@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.

@rchande
Copy link
Contributor Author

rchande commented Dec 7, 2016

@jasonmalinowski @DustinCampbell Do we have a branch we're targeting for QFE changes?

@DustinCampbell
Copy link
Member

I guess it would be microupdate? I'm not 100% sure though. @jasonmalinowski will know best.

@jasonmalinowski
Copy link
Member

@rchande: you should target this against microupdate.

@rchande rchande changed the base branch from master to microupdate December 7, 2016 20:33
@rchande rchande changed the base branch from microupdate to master December 7, 2016 20:48
}

var options = document.Project.Solution.Workspace.Options
.WithChangedOption(new OptionKey(CompletionOptions.HideAdvancedMembers, document.Project.Language), hideAdvancedMembers);
Copy link
Member

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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why base.?

@rchande rchande force-pushed the fastersymbolcompletion branch from 23992e4 to df29fa8 Compare January 4, 2017 18:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants