-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Completion for unimported extension methods #39126
Conversation
happy to look. but perf analysis is def necessary. |
...e/Completion/CompletionProviders/ImportCompletion/ExtensionMethodImportCompletionProvider.cs
Outdated
Show resolved
Hide resolved
...arp/Portable/Completion/CompletionProviders/ImportCompletion/TypeImportCompletionProvider.cs
Outdated
Show resolved
Hide resolved
...letion/Providers/ImportCompletionProvider/AbstractExtensionMethodImportCompletionProvider.cs
Outdated
Show resolved
Hide resolved
...letion/Providers/ImportCompletionProvider/AbstractExtensionMethodImportCompletionProvider.cs
Outdated
Show resolved
Hide resolved
...letion/Providers/ImportCompletionProvider/AbstractExtensionMethodImportCompletionProvider.cs
Outdated
Show resolved
Hide resolved
...letion/Providers/ImportCompletionProvider/AbstractExtensionMethodImportCompletionProvider.cs
Outdated
Show resolved
Hide resolved
|
||
completionContext.AddItems(items); | ||
|
||
telemetryCounter.TotalExtensionMethodsProvided = items.Count; |
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.
todo: review this.
...letion/Providers/ImportCompletionProvider/AbstractExtensionMethodImportCompletionProvider.cs
Outdated
Show resolved
Hide resolved
...letion/Providers/ImportCompletionProvider/AbstractExtensionMethodImportCompletionProvider.cs
Show resolved
Hide resolved
...e/Portable/Completion/Providers/ImportCompletionProvider/AbstractImportCompletionProvider.cs
Show resolved
Hide resolved
return; | ||
} | ||
|
||
completionContext.ExpandItemsAvailable = true; |
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's this?
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 you need to do this if you already exported the member that said you were IsExpandItemProvider ? I don't get the purpose of having both flags.
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 two possible cases if expand provider returns nothing, first is there's actually nothing, another is we might be able to return something but didn't (e.g. timed out). We want to grey out the expander in the first case but show it as selectable in the latter.
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 you doc this? i honestly find it very confusing still. i would prefer ths all be unified somehow .
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's already doc for ExpandItemsAvailable
property
src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.Node.cs
Outdated
Show resolved
Hide resolved
src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.Node.cs
Outdated
Show resolved
Hide resolved
|
||
public ParameterTypeInfo GetArrayType(ParameterTypeInfo elementType, ArrayShape shape) => ComplexInfo; | ||
|
||
public ParameterTypeInfo GetSZArrayType(ParameterTypeInfo elementType) => ComplexInfo; |
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.
doc most of the code here.
src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.Node.cs
Outdated
Show resolved
Hide resolved
src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.Node.cs
Show resolved
Hide resolved
// categories for filtering purpose. Whether a method is simple is determined based on if we can determine | ||
// it's target type easily with a pure text matching. For complex methods, we will need to rely on symbol | ||
// to decide if it's feasible. | ||
// All primitive types, types in regular use form are considered simple. |
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.
"types in regular use form"?
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.
please add exmaples/explanations.
src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs
Outdated
Show resolved
Hide resolved
@@ -407,10 +422,18 @@ private void GenerateMetadataNodes() | |||
// we just pull in methods that have attributes on them. |
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 wonder if we can do better. at least see if hte attribute is called "Extension".
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.
Sure can do, it's just unclear how much benefit it would provide
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.
it would probably greatly decrease the number of methods we pull in.
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 still seems worth looking into IMO.
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 doesn't seem to cause any issues during my test in roslyn.sln. Will file a bug and consider this in a follow-up PR
var info = await document.GetSyntaxTreeIndexAsync(loadOnly, cancellationToken).ConfigureAwait(false); | ||
if (info == null) | ||
{ | ||
return null; |
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.
please doc that this is an intentional decision, and you didn't accidentally forget to 'continue' here.
...table/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs
Show resolved
Hide resolved
{ | ||
if (s_indexingTask.IsCompleted) | ||
{ | ||
s_indexingTask = Task.Run(() => GetPossibleExtensionMethodMatchesAsync(project, allTypeNames, forceIndexCreation: false, isPrecalculation: true, CancellationToken.None)); |
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 passing forceIndexCreation: false
here?
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.
fixed
// Don't provide anything if we don't have all the required SyntaxTreeIndex created. | ||
if (cacheEntry == null) | ||
{ | ||
return null; |
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.
doc this.
} | ||
} | ||
|
||
// We are just trying to populate the cache in background, no need to return any results. |
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.
ew. this is not pretty. a better way would be to extract out the cache computation logic and then call into that from both locations rather than having the precalculation logic cause it to happen (but hten skip some work) by calling into the actual query codepath.
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.
fixed
cancellationToken.ThrowIfCancellationRequested(); | ||
|
||
var indexOfLastDot = fullyQualifiedContainerName.LastIndexOf('.'); | ||
var qualifiedNamespaceName = indexOfLastDot > 0 ? fullyQualifiedContainerName.Substring(0, indexOfLastDot) : string.Empty; |
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's a pity. so we force a string-alloc on every item found? is this ok because the expetation is that the filter will hav ea small number of results?
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.
yes, it's on every containing type we found. And yes, with filter, this number should be fairly small so it's unlikely to cause perf issues.
IMethodSymbol? reducedMethodSymbol = null; | ||
|
||
if (methodSymbol.IsExtensionMethod && | ||
(methodNames.Count == 0 || methodNames.Contains(methodSymbol.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.
i don't understand teh methodNames.Count == 0
check. if we had zero method names, how would we get into the outer loop in the first place. I also don't understand the inner check. We only asked for methods with this speicfic name (from methodNames), so how would methodNames not contain the method 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.
Good catch. It was a leftover from previous logic. Removed!
|
||
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); | ||
var items = GetExtensionMethodItems(assembly.GlobalNamespace, receiverTypeSymbol, | ||
semanticModel!, position, namespaceInScope, matchedMethods, counter, 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.
i don't see how this could work. here's the issue:
- you looked at all dependent projects to add their pe references and get the indices for them.
- you're now walking your particular assembly.GlobalNamespace symbol actually looking for the extension methods.
This would not find any extension methods for pe references that this compilation doesn't itself point at.
So basically, i just don't get how this coudl work.
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.
As discussed offline, transitive PE reference isn't support in C#/VB, so this is simply a mistake and I will remove related code.
...table/Completion/Providers/ImportCompletionProvider/ExtensionMethodImportCompletionHelper.cs
Outdated
Show resolved
Hide resolved
|
||
/// <summary> | ||
/// The purpose of this is to help us keeping track of conflicting types with data required to create corresponding | ||
/// items in a tree, which is easy to use while navigating symbol tree recursively. |
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.
would be good to give an example of what counts as a 'conflict'.
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
/// The purpose of this is to help us keeping track of conflicting types with data required to create corresponding | ||
/// items in a tree, which is easy to use while navigating symbol tree recursively. | ||
/// </summary> | ||
private class ConflictNameNode : IDisposable |
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'm not really sure how conflicts arise. your indices already are associated with source or pe-references. so you can triviall walk the corresponding IAssembly for each to find your matches, and you should never run into a conflict in these specific iassembly symbols.
} | ||
|
||
// Otherwise, this is a type item, so we don't have SymbolKey data. But we should still have all | ||
// the data to construct its full metadata 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.
dumb question. why not just have the TypeItem do the same thing the extension method item does? then you can operate on both consistently.
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.
Because after the cached items are created, type items don't operate on symbols at all (except for getting description), holding on to those SymbolKey data in cache seems wasteful. Whereas, we need symbols to provide extension method items every time.
|
||
namespace Microsoft.CodeAnalysis.Completion.Providers | ||
{ | ||
internal sealed class SerializableImportCompletionItem |
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 these be structs to save on allocations?
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.
Sure. Fixed
return TryGetSimpleTypeName(aliasQualifiedNameNode.Name, typeParameterNames, out simpleTypeName); | ||
|
||
case QualifiedNameSyntax qualifiedNameNode: | ||
// For an identifier to the right of a '.', it can't be a type parameter, |
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 can't happen. :)
src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs
Outdated
Show resolved
Hide resolved
src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs
Show resolved
Hide resolved
} | ||
} | ||
|
||
private static readonly Func<KeyValuePair<string, ArrayBuilder<int>>, string> s_getKey = kvp => kvp.Key; | ||
private static readonly Func<KeyValuePair<string, ArrayBuilder<int>>, ImmutableArray<int>> s_getValuesAsImmutableArray = kvp => kvp.Value.ToImmutableAndFree(); |
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 second one is a bit terrifying.
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 free
part to finally
src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex_Forwarders.cs
Outdated
Show resolved
Hide resolved
/// In VB, we have a member access expression with a null expression, this may be one of the | ||
/// following forms: | ||
/// 1) new With { .a = 1, .b = .a .a refers to the anonymous type | ||
/// 2) With obj : .m .m refers to the obj type |
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.
do you test thsi for extension method import?
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 have made the change so we don't support this scenario, otherwise we'd need to check whether it's on LHS or RHS, would be fairly simple, but given how rare this is, I'd prefer to avoid the extra complexity here.
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.
let me rephrase :) can we have a test to make sure we don't crash here :)
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.
Yes, we have a test for this
src/Workspaces/Core/Portable/Shared/Extensions/DocumentExtensions.cs
Outdated
Show resolved
Hide resolved
overall looking good. however last pass did raise at least 2-3 serious concerns on my part. either things are broken based on my reading, or i'm not understanding something here. def need clarity in those areas. thanks! |
I got verbal approval from @CyrusNajmabadi via gitter discussion. I will try the change suggested here in a separate PR and see if it can benefit perf. |
#35059
TODO:
More tests for VBPerf analysis.Needs to Call into remote process for filtering, since that's where index is built.Implement item descriptionDetermine the bail condition (e.g. if any of the filter items isn't calculated? And/Or a timebox? )now it bails if any of the required filter is not already created. Using expander would force build them.TelemetryThere's an existing bug in SymbolTreeInfo that would cause us to miss extension methods declared in PE references which was compiled from VB source code. #39558
@CyrusNajmabadi Could you please give this a quick look and let me know if the implementation of the filter makes sense? Thanks!