未验证 提交 2eb6c143 编写于 作者: G Gen Lu 提交者: GitHub

Merge pull request #39126 from genlu/extCompletion

Completion for unimported extension methods
......@@ -266,13 +266,6 @@ class Bat
await VerifyTypeImportItemIsAbsentAsync(markup, "Bar", inlineDescription: "Foo");
}
private static string GetMarkupWithReference(string currentFile, string referencedFile, bool isProjectReference, string alias = null)
{
return isProjectReference
? CreateMarkupForProjecWithProjectReference(currentFile, referencedFile, LanguageNames.CSharp, LanguageNames.CSharp)
: CreateMarkupForProjectWithMetadataReference(currentFile, referencedFile, LanguageNames.CSharp, LanguageNames.CSharp);
}
[InlineData(true)]
[InlineData(false)]
[Theory, Trait(Traits.Feature, Traits.Features.Completion)]
......@@ -292,7 +285,7 @@ class Bat
$$
}
}";
var markup = GetMarkupWithReference(file2, file1, isProjectReference);
var markup = GetMarkupWithReference(file2, file1, LanguageNames.CSharp, LanguageNames.CSharp, isProjectReference);
await VerifyTypeImportItemExistsAsync(markup, "Bar", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo");
}
......@@ -316,7 +309,7 @@ class Bat
$$
}
}";
var markup = GetMarkupWithReference(file2, file1, isProjectReference);
var markup = GetMarkupWithReference(file2, file1, LanguageNames.CSharp, LanguageNames.CSharp, isProjectReference);
await VerifyTypeImportItemIsAbsentAsync(markup, "Bar", inlineDescription: "Foo");
}
......@@ -339,7 +332,7 @@ class Bat
$$
}
}";
var markup = GetMarkupWithReference(file2, file1, isProjectReference);
var markup = GetMarkupWithReference(file2, file1, LanguageNames.CSharp, LanguageNames.CSharp, isProjectReference);
await VerifyTypeImportItemIsAbsentAsync(markup, "Bar", inlineDescription: "Foo");
}
......@@ -365,7 +358,7 @@ class Bat
$$
}
}";
var markup = GetMarkupWithReference(file2, file1, isProjectReference);
var markup = GetMarkupWithReference(file2, file1, LanguageNames.CSharp, LanguageNames.CSharp, isProjectReference);
await VerifyTypeImportItemIsAbsentAsync(markup, "Bar", displayTextSuffix: "", inlineDescription: "Foo");
await VerifyTypeImportItemExistsAsync(markup, "Bar", displayTextSuffix: "<>", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo");
}
......@@ -393,7 +386,7 @@ class Bat
$$
}
}";
var markup = GetMarkupWithReference(file2, file1, isProjectReference);
var markup = GetMarkupWithReference(file2, file1, LanguageNames.CSharp, LanguageNames.CSharp, isProjectReference);
await VerifyTypeImportItemIsAbsentAsync(markup, "Bar", displayTextSuffix: "", inlineDescription: "Foo");
await VerifyTypeImportItemIsAbsentAsync(markup, "Bar", displayTextSuffix: "<>", inlineDescription: "Foo");
}
......@@ -422,7 +415,7 @@ class Bat
$$
}
}";
var markup = GetMarkupWithReference(file2, file1, isProjectReference);
var markup = GetMarkupWithReference(file2, file1, LanguageNames.CSharp, LanguageNames.CSharp, isProjectReference);
await VerifyTypeImportItemExistsAsync(markup, "Bar", glyph: (int)Glyph.ClassInternal, inlineDescription: "Foo");
await VerifyTypeImportItemExistsAsync(markup, "Bar", displayTextSuffix: "<>", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo");
}
......@@ -452,7 +445,7 @@ class Bat
$$
}
}";
var markup = GetMarkupWithReference(file2, file1, isProjectReference);
var markup = GetMarkupWithReference(file2, file1, LanguageNames.CSharp, LanguageNames.CSharp, isProjectReference);
await VerifyTypeImportItemIsAbsentAsync(markup, "Bar", inlineDescription: "Foo");
await VerifyTypeImportItemIsAbsentAsync(markup, "Bar", displayTextSuffix: "<>", inlineDescription: "Foo");
}
......@@ -482,7 +475,7 @@ class Bat
$$
}
}";
var markup = GetMarkupWithReference(file2, file1, isProjectReference);
var markup = GetMarkupWithReference(file2, file1, LanguageNames.CSharp, LanguageNames.CSharp, isProjectReference);
await VerifyTypeImportItemExistsAsync(markup, "Bar", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo");
await VerifyTypeImportItemExistsAsync(markup, "Bar", displayTextSuffix: "<>", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo");
}
......@@ -512,7 +505,7 @@ class Bat
$$
}
}";
var markup = GetMarkupWithReference(file2, file1, isProjectReference);
var markup = GetMarkupWithReference(file2, file1, LanguageNames.CSharp, LanguageNames.CSharp, isProjectReference);
await VerifyTypeImportItemIsAbsentAsync(markup, "Bar", inlineDescription: "Foo");
await VerifyTypeImportItemIsAbsentAsync(markup, "Bar", displayTextSuffix: "<>", inlineDescription: "Foo");
}
......@@ -544,7 +537,7 @@ class Bat
$$
}
}";
var markup = GetMarkupWithReference(file2, file1, isProjectReference);
var markup = GetMarkupWithReference(file2, file1, LanguageNames.CSharp, LanguageNames.CSharp, isProjectReference);
await VerifyTypeImportItemExistsAsync(markup, "Bar", glyph: (int)Glyph.ClassInternal, inlineDescription: "Foo");
await VerifyTypeImportItemExistsAsync(markup, "Bar", displayTextSuffix: "<>", glyph: (int)Glyph.ClassInternal, inlineDescription: "Foo");
}
......@@ -570,7 +563,7 @@ class Bat
$$
}
}";
var markup = GetMarkupWithReference(file2, file1, isProjectReference);
var markup = GetMarkupWithReference(file2, file1, LanguageNames.CSharp, LanguageNames.CSharp, isProjectReference);
await VerifyTypeImportItemExistsAsync(markup, "Bar", glyph: (int)Glyph.ClassInternal, inlineDescription: "Foo");
}
......@@ -590,7 +583,7 @@ class Bat
$$
}
}";
var markup = CreateMarkupForProjecWithVBProjectReference(file2, file1, sourceLanguage: LanguageNames.CSharp, rootnamespace: "Foo");
var markup = CreateMarkupForProjecWithVBProjectReference(file2, file1, sourceLanguage: LanguageNames.CSharp, rootNamespace: "Foo");
await VerifyTypeImportItemExistsAsync(markup, "Barr", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo.Bar");
}
......@@ -616,7 +609,7 @@ class Bat
$$
}
}";
var markup = CreateMarkupForProjecWithVBProjectReference(file2, file1, sourceLanguage: LanguageNames.CSharp, rootnamespace: "");
var markup = CreateMarkupForProjecWithVBProjectReference(file2, file1, sourceLanguage: LanguageNames.CSharp, rootNamespace: "");
await VerifyTypeImportItemExistsAsync(markup, "Bar", glyph: (int)Glyph.ClassPublic, inlineDescription: "Na");
await VerifyTypeImportItemExistsAsync(markup, "Foo", glyph: (int)Glyph.ClassPublic, inlineDescription: "Na");
await VerifyTypeImportItemIsAbsentAsync(markup, "Bar", inlineDescription: "na");
......@@ -646,7 +639,7 @@ class Bat
$$
}
}";
var markup = CreateMarkupForProjecWithVBProjectReference(file2, file1, sourceLanguage: LanguageNames.CSharp, rootnamespace: "");
var markup = CreateMarkupForProjecWithVBProjectReference(file2, file1, sourceLanguage: LanguageNames.CSharp, rootNamespace: "");
await VerifyTypeImportItemIsAbsentAsync(markup, "Bar", inlineDescription: "Na");
await VerifyTypeImportItemIsAbsentAsync(markup, "Foo", inlineDescription: "Na");
await VerifyTypeImportItemIsAbsentAsync(markup, "Bar", inlineDescription: "na");
......@@ -669,7 +662,7 @@ class Bat
$$
}
}";
var markup = CreateMarkupForProjecWithVBProjectReference(file2, file1, sourceLanguage: LanguageNames.CSharp, rootnamespace: "Foo");
var markup = CreateMarkupForProjecWithVBProjectReference(file2, file1, sourceLanguage: LanguageNames.CSharp, rootNamespace: "Foo");
await VerifyTypeImportItemIsAbsentAsync(markup, "Barr", inlineDescription: "Foo.Bar");
}
......@@ -690,7 +683,7 @@ class Bat
$$
}
}";
var markup = CreateMarkupForProjecWithVBProjectReference(file2, file1, sourceLanguage: LanguageNames.CSharp, rootnamespace: "Foo");
var markup = CreateMarkupForProjecWithVBProjectReference(file2, file1, sourceLanguage: LanguageNames.CSharp, rootNamespace: "Foo");
await VerifyTypeImportItemIsAbsentAsync(markup, "Barr", inlineDescription: "Foo.Bar");
}
......@@ -724,7 +717,7 @@ class C
$$
}
}";
var markup = GetMarkupWithReference(file2, file1, isProjectReference);
var markup = GetMarkupWithReference(file2, file1, LanguageNames.CSharp, LanguageNames.CSharp, isProjectReference);
await VerifyTypeImportItemExistsAsync(markup, "Bar", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo");
await VerifyTypeImportItemExistsAsync(markup, "Bar", displayTextSuffix: "<>", glyph: (int)Glyph.ClassPublic, inlineDescription: "Foo");
await VerifyTypeImportItemExistsAsync(markup, "Bar", glyph: (int)Glyph.ClassPublic, inlineDescription: "Baz");
......@@ -797,7 +790,7 @@ class Bat
Barr$$
}
}";
var markup = CreateMarkupForProjecWithVBProjectReference(file2, file1, sourceLanguage: LanguageNames.CSharp, rootnamespace: "Foo");
var markup = CreateMarkupForProjecWithVBProjectReference(file2, file1, sourceLanguage: LanguageNames.CSharp, rootNamespace: "Foo");
await VerifyCustomCommitProviderAsync(markup, "Barr", expectedCodeAfterCommit, sourceCodeKind: kind);
}
......@@ -1305,6 +1298,29 @@ public async Task TriggerCompletionInSubsequentSubmission()
Assert.NotEmpty(completionList.Items);
}
[Fact, Trait(Traits.Feature, Traits.Features.Completion)]
public async Task ShouldNotTriggerInsideTrivia()
{
var file1 = $@"
namespace Foo
{{
public class Bar
{{}}
}}";
var file2 = @"
namespace Baz
{
/// <summary>
/// <see cref=""B$$""/>
/// </summary>
class Bat
{
}
}";
var markup = CreateMarkupForSingleProject(file2, file1, LanguageNames.CSharp);
await VerifyTypeImportItemIsAbsentAsync(markup, "Bar", inlineDescription: "Foo");
}
private static void AssertRelativeOrder(List<string> expectedTypesInRelativeOrder, ImmutableArray<CompletionItem> allCompletionItems)
{
var hashset = new HashSet<string>(expectedTypesInRelativeOrder);
......
......@@ -580,6 +580,13 @@ protected virtual void SetWorkspaceOptions(TestWorkspace workspace)
return VerifyItemWithReferenceWorkerAsync(xmlString, expectedItem, expectedSymbols, hideAdvancedMembers);
}
protected static string GetMarkupWithReference(string currentFile, string referencedFile, string sourceLanguage, string referenceLanguage, bool isProjectReference, string alias = null)
{
return isProjectReference
? CreateMarkupForProjecWithProjectReference(currentFile, referencedFile, sourceLanguage, referenceLanguage)
: CreateMarkupForProjectWithMetadataReference(currentFile, referencedFile, sourceLanguage, referenceLanguage);
}
protected static string CreateMarkupForProjectWithMetadataReference(string markup, string metadataReferenceCode, string sourceLanguage, string referencedLanguage)
{
return string.Format(@"
......@@ -588,6 +595,7 @@ protected static string CreateMarkupForProjectWithMetadataReference(string marku
<Document FilePath=""SourceDocument"">{1}</Document>
<MetadataReferenceFromSource Language=""{2}"" CommonReferences=""true"" IncludeXmlDocComments=""true"" DocumentationMode=""Diagnose"">
<Document FilePath=""ReferencedDocument"">{3}</Document>
<MetadataReference>" + typeof(ValueTuple<>).Assembly.Location + @"</MetadataReference>
</MetadataReferenceFromSource>
</Project>
</Workspace>", sourceLanguage, SecurityElement.Escape(markup), referencedLanguage, SecurityElement.Escape(metadataReferenceCode));
......@@ -608,8 +616,10 @@ protected static string CreateMarkupForProjectWithAliasedMetadataReference(strin
<Workspace>
<Project Language=""{0}"" CommonReferences=""true"" AssemblyName=""Project1"">
<Document FilePath=""SourceDocument"">{1}</Document>
<MetadataReference>" + typeof(ValueTuple<>).Assembly.Location + @"</MetadataReference>
<MetadataReferenceFromSource Language=""{2}"" CommonReferences=""true"" Aliases=""{3}"" IncludeXmlDocComments=""true"" DocumentationMode=""Diagnose"">
<Document FilePath=""ReferencedDocument"">{4}</Document>
<MetadataReference>" + typeof(ValueTuple<>).Assembly.Location + @"</MetadataReference>
</MetadataReferenceFromSource>
</Project>
</Workspace>", sourceLanguage, SecurityElement.Escape(markup), referencedLanguage, SecurityElement.Escape(aliases), SecurityElement.Escape(referencedCode));
......@@ -652,7 +662,7 @@ protected static string CreateMarkupForProjecWithProjectReference(string markup,
</Workspace>", sourceLanguage, SecurityElement.Escape(markup), referencedLanguage, SecurityElement.Escape(referencedCode));
}
protected static string CreateMarkupForProjecWithVBProjectReference(string markup, string referencedCode, string sourceLanguage, string rootnamespace = "")
protected static string CreateMarkupForProjecWithVBProjectReference(string markup, string referencedCode, string sourceLanguage, string rootNamespace = "")
{
return string.Format(@"
<Workspace>
......@@ -665,7 +675,7 @@ protected static string CreateMarkupForProjecWithVBProjectReference(string marku
<CompilationOptions RootNamespace=""{4}""/>
</Project>
</Workspace>", sourceLanguage, SecurityElement.Escape(markup), LanguageNames.VisualBasic, SecurityElement.Escape(referencedCode), rootnamespace);
</Workspace>", sourceLanguage, SecurityElement.Escape(markup), LanguageNames.VisualBasic, SecurityElement.Escape(referencedCode), rootNamespace);
}
private Task VerifyItemInSameProjectAsync(string markup, string referencedCode, string expectedItem, int expectedSymbols, string sourceLanguage, bool hideAdvancedMembers)
......
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports Microsoft.CodeAnalysis.Completion
Imports Microsoft.CodeAnalysis.Editor.UnitTests
Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces
Imports Microsoft.CodeAnalysis.VisualBasic.Completion.Providers
Imports Microsoft.VisualStudio.Composition
Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Completion.CompletionProviders
<UseExportProvider>
Public Class ExtensionMethodImportCompletionProviderTests
Inherits AbstractVisualBasicCompletionProviderTests
Public Sub New(workspaceFixture As VisualBasicTestWorkspaceFixture)
MyBase.New(workspaceFixture)
End Sub
Private Property IsExpandedCompletion As Boolean = True
Private Property ShowImportCompletionItemsOptionValue As Boolean = True
' -1 would disable timebox, whereas 0 means always timeout.
Private Property TimeoutInMilliseconds As Integer = -1
Protected Overrides Sub SetWorkspaceOptions(workspace As TestWorkspace)
workspace.Options = workspace.Options _
.WithChangedOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, LanguageNames.VisualBasic, ShowImportCompletionItemsOptionValue) _
.WithChangedOption(CompletionServiceOptions.TimeoutInMillisecondsForImportCompletion, TimeoutInMilliseconds) _
.WithChangedOption(CompletionServiceOptions.IsExpandedCompletion, IsExpandedCompletion)
End Sub
Protected Overrides Function GetExportProvider() As ExportProvider
Return ExportProviderCache.GetOrCreateExportProviderFactory(TestExportProvider.EntireAssemblyCatalogWithCSharpAndVisualBasic.WithPart(GetType(TestExperimentationService))).CreateExportProvider()
End Function
Friend Overrides Function CreateCompletionProvider() As CompletionProvider
Return New ExtensionMethodImportCompletionProvider()
End Function
Public Enum ReferenceType
None
Project
Metadata
End Enum
Public Shared Function ReferenceTypeData() As IEnumerable(Of Object())
Return (New ReferenceType() {ReferenceType.None, ReferenceType.Project, ReferenceType.Metadata}).Select(Function(refType)
Return New Object() {refType}
End Function)
End Function
Private Shared Function GetMarkup(current As String, referenced As String, refType As ReferenceType, Optional currentLanguage As String = LanguageNames.VisualBasic, Optional referencedLanguage As String = LanguageNames.VisualBasic) As String
If refType = ReferenceType.None Then
Return CreateMarkupForSingleProject(current, referenced, currentLanguage)
ElseIf refType = ReferenceType.Project Then
Return GetMarkupWithReference(current, referenced, currentLanguage, referencedLanguage, True)
ElseIf refType = ReferenceType.Metadata Then
Return GetMarkupWithReference(current, referenced, currentLanguage, referencedLanguage, False)
Else
Return Nothing
End If
End Function
<InlineData(ReferenceType.None)>
<InlineData(ReferenceType.Project)>
<Theory, Trait(Traits.Feature, Traits.Features.Completion)>
Public Async Function TestExtensionAttribute(refType As ReferenceType) As Task
' attribute suffix isn't capitalized
Dim file1 = <Text><![CDATA[
Imports System
Imports System.Runtime.CompilerServices
Namespace Foo
Public Module ExtensionModule
<System.Runtime.CompilerServices.Extension()>
Public Sub ExtensionMethod1(aString As String)
Console.WriteLine(aString)
End Sub
<Extension>
Public Sub ExtensionMethod2(aString As String)
Console.WriteLine(aString)
End Sub
<ExtensionAttribute>
Public Sub ExtensionMethod3(aString As String)
Console.WriteLine(aString)
End Sub
<Extension()>
Public Sub ExtensionMethod4(aString As String)
Console.WriteLine(aString)
End Sub
<System.Runtime.CompilerServices.ExtensionAttribute>
Public Sub ExtensionMethod5(aString As String)
Console.WriteLine(aString)
End Sub
<extension()>
Public Sub ExtensionMethod6(aString As String)
Console.WriteLine(aString)
End Sub
Public Sub ExtensionMethod7(aString As String)
Console.WriteLine(aString)
End Sub
End Module
End Namespace]]></Text>.Value
Dim file2 = <Text><![CDATA[
Public Class Bar
Sub Main()
dim x = ""
x.$$
End Sub
End Class]]></Text>.Value
Dim markup = GetMarkup(file2, file1, refType)
Await VerifyItemExistsAsync(markup, "ExtensionMethod1", glyph:=Glyph.ExtensionMethodPublic, inlineDescription:="Foo")
Await VerifyItemExistsAsync(markup, "ExtensionMethod2", glyph:=Glyph.ExtensionMethodPublic, inlineDescription:="Foo")
Await VerifyItemExistsAsync(markup, "ExtensionMethod3", glyph:=Glyph.ExtensionMethodPublic, inlineDescription:="Foo")
Await VerifyItemExistsAsync(markup, "ExtensionMethod4", glyph:=Glyph.ExtensionMethodPublic, inlineDescription:="Foo")
Await VerifyItemExistsAsync(markup, "ExtensionMethod5", glyph:=Glyph.ExtensionMethodPublic, inlineDescription:="Foo")
Await VerifyItemExistsAsync(markup, "ExtensionMethod6", glyph:=Glyph.ExtensionMethodPublic, inlineDescription:="Foo")
Await VerifyItemIsAbsentAsync(markup, "ExtensionMethod7", inlineDescription:="Foo")
End Function
<InlineData(ReferenceType.None)>
<InlineData(ReferenceType.Project)>
<Theory, Trait(Traits.Feature, Traits.Features.Completion)>
Public Async Function TestCaseMismatchInTargetType(refType As ReferenceType) As Task
' attribute suffix isn't capitalized
Dim file1 = <Text><![CDATA[
Imports System
Imports System.Runtime.CompilerServices
Namespace Foo
Public Module ExtensionModule
<Extension>
Public Sub ExtensionMethod1(exp As exception)
End Sub
<Extension>
Public Sub ExtensionMethod2(exp As Exception)
Console.WriteLine(aString)
End Sub
End Module
End Namespace]]></Text>.Value
Dim file2 = <Text><![CDATA[
Imports System
Public Class Bar
Sub M(x as exception)
x.$$
End Sub
End Class]]></Text>.Value
Dim markup = GetMarkup(file2, file1, refType)
Await VerifyItemExistsAsync(markup, "ExtensionMethod1", glyph:=Glyph.ExtensionMethodPublic, inlineDescription:="Foo")
Await VerifyItemExistsAsync(markup, "ExtensionMethod2", glyph:=Glyph.ExtensionMethodPublic, inlineDescription:="Foo")
End Function
<InlineData(ReferenceType.None)>
<InlineData(ReferenceType.Project)>
<Theory, Trait(Traits.Feature, Traits.Features.Completion)>
Public Async Function TestCaseMismatchInNamespaceImport(refType As ReferenceType) As Task
' attribute suffix isn't capitalized
Dim file1 = <Text><![CDATA[
Imports System
Imports System.Runtime.CompilerServices
Namespace foo
Public Module ExtensionModule
<Extension>
Public Sub ExtensionMethod1(exp As exception)
End Sub
<Extension>
Public Sub ExtensionMethod2(exp As Exception)
Console.WriteLine(aString)
End Sub
End Module
End Namespace]]></Text>.Value
Dim file2 = <Text><![CDATA[
Imports System
Imports Foo
Public Class Bar
Sub M(x as exception)
x.$$
End Sub
End Class]]></Text>.Value
Dim markup = GetMarkup(file2, file1, refType)
Await VerifyItemIsAbsentAsync(markup, "ExtensionMethod1", inlineDescription:="Foo")
Await VerifyItemIsAbsentAsync(markup, "ExtensionMethod2", inlineDescription:="Foo")
End Function
<InlineData(ReferenceType.None)>
<InlineData(ReferenceType.Project)>
<Theory, Trait(Traits.Feature, Traits.Features.Completion)>
Public Async Function TestImplicitTarget1(refType As ReferenceType) As Task
Dim file1 = <Text><![CDATA[
Imports System
Imports System.Runtime.CompilerServices
Namespace NS
Public Module Foo
<Extension>
Public Function ExtentionMethod(x As Bar) As Boolean
Return True
End Function
End Module
Public Class Bar
Public X As Boolean
End Class
End Namespace]]></Text>.Value
Dim file2 = <Text><![CDATA[
Imports System
Public Class Baz
Sub M()
Dim x = New Bar() {.$$}
End Sub
End Class]]></Text>.Value
Dim markup = GetMarkup(file2, file1, refType)
Await VerifyItemIsAbsentAsync(markup, "ExtentionMethod", inlineDescription:="NS")
End Function
<InlineData(ReferenceType.None)>
<InlineData(ReferenceType.Project)>
<Theory, Trait(Traits.Feature, Traits.Features.Completion)>
Public Async Function TestImplicitTarget2(refType As ReferenceType) As Task
Dim file1 = <Text><![CDATA[
Imports System
Imports System.Runtime.CompilerServices
Namespace NS
Public Module Foo
<Extension>
Public Function ExtentionMethod(x As Bar) As Boolean
Return True
End Function
End Module
Public Class Bar
Public X As Boolean
End Class
End Namespace]]></Text>.Value
Dim file2 = <Text><![CDATA[
Imports System
Public Class Baz
Sub M()
Dim x = New Bar() {.X = .$$}
End Sub
End Class]]></Text>.Value
Dim markup = GetMarkup(file2, file1, refType)
Await VerifyItemIsAbsentAsync(markup, "ExtentionMethod", inlineDescription:="NS")
End Function
End Class
End Namespace
......@@ -61,7 +61,9 @@ internal class CSharpCompletionService : CommonCompletionService
new TupleNameCompletionProvider(),
new DeclarationNameCompletionProvider(),
new InternalsVisibleToCompletionProvider(),
new PropertySubpatternCompletionProvider());
new PropertySubpatternCompletionProvider(),
new TypeImportCompletionProvider(),
new ExtensionMethodImportCompletionProvider());
var languageServices = workspace.Services.GetLanguageServices(LanguageNames.CSharp);
var languagesProvider = languageServices.GetService<IEmbeddedLanguagesProvider>();
......@@ -71,8 +73,6 @@ internal class CSharpCompletionService : CommonCompletionService
new EmbeddedLanguageCompletionProvider(languagesProvider));
}
defaultCompletionProviders = defaultCompletionProviders.Add(new TypeImportCompletionProvider());
_defaultCompletionProviders = defaultCompletionProviders;
}
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable enable
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Completion.Providers;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers
{
internal sealed class ExtensionMethodImportCompletionProvider : AbstractExtensionMethodImportCompletionProvider
{
protected override string GenericSuffix => "<>";
internal override bool IsInsertionTrigger(SourceText text, int characterPosition, OptionSet options)
=> CompletionUtilities.IsTriggerCharacter(text, characterPosition, options);
protected override ImmutableArray<string> GetImportedNamespaces(
SyntaxNode location,
SemanticModel semanticModel,
CancellationToken cancellationToken)
=> ImportCompletionProviderHelper.GetImportedNamespaces(location, semanticModel, cancellationToken);
protected override Task<SyntaxContext> CreateContextAsync(Document document, int position, CancellationToken cancellationToken)
=> ImportCompletionProviderHelper.CreateContextAsync(document, position, cancellationToken);
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable enable
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers
{
internal static class ImportCompletionProviderHelper
{
public static ImmutableArray<string> GetImportedNamespaces(
SyntaxNode location,
SemanticModel semanticModel,
CancellationToken cancellationToken)
=> semanticModel.GetUsingNamespacesInScope(location)
.SelectAsArray(namespaceSymbol => namespaceSymbol.ToDisplayString(SymbolDisplayFormats.NameFormat));
public static async Task<SyntaxContext> CreateContextAsync(Document document, int position, CancellationToken cancellationToken)
{
// Need regular semantic model because we will use it to get imported namespace symbols. Otherwise we will try to
// reach outside of the span and ended up with "node not within syntax tree" error from the speculative model.
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
return CSharpSyntaxContext.CreateContext(document.Project.Solution.Workspace, semanticModel, position, cancellationToken);
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable enable
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Completion.Providers;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers
{
internal class TypeImportCompletionProvider : AbstractTypeImportCompletionProvider
internal sealed class TypeImportCompletionProvider : AbstractTypeImportCompletionProvider
{
internal override bool IsInsertionTrigger(SourceText text, int characterPosition, OptionSet options)
=> CompletionUtilities.IsTriggerCharacter(text, characterPosition, options);
......@@ -24,25 +21,9 @@ internal override bool IsInsertionTrigger(SourceText text, int characterPosition
SyntaxNode location,
SemanticModel semanticModel,
CancellationToken cancellationToken)
{
// Get namespaces from usings
return semanticModel.GetUsingNamespacesInScope(location)
.SelectAsArray(namespaceSymbol => namespaceSymbol.ToDisplayString(SymbolDisplayFormats.NameFormat));
}
protected override async Task<SyntaxContext> CreateContextAsync(Document document, int position, CancellationToken cancellationToken)
{
// Need regular semantic model because we will use it to get imported namespace symbols. Otherwise we will try to
// reach outside of the span and ended up with "node not within syntax tree" error from the speculative model.
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
return CSharpSyntaxContext.CreateContext(document.Project.Solution.Workspace, semanticModel, position, cancellationToken);
}
=> ImportCompletionProviderHelper.GetImportedNamespaces(location, semanticModel, cancellationToken);
protected override async Task<bool> IsInImportsDirectiveAsync(Document document, int position, CancellationToken cancellationToken)
{
var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
var leftToken = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken, includeDirectives: true);
return leftToken.GetAncestor<UsingDirectiveSyntax>() != null;
}
protected override Task<SyntaxContext> CreateContextAsync(Document document, int position, CancellationToken cancellationToken)
=> ImportCompletionProviderHelper.CreateContextAsync(document, position, cancellationToken);
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable enable
using System.Composition;
using Microsoft.CodeAnalysis.Completion.Providers.ImportCompletion;
using Microsoft.CodeAnalysis.Host;
......@@ -8,10 +10,10 @@
namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers
{
[ExportLanguageServiceFactory(typeof(ITypeImportCompletionService), LanguageNames.CSharp), Shared]
internal sealed class CSharpTypeImportCompletionServiceFactory : ILanguageServiceFactory
internal sealed class TypeImportCompletionServiceFactory : ILanguageServiceFactory
{
[ImportingConstructor]
public CSharpTypeImportCompletionServiceFactory()
public TypeImportCompletionServiceFactory()
{
}
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
......@@ -26,7 +28,7 @@ public CompletionHelper(bool isCaseSensitive)
public static CompletionHelper GetHelper(Document document)
{
return document.Project.Solution.Workspace.Services.GetService<ICompletionHelperService>()
return document.Project.Solution.Workspace.Services.GetRequiredService<ICompletionHelperService>()
.GetCompletionHelper(document);
}
......@@ -309,5 +311,8 @@ private static int CompareExpandedItem(CompletionItem item1, PatternMatch match1
// Non-expanded item is the only exact match, so we definitely prefer it.
return isItem1Expanded ? 1 : -1;
}
public static string ConcatNamespace(string? containingNamespace, string name)
=> string.IsNullOrEmpty(containingNamespace) ? name : containingNamespace + "." + name;
}
}
......@@ -22,7 +22,16 @@ internal enum ActionInfo
TypeImportCompletionReferenceCount,
TypeImportCompletionTimeoutCount,
TargetTypeCompletionTicks
TargetTypeCompletionTicks,
ExtensionMethodCompletionSuccessCount,
// following are only reported when sucessful (i.e. filter is available)
ExtensionMethodCompletionTicks,
ExtensionMethodCompletionMethodsProvided,
ExtensionMethodCompletionGetFilterTicks,
ExtensionMethodCompletionGetSymbolTicks,
ExtensionMethodCompletionTypesChecked,
ExtensionMethodCompletionMethodsChecked,
}
internal static void LogTypeImportCompletionTicksDataPoint(int count) =>
......@@ -40,6 +49,29 @@ internal enum ActionInfo
internal static void LogTargetTypeCompletionTicksDataPoint(int count) =>
s_statisticLogAggregator.AddDataPoint((int)ActionInfo.TargetTypeCompletionTicks, count);
internal static void LogExtensionMethodCompletionSuccess() =>
s_logAggregator.IncreaseCount((int)ActionInfo.ExtensionMethodCompletionSuccessCount);
internal static void LogExtensionMethodCompletionTicksDataPoint(int count) =>
s_statisticLogAggregator.AddDataPoint((int)ActionInfo.ExtensionMethodCompletionTicks, count);
internal static void LogExtensionMethodCompletionMethodsProvidedDataPoint(int count) =>
s_statisticLogAggregator.AddDataPoint((int)ActionInfo.ExtensionMethodCompletionMethodsProvided, count);
internal static void LogExtensionMethodCompletionGetFilterTicksDataPoint(int count) =>
s_statisticLogAggregator.AddDataPoint((int)ActionInfo.ExtensionMethodCompletionGetFilterTicks, count);
internal static void LogExtensionMethodCompletionGetSymbolTicksDataPoint(int count) =>
s_statisticLogAggregator.AddDataPoint((int)ActionInfo.ExtensionMethodCompletionGetSymbolTicks, count);
internal static void LogExtensionMethodCompletionTypesCheckedDataPoint(int count) =>
s_statisticLogAggregator.AddDataPoint((int)ActionInfo.ExtensionMethodCompletionTypesChecked, count);
internal static void LogExtensionMethodCompletionMethodsCheckedDataPoint(int count) =>
s_statisticLogAggregator.AddDataPoint((int)ActionInfo.ExtensionMethodCompletionMethodsChecked, count);
internal static void ReportTelemetry()
{
Logger.Log(FunctionId.Intellisense_CompletionProviders_Data, KeyValueLogMessage.Create(m =>
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable enable
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery;
namespace Microsoft.CodeAnalysis.Completion.Providers
{
internal abstract class AbstractExtensionMethodImportCompletionProvider : AbstractImportCompletionProvider
{
protected abstract string GenericSuffix { get; }
protected override bool ShouldProvideCompletion(Document document, SyntaxContext syntaxContext)
=> syntaxContext.IsRightOfNameSeparator && IsAddingImportsSupported(document);
protected async override Task AddCompletionItemsAsync(
CompletionContext completionContext,
SyntaxContext syntaxContext,
HashSet<string> namespaceInScope,
bool isExpandedCompletion,
CancellationToken cancellationToken)
{
using (Logger.LogBlock(FunctionId.Completion_ExtensionMethodImportCompletionProvider_GetCompletionItemsAsync, cancellationToken))
{
var syntaxFacts = completionContext.Document.GetRequiredLanguageService<ISyntaxFactsService>();
if (TryGetReceiverTypeSymbol(syntaxContext, syntaxFacts, cancellationToken, out var receiverTypeSymbol))
{
var items = await ExtensionMethodImportCompletionHelper.GetUnimportedExtensionMethodsAsync(
completionContext.Document,
completionContext.Position,
receiverTypeSymbol,
namespaceInScope,
forceIndexCreation: isExpandedCompletion,
cancellationToken).ConfigureAwait(false);
completionContext.AddItems(items.Select(i => Convert(i)));
}
else
{
// If we can't get a valid receiver type, then we don't show expander as available.
// We need to set this explicitly here bacause we didn't do the (more expensive) symbol check inside
// `ShouldProvideCompletion` method above, which is intended for quick syntax based check.
completionContext.ExpandItemsAvailable = false;
}
}
}
private static bool TryGetReceiverTypeSymbol(
SyntaxContext syntaxContext,
ISyntaxFactsService syntaxFacts,
CancellationToken cancellationToken,
[NotNullWhen(true)] out ITypeSymbol? receiverTypeSymbol)
{
var parentNode = syntaxContext.TargetToken.Parent;
// Even though implicit access to extension method is allowed, we decide not support it for simplicity
// e.g. we will not provide completion for unimport extension method in this case
// New Bar() {.X = .$$ }
var expressionNode = syntaxFacts.GetLeftSideOfDot(parentNode, allowImplicitTarget: false);
if (expressionNode != null)
{
// Check if we are accessing members of a type, no extension methods are exposed off of types.
if (!(syntaxContext.SemanticModel.GetSymbolInfo(expressionNode, cancellationToken).GetAnySymbol() is ITypeSymbol))
{
// The expression we're calling off of needs to have an actual instance type.
// We try to be more tolerant to errors here so completion would still be available in certain case of partially typed code.
receiverTypeSymbol = syntaxContext.SemanticModel.GetTypeInfo(expressionNode, cancellationToken).Type;
if (receiverTypeSymbol is IErrorTypeSymbol errorTypeSymbol)
{
receiverTypeSymbol = errorTypeSymbol.CandidateSymbols.Select(s => GetSymbolType(s)).FirstOrDefault(s => s != null);
}
return receiverTypeSymbol != null;
}
}
receiverTypeSymbol = null;
return false;
}
private static ITypeSymbol? GetSymbolType(ISymbol symbol)
=> symbol switch
{
ILocalSymbol localSymbol => localSymbol.Type,
IFieldSymbol fieldSymbol => fieldSymbol.Type,
IPropertySymbol propertySymbol => propertySymbol.Type,
IParameterSymbol parameterSymbol => parameterSymbol.Type,
IAliasSymbol aliasSymbol => aliasSymbol.Target as ITypeSymbol,
_ => symbol as ITypeSymbol,
};
private CompletionItem Convert(SerializableImportCompletionItem serializableItem)
=> ImportCompletionItem.Create(
serializableItem.Name,
serializableItem.Arity,
serializableItem.ContainingNamespace,
serializableItem.Glyph,
GenericSuffix,
CompletionItemFlags.Expanded,
serializableItem.SymbolKeyData);
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable enable
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
namespace Microsoft.CodeAnalysis.Completion.Providers.ImportCompletion
{
internal abstract class AbstractImportCompletionCacheServiceFactory<TProjectCacheEntry, TMetadataCacheEntry> : IWorkspaceServiceFactory
{
private readonly ConcurrentDictionary<string, TMetadataCacheEntry> _peItemsCache
= new ConcurrentDictionary<string, TMetadataCacheEntry>();
private readonly ConcurrentDictionary<ProjectId, TProjectCacheEntry> _projectItemsCache
= new ConcurrentDictionary<ProjectId, TProjectCacheEntry>();
public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
{
var workspace = workspaceServices.Workspace;
if (workspace.Kind == WorkspaceKind.Host)
{
var cacheService = workspaceServices.GetService<IWorkspaceCacheService>();
if (cacheService != null)
{
cacheService.CacheFlushRequested += OnCacheFlushRequested;
}
}
return new ImportCompletionCacheService(_peItemsCache, _projectItemsCache);
}
private void OnCacheFlushRequested(object sender, EventArgs e)
{
_peItemsCache.Clear();
_projectItemsCache.Clear();
}
private class ImportCompletionCacheService : IImportCompletionCacheService<TProjectCacheEntry, TMetadataCacheEntry>
{
public IDictionary<string, TMetadataCacheEntry> PEItemsCache { get; }
public IDictionary<ProjectId, TProjectCacheEntry> ProjectItemsCache { get; }
public ImportCompletionCacheService(
ConcurrentDictionary<string, TMetadataCacheEntry> peCache,
ConcurrentDictionary<ProjectId, TProjectCacheEntry> projectCache)
{
PEItemsCache = peCache;
ProjectItemsCache = projectCache;
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable enable
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.AddImports;
using Microsoft.CodeAnalysis.EditAndContinue;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Experiments;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.Completion.Providers
{
internal abstract class AbstractImportCompletionProvider : CommonCompletionProvider
{
protected abstract Task<SyntaxContext> CreateContextAsync(Document document, int position, CancellationToken cancellationToken);
protected abstract ImmutableArray<string> GetImportedNamespaces(SyntaxNode location, SemanticModel semanticModel, CancellationToken cancellationToken);
protected abstract bool ShouldProvideCompletion(Document document, SyntaxContext syntaxContext);
protected abstract Task AddCompletionItemsAsync(CompletionContext completionContext, SyntaxContext syntaxContext, HashSet<string> namespacesInScope, bool isExpandedCompletion, CancellationToken cancellationToken);
internal override bool IsExpandItemProvider => true;
private bool? _isImportCompletionExperimentEnabled = null;
private bool IsExperimentEnabled(Workspace workspace)
{
if (!_isImportCompletionExperimentEnabled.HasValue)
{
var experimentationService = workspace.Services.GetRequiredService<IExperimentationService>();
_isImportCompletionExperimentEnabled = experimentationService.IsExperimentEnabled(WellKnownExperimentNames.TypeImportCompletion);
}
return _isImportCompletionExperimentEnabled == true;
}
public override async Task ProvideCompletionsAsync(CompletionContext completionContext)
{
var cancellationToken = completionContext.CancellationToken;
var document = completionContext.Document;
// We need to check for context before option values, so we can tell completion service that we are in a context to provide expanded items
// even though import completion might be disabled. This would show the expander in completion list which user can then use to explicitly ask for unimported items.
var syntaxContext = await CreateContextAsync(document, completionContext.Position, cancellationToken).ConfigureAwait(false);
if (!ShouldProvideCompletion(document, syntaxContext))
{
return;
}
completionContext.ExpandItemsAvailable = true;
// We will trigger import completion regardless of the option/experiment if extended items is being requested explicitly (via expander in completion list)
var isExpandedCompletion = completionContext.Options.GetOption(CompletionServiceOptions.IsExpandedCompletion);
if (!isExpandedCompletion)
{
var importCompletionOptionValue = completionContext.Options.GetOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, document.Project.Language);
// Don't trigger import completion if the option value is "default" and the experiment is disabled for the user.
if (importCompletionOptionValue == false ||
(importCompletionOptionValue == null && !IsExperimentEnabled(document.Project.Solution.Workspace)))
{
return;
}
}
// Find all namespaces in scope at current cursor location,
// which will be used to filter so the provider only returns out-of-scope types.
var namespacesInScope = GetNamespacesInScope(document, syntaxContext, cancellationToken);
await AddCompletionItemsAsync(completionContext, syntaxContext, namespacesInScope, isExpandedCompletion, cancellationToken).ConfigureAwait(false);
}
private HashSet<string> GetNamespacesInScope(Document document, SyntaxContext syntaxContext, CancellationToken cancellationToken)
{
var semanticModel = syntaxContext.SemanticModel;
var importedNamespaces = GetImportedNamespaces(syntaxContext.LeftToken.Parent, semanticModel, cancellationToken);
// This hashset will be used to match namespace names, so it must have the same case-sensitivity as the source language.
var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>();
var namespacesInScope = new HashSet<string>(importedNamespaces, syntaxFacts.StringComparer);
// Get containing namespaces.
var namespaceSymbol = semanticModel.GetEnclosingNamespace(syntaxContext.Position, cancellationToken);
while (namespaceSymbol != null)
{
namespacesInScope.Add(namespaceSymbol.ToDisplayString(SymbolDisplayFormats.NameFormat));
namespaceSymbol = namespaceSymbol.ContainingNamespace;
}
return namespacesInScope;
}
internal override async Task<CompletionChange> GetChangeAsync(Document document, CompletionItem completionItem, TextSpan completionListSpan, char? commitKey, CancellationToken cancellationToken)
{
var containingNamespace = ImportCompletionItem.GetContainingNamespace(completionItem);
if (await ShouldCompleteWithFullyQualifyTypeName().ConfigureAwait(false))
{
var fullyQualifiedName = $"{containingNamespace}.{completionItem.DisplayText}";
var change = new TextChange(completionListSpan, fullyQualifiedName);
return CompletionChange.Create(change);
}
// Find context node so we can use it to decide where to insert using/imports.
var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
var root = await tree.GetRootAsync(cancellationToken).ConfigureAwait(false);
var addImportContextNode = root.FindToken(completionListSpan.Start, findInsideTrivia: true).Parent;
// Add required using/imports directive.
var addImportService = document.GetRequiredLanguageService<IAddImportsService>();
var optionSet = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);
var placeSystemNamespaceFirst = optionSet.GetOption(GenerationOptions.PlaceSystemNamespaceFirst, document.Project.Language);
var compilation = await document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false);
var importNode = CreateImport(document, containingNamespace);
var rootWithImport = addImportService.AddImport(compilation, root, addImportContextNode, importNode, placeSystemNamespaceFirst, cancellationToken);
var documentWithImport = document.WithSyntaxRoot(rootWithImport);
// This only formats the annotated import we just added, not the entire document.
var formattedDocumentWithImport = await Formatter.FormatAsync(documentWithImport, Formatter.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false);
var builder = ArrayBuilder<TextChange>.GetInstance();
// Get text change for add import
var importChanges = await formattedDocumentWithImport.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false);
builder.AddRange(importChanges);
// Create text change for complete type name.
//
// Note: Don't try to obtain TextChange for completed type name by replacing the text directly,
// then use Document.GetTextChangesAsync on document created from the changed text. This is
// because it will do a diff and return TextChanges with minimum span instead of actual
// replacement span.
//
// For example: If I'm typing "asd", the completion provider could be triggered after "a"
// is typed. Then if I selected type "AsnEncodedData" to commit, by using the approach described
// above, we will get a TextChange of "AsnEncodedDat" with 0 length span, instead of a change of
// the full display text with a span of length 1. This will later mess up span-tracking and end up
// with "AsnEncodedDatasd" in the code.
builder.Add(new TextChange(completionListSpan, completionItem.DisplayText));
// Then get the combined change
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
var newText = text.WithChanges(builder);
return CompletionChange.Create(Utilities.Collapse(newText, builder.ToImmutableAndFree()));
async Task<bool> ShouldCompleteWithFullyQualifyTypeName()
{
if (!IsAddingImportsSupported(document))
{
return true;
}
// We might need to qualify unimported types to use them in an import directive, because they only affect members of the containing
// import container (e.g. namespace/class/etc. declarations).
//
// For example, `List` and `StringBuilder` both need to be fully qualified below:
//
// using CollectionOfStringBuilders = System.Collections.Generic.List<System.Text.StringBuilder>;
//
// However, if we are typing in an C# using directive that is inside a nested import container (i.e. inside a namespace declaration block),
// then we can add an using in the outer import container instead (this is not allowed in VB).
//
// For example:
//
// using System.Collections.Generic;
// using System.Text;
//
// namespace Foo
// {
// using CollectionOfStringBuilders = List<StringBuilder>;
// }
//
// Here we will always choose to qualify the unimported type, just to be consistent and keeps things simple.
return await IsInImportsDirectiveAsync(document, completionListSpan.Start, cancellationToken).ConfigureAwait(false);
}
}
private static async Task<bool> IsInImportsDirectiveAsync(Document document, int position, CancellationToken cancellationToken)
{
var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>();
var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
var leftToken = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken, includeDirectives: true);
return leftToken.GetAncestor(syntaxFacts.IsUsingOrExternOrImport) != null;
}
protected static bool IsAddingImportsSupported(Document document)
{
var workspace = document.Project.Solution.Workspace;
// Certain types of workspace don't support document change, e.g. DebuggerIntellisense
if (!workspace.CanApplyChange(ApplyChangesKind.ChangeDocument))
{
return false;
}
// During an EnC session, adding import is not supported.
var encService = workspace.Services.GetService<IEditAndContinueWorkspaceService>();
if (encService?.IsDebuggingSessionInProgress == true)
{
return false;
}
// Certain documents, e.g. Razor document, don't support adding imports
var documentSupportsFeatureService = workspace.Services.GetRequiredService<IDocumentSupportsFeatureService>();
if (!documentSupportsFeatureService.SupportsRefactorings(document))
{
return false;
}
return true;
}
private static SyntaxNode CreateImport(Document document, string namespaceName)
{
var syntaxGenerator = SyntaxGenerator.GetGenerator(document);
return syntaxGenerator.NamespaceImportDeclaration(namespaceName).WithAdditionalAnnotations(Formatter.Annotation);
}
protected override Task<CompletionDescription> GetDescriptionWorkerAsync(Document document, CompletionItem item, CancellationToken cancellationToken)
=> ImportCompletionItem.GetCompletionDescriptionAsync(document, item, cancellationToken);
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.AddImports;
using Microsoft.CodeAnalysis.Completion.Log;
using Microsoft.CodeAnalysis.Completion.Providers.ImportCompletion;
using Microsoft.CodeAnalysis.EditAndContinue;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Experiments;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.Completion.Providers
{
internal abstract partial class AbstractTypeImportCompletionProvider : CommonCompletionProvider
internal abstract class AbstractTypeImportCompletionProvider : AbstractImportCompletionProvider
{
private bool? _isTypeImportCompletionExperimentEnabled = null;
protected abstract Task<SyntaxContext> CreateContextAsync(Document document, int position, CancellationToken cancellationToken);
protected abstract ImmutableArray<string> GetImportedNamespaces(
SyntaxNode location,
SemanticModel semanticModel,
CancellationToken cancellationToken);
protected abstract Task<bool> IsInImportsDirectiveAsync(Document document, int position, CancellationToken cancellationToken);
internal override bool IsExpandItemProvider => true;
protected override bool ShouldProvideCompletion(Document document, SyntaxContext syntaxContext)
=> syntaxContext.IsTypeContext;
public override async Task ProvideCompletionsAsync(CompletionContext completionContext)
protected override async Task AddCompletionItemsAsync(CompletionContext completionContext, SyntaxContext syntaxContext, HashSet<string> namespacesInScope, bool isExpandedCompletion, CancellationToken cancellationToken)
{
var cancellationToken = completionContext.CancellationToken;
var document = completionContext.Document;
var workspace = document.Project.Solution.Workspace;
// We need to check for context before option values, so we can tell completion service that we are in a context to provide expanded items
// even though import completion might be disabled. This would show the expander in completion list which user can then use to explicitly ask for unimported items.
var syntaxContext = await CreateContextAsync(document, completionContext.Position, cancellationToken).ConfigureAwait(false);
if (!syntaxContext.IsTypeContext)
{
return;
}
completionContext.ExpandItemsAvailable = true;
using var _ = Logger.LogBlock(FunctionId.Completion_TypeImportCompletionProvider_GetCompletionItemsAsync, cancellationToken);
var telemetryCounter = new TelemetryCounter();
// We will trigger import completion regardless of the option/experiment if extended items is being requested explicitly (via expander in completion list)
var isExpandedCompletion = completionContext.Options.GetOption(CompletionServiceOptions.IsExpandedCompletion);
if (!isExpandedCompletion)
{
var importCompletionOptionValue = completionContext.Options.GetOption(CompletionOptions.ShowItemsFromUnimportedNamespaces, document.Project.Language);
// Don't trigger import completion if the option value is "default" and the experiment is disabled for the user.
if (importCompletionOptionValue == false ||
(importCompletionOptionValue == null && !IsTypeImportCompletionExperimentEnabled(workspace)))
{
return;
}
}
using (Logger.LogBlock(FunctionId.Completion_TypeImportCompletionProvider_GetCompletionItemsAsync, cancellationToken))
using (var telemetryCounter = new TelemetryCounter())
{
await AddCompletionItemsAsync(completionContext, syntaxContext, isExpandedCompletion, telemetryCounter, cancellationToken).ConfigureAwait(false);
}
}
private bool IsTypeImportCompletionExperimentEnabled(Workspace workspace)
{
if (!_isTypeImportCompletionExperimentEnabled.HasValue)
{
var experimentationService = workspace.Services.GetService<IExperimentationService>();
_isTypeImportCompletionExperimentEnabled = experimentationService.IsExperimentEnabled(WellKnownExperimentNames.TypeImportCompletion);
}
return _isTypeImportCompletionExperimentEnabled == true;
}
private async Task AddCompletionItemsAsync(CompletionContext completionContext, SyntaxContext syntaxContext, bool isExpandedCompletion, TelemetryCounter telemetryCounter, CancellationToken cancellationToken)
{
var document = completionContext.Document;
var project = document.Project;
var workspace = project.Solution.Workspace;
var typeImportCompletionService = document.GetLanguageService<ITypeImportCompletionService>();
// Find all namespaces in scope at current cursor location,
// which will be used to filter so the provider only returns out-of-scope types.
var namespacesInScope = GetNamespacesInScope(document, syntaxContext, cancellationToken);
var typeImportCompletionService = document.GetLanguageService<ITypeImportCompletionService>()!;
var tasksToGetCompletionItems = ArrayBuilder<Task<ImmutableArray<CompletionItem>>>.GetInstance();
// Get completion items from current project.
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
var compilation = (await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false))!;
tasksToGetCompletionItems.Add(Task.Run(() => typeImportCompletionService.GetTopLevelTypesAsync(
project,
syntaxContext,
......@@ -145,6 +79,7 @@ private async Task AddCompletionItemsAsync(CompletionContext completionContext,
}
telemetryCounter.ReferenceCount = referencedAssemblySymbols.Length;
telemetryCounter.Report();
return;
......@@ -186,7 +121,7 @@ static void AddItems(ImmutableArray<CompletionItem> items, CompletionContext com
{
foreach (var item in items)
{
var containingNamespace = TypeImportCompletionItem.GetContainingNamespace(item);
var containingNamespace = ImportCompletionItem.GetContainingNamespace(item);
if (!namespacesInScope.Contains(containingNamespace))
{
// We can return cached item directly, item's span will be fixed by completion service.
......@@ -200,159 +135,21 @@ static void AddItems(ImmutableArray<CompletionItem> items, CompletionContext com
}
}
private HashSet<string> GetNamespacesInScope(Document document, SyntaxContext syntaxContext, CancellationToken cancellationToken)
private class TelemetryCounter
{
var semanticModel = syntaxContext.SemanticModel;
var importedNamespaces = GetImportedNamespaces(syntaxContext.LeftToken.Parent, semanticModel, cancellationToken);
// This hashset will be used to match namespace names, so it must have the same case-sensitivity as the source language.
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
var namespacesInScope = new HashSet<string>(importedNamespaces, syntaxFacts.StringComparer);
// Get containing namespaces.
var namespaceSymbol = semanticModel.GetEnclosingNamespace(syntaxContext.Position, cancellationToken);
while (namespaceSymbol != null)
{
namespacesInScope.Add(namespaceSymbol.ToDisplayString(SymbolDisplayFormats.NameFormat));
namespaceSymbol = namespaceSymbol.ContainingNamespace;
}
return namespacesInScope;
}
internal override async Task<CompletionChange> GetChangeAsync(Document document, CompletionItem completionItem, TextSpan completionListSpan, char? commitKey, CancellationToken cancellationToken)
{
var containingNamespace = TypeImportCompletionItem.GetContainingNamespace(completionItem);
Debug.Assert(containingNamespace != null);
if (await ShouldCompleteWithFullyQualifyTypeName().ConfigureAwait(false))
{
var fullyQualifiedName = $"{containingNamespace}.{completionItem.DisplayText}";
var change = new TextChange(completionListSpan, fullyQualifiedName);
return CompletionChange.Create(change);
}
else
{
// Find context node so we can use it to decide where to insert using/imports.
var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
var root = await tree.GetRootAsync(cancellationToken).ConfigureAwait(false);
var addImportContextNode = root.FindToken(completionListSpan.Start, findInsideTrivia: true).Parent;
// Add required using/imports directive.
var addImportService = document.GetLanguageService<IAddImportsService>();
var optionSet = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);
var placeSystemNamespaceFirst = optionSet.GetOption(GenerationOptions.PlaceSystemNamespaceFirst, document.Project.Language);
var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
var importNode = CreateImport(document, containingNamespace);
var rootWithImport = addImportService.AddImport(compilation, root, addImportContextNode, importNode, placeSystemNamespaceFirst, cancellationToken);
var documentWithImport = document.WithSyntaxRoot(rootWithImport);
var formattedDocumentWithImport = await Formatter.FormatAsync(documentWithImport, Formatter.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false);
var builder = ArrayBuilder<TextChange>.GetInstance();
// Get text change for add improt
var importChanges = await formattedDocumentWithImport.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false);
builder.AddRange(importChanges);
// Create text change for complete type name.
//
// Note: Don't try to obtain TextChange for completed type name by replacing the text directly,
// then use Document.GetTextChangesAsync on document created from the changed text. This is
// because it will do a diff and return TextChanges with minimum span instead of actual
// replacement span.
//
// For example: If I'm typing "asd", the completion provider could be triggered after "a"
// is typed. Then if I selected type "AsnEncodedData" to commit, by using the approach described
// above, we will get a TextChange of "AsnEncodedDat" with 0 length span, instead of a change of
// the full display text with a span of length 1. This will later mess up span-tracking and end up
// with "AsnEncodedDatasd" in the code.
builder.Add(new TextChange(completionListSpan, completionItem.DisplayText));
// Then get the combined change
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
var newText = text.WithChanges(builder);
return CompletionChange.Create(Utilities.Collapse(newText, builder.ToImmutableAndFree()));
}
async Task<bool> ShouldCompleteWithFullyQualifyTypeName()
{
var workspace = document.Project.Solution.Workspace;
// Certain types of workspace don't support document change, e.g. DebuggerIntellisense
if (!workspace.CanApplyChange(ApplyChangesKind.ChangeDocument))
{
return true;
}
// During an EnC session, adding import is not supported.
var encService = workspace.Services.GetService<IEditAndContinueWorkspaceService>();
if (encService?.IsDebuggingSessionInProgress == true)
{
return true;
}
// Certain documents, e.g. Razor document, don't support adding imports
var documentSupportsFeatureService = workspace.Services.GetService<IDocumentSupportsFeatureService>();
if (!documentSupportsFeatureService.SupportsRefactorings(document))
{
return true;
}
// We might need to qualify unimported types to use them in an import directive, because they only affect members of the containing
// import container (e.g. namespace/class/etc. declarations).
//
// For example, `List` and `StringBuilder` both need to be fully qualified below:
//
// using CollectionOfStringBuilders = System.Collections.Generic.List<System.Text.StringBuilder>;
//
// However, if we are typing in an C# using directive that is inside a nested import container (i.e. inside a namespace declaration block),
// then we can add an using in the outer import container instead (this is not allowed in VB).
//
// For example:
//
// using System.Collections.Generic;
// using System.Text;
//
// namespace Foo
// {
// using CollectionOfStringBuilders = List<StringBuilder>;
// }
//
// Here we will always choose to qualify the unimported type, just to be consistent and keeps things simple.
return await IsInImportsDirectiveAsync(document, completionListSpan.Start, cancellationToken).ConfigureAwait(false);
}
}
private static SyntaxNode CreateImport(Document document, string namespaceName)
{
var syntaxGenerator = SyntaxGenerator.GetGenerator(document);
return syntaxGenerator.NamespaceImportDeclaration(namespaceName).WithAdditionalAnnotations(Formatter.Annotation);
}
protected override Task<CompletionDescription> GetDescriptionWorkerAsync(Document document, CompletionItem item, CancellationToken cancellationToken)
=> TypeImportCompletionItem.GetCompletionDescriptionAsync(document, item, cancellationToken);
private class TelemetryCounter : IDisposable
{
private readonly int _tick;
protected int Tick { get; }
public int ItemsCount { get; set; }
public int ReferenceCount { get; set; }
public bool TimedOut { get; set; }
public TelemetryCounter()
{
_tick = Environment.TickCount;
Tick = Environment.TickCount;
}
public void Dispose()
public void Report()
{
var delta = Environment.TickCount - _tick;
var delta = Environment.TickCount - Tick;
CompletionProvidersLogger.LogTypeImportCompletionTicksDataPoint(delta);
CompletionProvidersLogger.LogTypeImportCompletionItemCountDataPoint(ItemsCount);
CompletionProvidersLogger.LogTypeImportCompletionReferenceCountDataPoint(ReferenceCount);
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable enable
using System;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Completion.Providers.ImportCompletion
{
internal abstract partial class AbstractTypeImportCompletionService
{
private readonly struct CacheEntry
{
public string Language { get; }
public Checksum Checksum { get; }
private ImmutableArray<TypeImportCompletionItemInfo> ItemInfos { get; }
private CacheEntry(
Checksum checksum,
string language,
ImmutableArray<TypeImportCompletionItemInfo> items)
{
Checksum = checksum;
Language = language;
ItemInfos = items;
}
public ImmutableArray<CompletionItem> GetItemsForContext(
string language,
string genericTypeSuffix,
bool isInternalsVisible,
bool isAttributeContext,
bool isCaseSensitive)
{
// We will need to adjust some items if the request is made in:
// 1. attribute context, then we will not show or complete with "Attribute" suffix.
// 2. a project with different langauge than when the cache entry was created,
// then we will change the generic suffix accordingly.
// Otherwise, we can simply return cached items.
var isSameLanguage = Language == language;
if (isSameLanguage && !isAttributeContext)
{
return ItemInfos.Where(info => info.IsPublic || isInternalsVisible).SelectAsArray(info => info.Item);
}
var builder = ArrayBuilder<CompletionItem>.GetInstance();
foreach (var info in ItemInfos)
{
if (info.IsPublic || isInternalsVisible)
{
var item = info.Item;
if (isAttributeContext)
{
if (!info.IsAttribute)
{
continue;
}
item = GetAppropriateAttributeItem(info.Item, isCaseSensitive);
}
if (!isSameLanguage && info.IsGeneric)
{
// We don't want to cache this item.
item = ImportCompletionItem.CreateItemWithGenericDisplaySuffix(item, genericTypeSuffix);
}
builder.Add(item);
}
}
return builder.ToImmutableAndFree();
static CompletionItem GetAppropriateAttributeItem(CompletionItem attributeItem, bool isCaseSensitive)
{
if (attributeItem.DisplayText.TryGetWithoutAttributeSuffix(isCaseSensitive: isCaseSensitive, out var attributeNameWithoutSuffix))
{
// We don't want to cache this item.
return ImportCompletionItem.CreateAttributeItemWithoutSuffix(attributeItem, attributeNameWithoutSuffix);
}
return attributeItem;
}
}
public class Builder : IDisposable
{
private readonly string _language;
private readonly string _genericTypeSuffix;
private readonly Checksum _checksum;
private readonly ArrayBuilder<TypeImportCompletionItemInfo> _itemsBuilder;
public Builder(Checksum checksum, string language, string genericTypeSuffix)
{
_checksum = checksum;
_language = language;
_genericTypeSuffix = genericTypeSuffix;
_itemsBuilder = ArrayBuilder<TypeImportCompletionItemInfo>.GetInstance();
}
public CacheEntry ToReferenceCacheEntry()
{
return new CacheEntry(
_checksum,
_language,
_itemsBuilder.ToImmutable());
}
public void AddItem(INamedTypeSymbol symbol, string containingNamespace, bool isPublic)
{
var isGeneric = symbol.Arity > 0;
// Need to determine if a type is an attribute up front since we want to filter out
// non-attribute types when in attribute context. We can't do this lazily since we don't hold
// on to symbols. However, the cost of calling `IsAttribute` on every top-level type symbols
// is prohibitively high, so we opt for the heuristic that would do the simple textual "Attribute"
// suffix check first, then the more expensive symbolic check. As a result, all unimported
// attribute types that don't have "Attribute" suffix would be filtered out when in attribute context.
var isAttribute = symbol.Name.HasAttributeSuffix(isCaseSensitive: false) && symbol.IsAttribute();
var item = ImportCompletionItem.Create(symbol, containingNamespace, _genericTypeSuffix);
_itemsBuilder.Add(new TypeImportCompletionItemInfo(item, isPublic, isGeneric, isAttribute));
}
public void Dispose()
=> _itemsBuilder.Free();
}
}
[ExportWorkspaceServiceFactory(typeof(IImportCompletionCacheService<CacheEntry, CacheEntry>), ServiceLayer.Editor), Shared]
private sealed class CacheServiceFactory : AbstractImportCompletionCacheServiceFactory<CacheEntry, CacheEntry>
{
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Completion.Providers.ImportCompletion
{
internal abstract partial class AbstractTypeImportCompletionService : ITypeImportCompletionService
{
private ITypeImportCompletionCacheService CacheService { get; }
private IImportCompletionCacheService<CacheEntry, CacheEntry> CacheService { get; }
protected abstract string GenericTypeSuffix { get; }
......@@ -25,7 +24,7 @@ internal abstract partial class AbstractTypeImportCompletionService : ITypeImpor
internal AbstractTypeImportCompletionService(Workspace workspace)
{
CacheService = workspace.Services.GetService<ITypeImportCompletionCacheService>();
CacheService = workspace.Services.GetRequiredService<IImportCompletionCacheService<CacheEntry, CacheEntry>>();
}
public async Task<ImmutableArray<CompletionItem>> GetTopLevelTypesAsync(
......@@ -39,7 +38,7 @@ internal AbstractTypeImportCompletionService(Workspace workspace)
throw new ArgumentException(nameof(project));
}
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false);
// Since we only need top level types from source, therefore we only care if source symbol checksum changes.
var checksum = await SymbolTreeInfo.GetSourceSymbolsChecksumAsync(project, cancellationToken).ConfigureAwait(false);
......@@ -97,7 +96,24 @@ static string GetReferenceKey(PortableExecutableReference reference)
Checksum checksum,
SyntaxContext syntaxContext,
bool isInternalsVisible,
IDictionary<TKey, ReferenceCacheEntry> cache,
IDictionary<TKey, CacheEntry> cache,
CancellationToken cancellationToken)
{
var cacheEntry = GetCacheEntry(key, assembly, checksum, syntaxContext, cache, cancellationToken);
return cacheEntry.GetItemsForContext(
syntaxContext.SemanticModel.Language,
GenericTypeSuffix,
isInternalsVisible,
syntaxContext.IsAttributeNameContext,
IsCaseSensitive);
}
private CacheEntry GetCacheEntry<TKey>(
TKey key,
IAssemblySymbol assembly,
Checksum checksum,
SyntaxContext syntaxContext,
IDictionary<TKey, CacheEntry> cache,
CancellationToken cancellationToken)
{
var language = syntaxContext.SemanticModel.Language;
......@@ -106,23 +122,18 @@ static string GetReferenceKey(PortableExecutableReference reference)
if (!cache.TryGetValue(key, out var cacheEntry) ||
cacheEntry.Checksum != checksum)
{
var builder = new ReferenceCacheEntry.Builder(checksum, language, GenericTypeSuffix);
using var builder = new CacheEntry.Builder(checksum, language, GenericTypeSuffix);
GetCompletionItemsForTopLevelTypeDeclarations(assembly.GlobalNamespace, builder, cancellationToken);
cacheEntry = builder.ToReferenceCacheEntry();
cache[key] = cacheEntry;
}
return cacheEntry.GetItemsForContext(
language,
GenericTypeSuffix,
isInternalsVisible,
syntaxContext.IsAttributeNameContext,
IsCaseSensitive);
return cacheEntry;
}
private static void GetCompletionItemsForTopLevelTypeDeclarations(
INamespaceSymbol rootNamespaceSymbol,
ReferenceCacheEntry.Builder builder,
CacheEntry.Builder builder,
CancellationToken cancellationToken)
{
VisitNamespace(rootNamespaceSymbol, containingNamespace: null, builder, cancellationToken);
......@@ -130,12 +141,12 @@ static string GetReferenceKey(PortableExecutableReference reference)
static void VisitNamespace(
INamespaceSymbol symbol,
string containingNamespace,
ReferenceCacheEntry.Builder builder,
string? containingNamespace,
CacheEntry.Builder builder,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
containingNamespace = ConcatNamespace(containingNamespace, symbol.Name);
containingNamespace = CompletionHelper.ConcatNamespace(containingNamespace, symbol.Name);
foreach (var memberNamespace in symbol.GetNamespaceMembers())
{
......@@ -186,17 +197,6 @@ static string GetReferenceKey(PortableExecutableReference reference)
}
}
private static string ConcatNamespace(string containingNamespace, string name)
{
Debug.Assert(name != null);
if (string.IsNullOrEmpty(containingNamespace))
{
return name;
}
return containingNamespace + "." + name;
}
private readonly struct TypeOverloadInfo
{
public TypeOverloadInfo(INamedTypeSymbol nonGenericOverload, INamedTypeSymbol bestGenericOverload, bool containsPublicGenericOverload)
......@@ -231,121 +231,6 @@ public TypeOverloadInfo Aggregate(INamedTypeSymbol type)
}
}
private readonly struct ReferenceCacheEntry
{
public class Builder
{
private readonly string _language;
private readonly string _genericTypeSuffix;
private readonly Checksum _checksum;
private readonly ArrayBuilder<TypeImportCompletionItemInfo> _itemsBuilder;
public Builder(Checksum checksum, string language, string genericTypeSuffix)
{
_checksum = checksum;
_language = language;
_genericTypeSuffix = genericTypeSuffix;
_itemsBuilder = ArrayBuilder<TypeImportCompletionItemInfo>.GetInstance();
}
public ReferenceCacheEntry ToReferenceCacheEntry()
{
return new ReferenceCacheEntry(
_checksum,
_language,
_itemsBuilder.ToImmutableAndFree());
}
public void AddItem(INamedTypeSymbol symbol, string containingNamespace, bool isPublic)
{
var isGeneric = symbol.Arity > 0;
// Need to determine if a type is an attribute up front since we want to filter out
// non-attribute types when in attribute context. We can't do this lazily since we don't hold
// on to symbols. However, the cost of calling `IsAttribute` on every top-level type symbols
// is prohibitively high, so we opt for the heuristic that would do the simple textual "Attribute"
// suffix check first, then the more expensive symbolic check. As a result, all unimported
// attribute types that don't have "Attribute" suffix would be filtered out when in attribute context.
var isAttribute = symbol.Name.HasAttributeSuffix(isCaseSensitive: false) && symbol.IsAttribute();
var item = TypeImportCompletionItem.Create(symbol, containingNamespace, _genericTypeSuffix);
_itemsBuilder.Add(new TypeImportCompletionItemInfo(item, isPublic, isGeneric, isAttribute));
}
}
private ReferenceCacheEntry(
Checksum checksum,
string language,
ImmutableArray<TypeImportCompletionItemInfo> items)
{
Checksum = checksum;
Language = language;
ItemInfos = items;
}
public string Language { get; }
public Checksum Checksum { get; }
private ImmutableArray<TypeImportCompletionItemInfo> ItemInfos { get; }
public ImmutableArray<CompletionItem> GetItemsForContext(
string language,
string genericTypeSuffix,
bool isInternalsVisible,
bool isAttributeContext,
bool isCaseSensitive)
{
var isSameLanguage = Language == language;
if (isSameLanguage && !isAttributeContext)
{
return ItemInfos.Where(info => info.IsPublic || isInternalsVisible).SelectAsArray(info => info.Item);
}
var builder = ArrayBuilder<CompletionItem>.GetInstance();
foreach (var info in ItemInfos)
{
if (info.IsPublic || isInternalsVisible)
{
var item = info.Item;
if (isAttributeContext)
{
if (!info.IsAttribute)
{
continue;
}
item = GetAppropriateAttributeItem(info.Item, isCaseSensitive);
}
if (!isSameLanguage && info.IsGeneric)
{
// We don't want to cache this item.
item = TypeImportCompletionItem.CreateItemWithGenericDisplaySuffix(item, genericTypeSuffix);
}
builder.Add(item);
}
}
return builder.ToImmutableAndFree();
static CompletionItem GetAppropriateAttributeItem(CompletionItem attributeItem, bool isCaseSensitive)
{
if (attributeItem.DisplayText.TryGetWithoutAttributeSuffix(isCaseSensitive: isCaseSensitive, out var attributeNameWithoutSuffix))
{
// We don't want to cache this item.
return TypeImportCompletionItem.CreateAttributeItemWithoutSuffix(attributeItem, attributeNameWithoutSuffix);
}
return attributeItem;
}
}
}
private readonly struct TypeImportCompletionItemInfo
{
private readonly ItemPropertyKind _properties;
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Composition;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
namespace Microsoft.CodeAnalysis.Completion.Providers.ImportCompletion
{
internal partial class AbstractTypeImportCompletionService
{
private interface ITypeImportCompletionCacheService : IWorkspaceService
{
// PE references are keyed on assembly path.
IDictionary<string, ReferenceCacheEntry> PEItemsCache { get; }
IDictionary<ProjectId, ReferenceCacheEntry> ProjectItemsCache { get; }
}
[ExportWorkspaceServiceFactory(typeof(ITypeImportCompletionCacheService), ServiceLayer.Editor), Shared]
private class TypeImportCompletionCacheServiceFactory : IWorkspaceServiceFactory
{
private readonly ConcurrentDictionary<string, ReferenceCacheEntry> _peItemsCache
= new ConcurrentDictionary<string, ReferenceCacheEntry>();
private readonly ConcurrentDictionary<ProjectId, ReferenceCacheEntry> _projectItemsCache
= new ConcurrentDictionary<ProjectId, ReferenceCacheEntry>();
[ImportingConstructor]
public TypeImportCompletionCacheServiceFactory()
{
}
public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
{
var workspace = workspaceServices.Workspace;
if (workspace.Kind == WorkspaceKind.Host)
{
var cacheService = workspaceServices.GetService<IWorkspaceCacheService>();
if (cacheService != null)
{
cacheService.CacheFlushRequested += OnCacheFlushRequested;
}
}
return new TypeImportCompletionCacheService(_peItemsCache, _projectItemsCache);
}
private void OnCacheFlushRequested(object sender, EventArgs e)
{
_peItemsCache.Clear();
_projectItemsCache.Clear();
}
private class TypeImportCompletionCacheService : ITypeImportCompletionCacheService
{
public IDictionary<string, ReferenceCacheEntry> PEItemsCache { get; }
public IDictionary<ProjectId, ReferenceCacheEntry> ProjectItemsCache { get; }
public TypeImportCompletionCacheService(
ConcurrentDictionary<string, ReferenceCacheEntry> peCache,
ConcurrentDictionary<ProjectId, ReferenceCacheEntry> projectCache)
{
PEItemsCache = peCache;
ProjectItemsCache = projectCache;
}
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Completion.Providers.ImportCompletion;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Completion.Providers
{
internal static partial class ExtensionMethodImportCompletionHelper
{
private readonly struct CacheEntry
{
public Checksum Checksum { get; }
public string Language { get; }
/// <summary>
/// Mapping from the name of target type to extension method symbol infos.
/// </summary>
public readonly MultiDictionary<string, DeclaredSymbolInfo> SimpleExtensionMethodInfo { get; }
public readonly ImmutableArray<DeclaredSymbolInfo> ComplexExtensionMethodInfo { get; }
private CacheEntry(
Checksum checksum,
string language,
MultiDictionary<string, DeclaredSymbolInfo> simpleExtensionMethodInfo,
ImmutableArray<DeclaredSymbolInfo> complexExtensionMethodInfo)
{
Checksum = checksum;
Language = language;
SimpleExtensionMethodInfo = simpleExtensionMethodInfo;
ComplexExtensionMethodInfo = complexExtensionMethodInfo;
}
public class Builder : IDisposable
{
private readonly Checksum _checksum;
private readonly string _language;
private readonly MultiDictionary<string, DeclaredSymbolInfo> _simpleItemBuilder;
private readonly ArrayBuilder<DeclaredSymbolInfo> _complexItemBuilder;
public Builder(Checksum checksum, string langauge, IEqualityComparer<string> comparer)
{
_checksum = checksum;
_language = langauge;
_simpleItemBuilder = new MultiDictionary<string, DeclaredSymbolInfo>(comparer);
_complexItemBuilder = ArrayBuilder<DeclaredSymbolInfo>.GetInstance();
}
public CacheEntry ToCacheEntry()
{
return new CacheEntry(
_checksum,
_language,
_simpleItemBuilder,
_complexItemBuilder.ToImmutable());
}
public void AddItem(SyntaxTreeIndex syntaxIndex)
{
foreach (var (targetType, symbolInfoIndices) in syntaxIndex.SimpleExtensionMethodInfo)
{
foreach (var index in symbolInfoIndices)
{
_simpleItemBuilder.Add(targetType, syntaxIndex.DeclaredSymbolInfos[index]);
}
}
foreach (var index in syntaxIndex.ComplexExtensionMethodInfo)
{
_complexItemBuilder.Add(syntaxIndex.DeclaredSymbolInfos[index]);
}
}
public void Dispose()
=> _complexItemBuilder.Free();
}
}
/// <summary>
/// We don't use PE cache from the service, so just pass in type `object` for PE entries.
/// </summary>
[ExportWorkspaceServiceFactory(typeof(IImportCompletionCacheService<CacheEntry, object>), ServiceLayer.Editor), Shared]
private sealed class CacheServiceFactory : AbstractImportCompletionCacheServiceFactory<CacheEntry, object>
{
}
private static IImportCompletionCacheService<CacheEntry, object> GetCacheService(Workspace workspace)
=> workspace.Services.GetRequiredService<IImportCompletionCacheService<CacheEntry, object>>();
private static async Task<CacheEntry?> GetCacheEntryAsync(
Project project,
bool loadOnly,
IImportCompletionCacheService<CacheEntry, object> cacheService,
CancellationToken cancellationToken)
{
// While we are caching data from SyntaxTreeInfo, all the things we cared about here are actually based on sources symbols.
// So using source symbol checksum would suffice.
var checksum = await SymbolTreeInfo.GetSourceSymbolsChecksumAsync(project, cancellationToken).ConfigureAwait(false);
// Cache miss, create all requested items.
if (!cacheService.ProjectItemsCache.TryGetValue(project.Id, out var cacheEntry) ||
cacheEntry.Checksum != checksum ||
cacheEntry.Language != project.Language)
{
var syntaxFacts = project.LanguageServices.GetRequiredService<ISyntaxFactsService>();
using var builder = new CacheEntry.Builder(checksum, project.Language, syntaxFacts.StringComparer);
foreach (var document in project.Documents)
{
// Don't look for extension methods in generated code.
if (document.State.Attributes.IsGenerated)
{
continue;
}
var info = await document.GetSyntaxTreeIndexAsync(loadOnly, cancellationToken).ConfigureAwait(false);
if (info == null)
{
return null;
}
if (info.ContainsExtensionMethod)
{
builder.AddItem(info);
}
}
cacheEntry = builder.ToCacheEntry();
cacheService.ProjectItemsCache[project.Id] = cacheEntry;
}
return cacheEntry;
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable enable
using System.Collections.Generic;
using Microsoft.CodeAnalysis.Host;
namespace Microsoft.CodeAnalysis.Completion.Providers.ImportCompletion
{
internal interface IImportCompletionCacheService<TProject, TPortableExecutable> : IWorkspaceService
{
// PE references are keyed on assembly path.
IDictionary<string, TPortableExecutable> PEItemsCache { get; }
IDictionary<ProjectId, TProject> ProjectItemsCache { get; }
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable enable
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.CodeAnalysis.Completion.Providers
{
internal interface IRemoteExtensionMethodImportCompletionService
{
Task<(IList<SerializableImportCompletionItem>, StatisticCounter)> GetUnimportedExtensionMethodsAsync(
DocumentId documentId,
int position,
string receiverTypeSymbolKeyData,
string[] namespaceInScope,
bool forceIndexCreation,
CancellationToken cancellationToken);
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable enable
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable enable
using System.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
namespace Microsoft.CodeAnalysis.Completion.Providers
{
internal static class TypeImportCompletionItem
internal static class ImportCompletionItem
{
private const string SortTextFormat = "~{0} {1}";
private const string GenericTypeNameManglingString = "`";
private static readonly string[] s_aritySuffixesOneToNine = { "`1", "`2", "`3", "`4", "`5", "`6", "`7", "`8", "`9" };
private const string TypeAritySuffixName = nameof(TypeAritySuffixName);
private const string AttributeFullName = nameof(AttributeFullName);
private const string SymbolKeyData = nameof(SymbolKeyData);
public static CompletionItem Create(INamedTypeSymbol typeSymbol, string containingNamespace, string genericTypeSuffix)
=> Create(typeSymbol.Name, typeSymbol.Arity, containingNamespace, typeSymbol.GetGlyph(), genericTypeSuffix, CompletionItemFlags.CachedAndExpanded, symbolKeyData: null);
public static CompletionItem Create(string name, int arity, string containingNamespace, Glyph glyph, string genericTypeSuffix, CompletionItemFlags flags, string? symbolKeyData)
{
PooledDictionary<string, string> propertyBuilder = null;
ImmutableDictionary<string, string>? properties = null;
if (typeSymbol.Arity > 0)
if (symbolKeyData != null || arity > 0)
{
propertyBuilder = PooledDictionary<string, string>.GetInstance();
propertyBuilder.Add(TypeAritySuffixName, GetAritySuffix(typeSymbol.Arity));
var builder = PooledDictionary<string, string>.GetInstance();
if (symbolKeyData != null)
{
builder.Add(SymbolKeyData, symbolKeyData);
}
else
{
// We don't need arity to recover symbol if we already have SymbolKeyData or it's 0.
// (but it still needed below to decide whether to show generic suffix)
builder.Add(TypeAritySuffixName, AbstractDeclaredSymbolInfoFactoryService.GetMetadataAritySuffix(arity));
}
properties = builder.ToImmutableDictionaryAndFree();
}
// Add tildes (ASCII: 126) to name and namespace as sort text:
......@@ -33,19 +50,19 @@ public static CompletionItem Create(INamedTypeSymbol typeSymbol, string containi
// 2. ' ' before namespace makes types with identical type name but from different namespace all show up in the list,
// it also makes sure type with shorter name shows first, e.g. 'SomeType` before 'SomeTypeWithLongerName'.
var sortTextBuilder = PooledStringBuilder.GetInstance();
sortTextBuilder.Builder.AppendFormat(SortTextFormat, typeSymbol.Name, containingNamespace);
sortTextBuilder.Builder.AppendFormat(SortTextFormat, name, containingNamespace);
var item = CompletionItem.Create(
displayText: typeSymbol.Name,
displayText: name,
sortText: sortTextBuilder.ToStringAndFree(),
properties: propertyBuilder?.ToImmutableDictionaryAndFree(),
tags: GlyphTags.GetTags(typeSymbol.GetGlyph()),
properties: properties,
tags: GlyphTags.GetTags(glyph),
rules: CompletionItemRules.Default,
displayTextPrefix: null,
displayTextSuffix: typeSymbol.Arity == 0 ? string.Empty : genericTypeSuffix,
displayTextSuffix: arity == 0 ? string.Empty : genericTypeSuffix,
inlineDescription: containingNamespace);
item.Flags = CompletionItemFlags.CachedAndExpanded;
item.Flags = flags;
return item;
}
......@@ -71,63 +88,58 @@ public static CompletionItem CreateAttributeItemWithoutSuffix(CompletionItem att
}
public static CompletionItem CreateItemWithGenericDisplaySuffix(CompletionItem item, string genericTypeSuffix)
{
return item.WithDisplayTextSuffix(genericTypeSuffix);
}
=> item.WithDisplayTextSuffix(genericTypeSuffix);
public static string GetContainingNamespace(CompletionItem item)
=> item.InlineDescription;
public static async Task<CompletionDescription> GetCompletionDescriptionAsync(Document document, CompletionItem item, CancellationToken cancellationToken)
{
var metadataName = GetMetadataName(item);
if (!string.IsNullOrEmpty(metadataName))
var compilation = (await document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false));
var symbol = GetSymbol(item, compilation);
if (symbol != null)
{
var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
var symbol = compilation.GetTypeByMetadataName(metadataName);
if (symbol != null)
{
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
// We choose not to display the number of "type overloads" for simplicity.
// Otherwise, we need additional logic to track internal and public visible
// types separately, and cache both completion items.
return await CommonCompletionUtilities.CreateDescriptionAsync(
document.Project.Solution.Workspace,
semanticModel,
position: 0,
symbol,
overloadCount: 0,
supportedPlatforms: null,
cancellationToken).ConfigureAwait(false);
}
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
// We choose not to display the number of "type overloads" for simplicity.
// Otherwise, we need additional logic to track internal and public visible
// types separately, and cache both completion items.
return await CommonCompletionUtilities.CreateDescriptionAsync(
document.Project.Solution.Workspace,
semanticModel,
position: 0,
symbol,
overloadCount: 0,
supportedPlatforms: null,
cancellationToken).ConfigureAwait(false);
}
return CompletionDescription.Empty;
}
private static string GetAritySuffix(int arity)
{
Debug.Assert(arity > 0);
return (arity <= s_aritySuffixesOneToNine.Length)
? s_aritySuffixesOneToNine[arity - 1]
: string.Concat(GenericTypeNameManglingString, arity.ToString(CultureInfo.InvariantCulture));
}
private static string GetFullyQualifiedName(string namespaceName, string typeName)
=> namespaceName.Length == 0 ? typeName : namespaceName + "." + typeName;
private static string GetMetadataName(CompletionItem item)
private static ISymbol? GetSymbol(CompletionItem item, Compilation compilation)
{
// If we have SymbolKey data (i.e. this is an extension method item), use it to recover symbol
if (item.Properties.TryGetValue(SymbolKeyData, out var symbolId))
{
return SymbolKey.ResolveString(symbolId, compilation).GetAnySymbol();
}
// 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
var containingNamespace = GetContainingNamespace(item);
var typeName = item.Properties.TryGetValue(AttributeFullName, out var attributeFullName) ? attributeFullName : item.DisplayText;
var fullyQualifiedName = GetFullyQualifiedName(containingNamespace, typeName);
if (item.Properties.TryGetValue(TypeAritySuffixName, out var aritySuffix))
{
return fullyQualifiedName + aritySuffix;
return compilation.GetTypeByMetadataName(fullyQualifiedName + aritySuffix);
}
return fullyQualifiedName;
return compilation.GetTypeByMetadataName(fullyQualifiedName);
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable enable
namespace Microsoft.CodeAnalysis.Completion.Providers
{
internal readonly struct SerializableImportCompletionItem
{
public readonly string SymbolKeyData;
public readonly int Arity;
public readonly string Name;
public readonly Glyph Glyph;
public readonly string ContainingNamespace;
public SerializableImportCompletionItem(string symbolKeyData, string name, int arity, Glyph glyph, string containingNamespace)
{
SymbolKeyData = symbolKeyData;
Arity = arity;
Name = name;
Glyph = glyph;
ContainingNamespace = containingNamespace;
}
}
}
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports System.Collections.Immutable
Imports System.Threading
Imports Microsoft.CodeAnalysis.Completion.Providers
Imports Microsoft.CodeAnalysis.Options
Imports Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery
Imports Microsoft.CodeAnalysis.Text
Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers
Friend NotInheritable Class ExtensionMethodImportCompletionProvider
Inherits AbstractExtensionMethodImportCompletionProvider
Protected Overrides ReadOnly Property GenericSuffix As String
Get
Return "(Of ...)"
End Get
End Property
Friend Overrides Function IsInsertionTrigger(text As SourceText, characterPosition As Integer, options As OptionSet) As Boolean
Return CompletionUtilities.IsDefaultTriggerCharacterOrParen(text, characterPosition, options)
End Function
Protected Overrides Function CreateContextAsync(document As Document, position As Integer, cancellationToken As CancellationToken) As Task(Of SyntaxContext)
Return ImportCompletionProviderHelper.CreateContextAsync(document, position, cancellationToken)
End Function
Protected Overrides Function GetImportedNamespaces(location As SyntaxNode, semanticModel As SemanticModel, cancellationToken As CancellationToken) As ImmutableArray(Of String)
Return ImportCompletionProviderHelper.GetImportedNamespaces(location, semanticModel, cancellationToken)
End Function
End Class
End Namespace
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports System.Collections.Immutable
Imports System.Threading
Imports Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery
Imports Microsoft.CodeAnalysis.VisualBasic.Extensions.ContextQuery
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers
Friend NotInheritable Class ImportCompletionProviderHelper
Public Shared Function GetImportedNamespaces(location As SyntaxNode, semanticModel As SemanticModel, cancellationToken As CancellationToken) As ImmutableArray(Of String)
Dim builder = ArrayBuilder(Of String).GetInstance()
' Get namespaces from import directives
Dim importsInScope = semanticModel.GetImportNamespacesInScope(location)
For Each import As INamespaceSymbol In importsInScope
builder.Add(import.ToDisplayString(SymbolDisplayFormats.NameFormat))
Next
' Get global imports from compilation option
Dim vbOptions = DirectCast(semanticModel.Compilation.Options, VisualBasicCompilationOptions)
For Each globalImport As GlobalImport In vbOptions.GlobalImports
builder.Add(globalImport.Name)
Next
Return builder.ToImmutableAndFree()
End Function
Public Shared Async Function CreateContextAsync(document As Document, position As Integer, cancellationToken As CancellationToken) As Task(Of SyntaxContext)
' Need regular semantic model because we will use it to get imported namespace symbols. Otherwise we will try to
' reach outside of the span And ended up with "node not within syntax tree" error from the speculative model.
Dim semanticModel = Await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(False)
Return Await VisualBasicSyntaxContext.CreateContextAsync(document.Project.Solution.Workspace, semanticModel, position, cancellationToken).ConfigureAwait(False)
End Function
End Class
End Namespace
......@@ -4,10 +4,8 @@ Imports System.Collections.Immutable
Imports System.Threading
Imports Microsoft.CodeAnalysis.Completion.Providers
Imports Microsoft.CodeAnalysis.Options
Imports Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic.Extensions.ContextQuery
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers
......@@ -19,35 +17,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers
Return CompletionUtilities.IsDefaultTriggerCharacterOrParen(text, characterPosition, options)
End Function
Protected Overrides Async Function CreateContextAsync(document As Document, position As Integer, cancellationToken As CancellationToken) As Task(Of SyntaxContext)
' Need regular semantic model because we will use it to get imported namespace symbols. Otherwise we will try to
' reach outside of the span And ended up with "node not within syntax tree" error from the speculative model.
Dim semanticModel = Await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(False)
Return Await VisualBasicSyntaxContext.CreateContextAsync(document.Project.Solution.Workspace, semanticModel, position, cancellationToken).ConfigureAwait(False)
Protected Overrides Function CreateContextAsync(document As Document, position As Integer, cancellationToken As CancellationToken) As Task(Of SyntaxContext)
Return ImportCompletionProviderHelper.CreateContextAsync(document, position, cancellationToken)
End Function
Protected Overrides Function GetImportedNamespaces(location As SyntaxNode, semanticModel As SemanticModel, cancellationToken As CancellationToken) As ImmutableArray(Of String)
Dim builder = ArrayBuilder(Of String).GetInstance()
' Get namespaces from import directives
Dim importsInScope = semanticModel.GetImportNamespacesInScope(location)
For Each import As INamespaceSymbol In importsInScope
builder.Add(import.ToDisplayString(SymbolDisplayFormats.NameFormat))
Next
' Get global imports from compilation option
Dim vbOptions = DirectCast(semanticModel.Compilation.Options, VisualBasicCompilationOptions)
For Each globalImport As GlobalImport In vbOptions.GlobalImports
builder.Add(globalImport.Name)
Next
Return builder.ToImmutableAndFree()
End Function
Protected Overrides Async Function IsInImportsDirectiveAsync(document As Document, position As Integer, cancellationToken As CancellationToken) As Task(Of Boolean)
Dim syntaxTree = Await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(False)
Dim leftToken = SyntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken, includeDirectives:=True, includeDocumentationComments:=True)
Return leftToken.GetAncestor(Of ImportsStatementSyntax)() IsNot Nothing
Return ImportCompletionProviderHelper.GetImportedNamespaces(location, semanticModel, cancellationToken)
End Function
End Class
End Namespace
......@@ -7,7 +7,7 @@ Imports Microsoft.CodeAnalysis.Host.Mef
Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers
<ExportLanguageServiceFactory(GetType(ITypeImportCompletionService), LanguageNames.VisualBasic), [Shared]>
Friend NotInheritable Class BasicTypeImportCompletionServiceFactory
Friend NotInheritable Class TypeImportCompletionServiceFactory
Implements ILanguageServiceFactory
<ImportingConstructor>
......
......@@ -63,6 +63,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion
End If
completionProviders = completionProviders.Add(New TypeImportCompletionProvider())
completionProviders = completionProviders.Add(New ExtensionMethodImportCompletionProvider())
_completionProviders = completionProviders
End Sub
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.FindSymbols;
......@@ -140,7 +140,7 @@ private void ProcessUsings(List<Dictionary<string, string>> aliasMaps, SyntaxLis
}
}
public override bool TryGetDeclaredSymbolInfo(StringTable stringTable, SyntaxNode node, out DeclaredSymbolInfo declaredSymbolInfo)
public override bool TryGetDeclaredSymbolInfo(StringTable stringTable, SyntaxNode node, string rootNamespace, out DeclaredSymbolInfo declaredSymbolInfo)
{
switch (node.Kind())
{
......@@ -492,5 +492,100 @@ private static string GetSimpleTypeName(SimpleNameSyntax name)
private bool IsExtensionMethod(MethodDeclarationSyntax method)
=> method.ParameterList.Parameters.Count > 0 &&
method.ParameterList.Parameters[0].Modifiers.Any(SyntaxKind.ThisKeyword);
// Root namespace is a VB only concept, which basically means root namespace is always global in C#.
public override string GetRootNamespace(CompilationOptions compilationOptions)
=> string.Empty;
public override bool TryGetAliasesFromUsingDirective(SyntaxNode node, out ImmutableArray<(string aliasName, string name)> aliases)
{
if (node is UsingDirectiveSyntax usingDirectiveNode && usingDirectiveNode.Alias != null)
{
if (TryGetSimpleTypeName(usingDirectiveNode.Alias.Name, typeParameterNames: null, out var aliasName) &&
TryGetSimpleTypeName(usingDirectiveNode.Name, typeParameterNames: null, out var name))
{
aliases = ImmutableArray.Create<(string, string)>((aliasName, name));
return true;
}
}
aliases = default;
return false;
}
public override string GetTargetTypeName(SyntaxNode node)
{
var methodDeclaration = (MethodDeclarationSyntax)node;
Debug.Assert(IsExtensionMethod(methodDeclaration));
var typeParameterNames = methodDeclaration.TypeParameterList?.Parameters.SelectAsArray(p => p.Identifier.Text);
TryGetSimpleTypeName(methodDeclaration.ParameterList.Parameters[0].Type, typeParameterNames, out var targetTypeName);
return targetTypeName;
}
private static bool TryGetSimpleTypeName(SyntaxNode node, ImmutableArray<string>? typeParameterNames, out string simpleTypeName)
{
if (node is TypeSyntax typeNode)
{
switch (typeNode)
{
case IdentifierNameSyntax identifierNameNode:
// We consider it a complex method if the receiver type is a type parameter.
var text = identifierNameNode.Identifier.Text;
simpleTypeName = typeParameterNames?.Contains(text) == true ? null : text;
return simpleTypeName != null;
case GenericNameSyntax genericNameNode:
var name = genericNameNode.Identifier.Text;
var arity = genericNameNode.Arity;
simpleTypeName = arity == 0 ? name : name + GetMetadataAritySuffix(arity);
return true;
case PredefinedTypeSyntax predefinedTypeNode:
simpleTypeName = GetSpecialTypeName(predefinedTypeNode);
return simpleTypeName != null;
case AliasQualifiedNameSyntax aliasQualifiedNameNode:
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,
// so we don't need to check for it further.
return TryGetSimpleTypeName(qualifiedNameNode.Right, typeParameterNames: null, out simpleTypeName);
case NullableTypeSyntax nullableNode:
// Ignore nullability, becase nullable reference type might not be enabled universally.
// In the worst case we just include more methods to check in out filter.
return TryGetSimpleTypeName(nullableNode.ElementType, typeParameterNames, out simpleTypeName);
}
}
simpleTypeName = null;
return false;
}
private static string GetSpecialTypeName(PredefinedTypeSyntax predefinedTypeNode)
{
var kind = predefinedTypeNode.Keyword.Kind();
return kind switch
{
SyntaxKind.BoolKeyword => "Boolean",
SyntaxKind.ByteKeyword => "Byte",
SyntaxKind.SByteKeyword => "SByte",
SyntaxKind.ShortKeyword => "Int16",
SyntaxKind.UShortKeyword => "UInt16",
SyntaxKind.IntKeyword => "Int32",
SyntaxKind.UIntKeyword => "UInt32",
SyntaxKind.LongKeyword => "Int64",
SyntaxKind.ULongKeyword => "UInt64",
SyntaxKind.DoubleKeyword => "Double",
SyntaxKind.FloatKeyword => "Single",
SyntaxKind.DecimalKeyword => "Decimal",
SyntaxKind.StringKeyword => "String",
SyntaxKind.CharKeyword => "Char",
SyntaxKind.ObjectKeyword => "Object",
_ => null,
};
}
}
}
......@@ -200,6 +200,9 @@ public bool IsUsingDirectiveName(SyntaxNode node)
((UsingDirectiveSyntax)node.Parent).Name == node;
}
public bool IsUsingAliasDirective(SyntaxNode node)
=> node is UsingDirectiveSyntax usingDirectiveNode && usingDirectiveNode.Alias != null;
public bool IsForEachStatement(SyntaxNode node)
=> node is ForEachStatementSyntax;
......@@ -1264,6 +1267,12 @@ public SyntaxNode GetRightSideOfDot(SyntaxNode node)
(node as MemberAccessExpressionSyntax)?.Name;
}
public SyntaxNode GetLeftSideOfDot(SyntaxNode node, bool allowImplicitTarget)
{
return (node as QualifiedNameSyntax)?.Left ??
(node as MemberAccessExpressionSyntax)?.Expression;
}
public bool IsLeftSideOfExplicitInterfaceSpecifier(SyntaxNode node)
=> (node as NameSyntax).IsLeftSideOfExplicitInterfaceSpecifier();
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using System.Diagnostics;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.FindSymbols
{
......@@ -19,15 +23,17 @@ internal partial class SymbolTreeInfo
[DebuggerDisplay("{GetDebuggerDisplay(),nq}")]
private struct BuilderNode
{
public static readonly BuilderNode RootNode = new BuilderNode("", RootNodeParentIndex);
public static readonly BuilderNode RootNode = new BuilderNode("", RootNodeParentIndex, default);
public readonly string Name;
public readonly int ParentIndex;
public readonly MultiDictionary<MetadataNode, ParameterTypeInfo>.ValueSet ParameterTypeInfos;
public BuilderNode(string name, int parentIndex)
public BuilderNode(string name, int parentIndex, MultiDictionary<MetadataNode, ParameterTypeInfo>.ValueSet parameterTypeInfos = default)
{
Name = name;
ParentIndex = parentIndex;
ParameterTypeInfos = parameterTypeInfos;
}
public bool IsRoot => ParentIndex == RootNodeParentIndex;
......@@ -72,5 +78,107 @@ private string GetDebuggerDisplay()
return NameSpan + ", " + ParentIndex;
}
}
private readonly struct ParameterTypeInfo
{
/// <summary>
/// This is the type name of the parameter when <see cref="IsComplexType"/> is false.
/// </summary>
public readonly string Name;
/// <summary>
/// Similar to <see cref="SyntaxTreeIndex.ExtensionMethodInfo"/>, we divide extension methods into simple
/// and complex 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.
///
/// Simple types include:
/// - Primitive types
/// - Types which is not a generic method parameter
/// - By reference type of any types above
/// </summary>
public readonly bool IsComplexType;
public ParameterTypeInfo(string name, bool isComplex)
{
Name = name;
IsComplexType = isComplex;
}
}
public readonly struct ExtensionMethodInfo
{
/// <summary>
/// Name of the extension method.
/// This can be used to retrive corresponding symbols via <see cref="INamespaceOrTypeSymbol.GetMembers(string)"/>
/// </summary>
public readonly string Name;
/// <summary>
/// Fully qualified name for the type that contains this extension method.
/// </summary>
public readonly string FullyQualifiedContainerName;
public ExtensionMethodInfo(string fullyQualifiedContainerName, string name)
{
FullyQualifiedContainerName = fullyQualifiedContainerName;
Name = name;
}
}
private sealed class ParameterTypeInfoProvider : ISignatureTypeProvider<ParameterTypeInfo, object>
{
public static readonly ParameterTypeInfoProvider Instance = new ParameterTypeInfoProvider();
private static ParameterTypeInfo ComplexInfo
=> new ParameterTypeInfo(string.Empty, isComplex: true);
public ParameterTypeInfo GetPrimitiveType(PrimitiveTypeCode typeCode)
=> new ParameterTypeInfo(typeCode.ToString(), isComplex: false);
public ParameterTypeInfo GetGenericInstantiation(ParameterTypeInfo genericType, ImmutableArray<ParameterTypeInfo> typeArguments)
=> genericType.IsComplexType
? ComplexInfo
: new ParameterTypeInfo(genericType.Name, isComplex: false);
public ParameterTypeInfo GetByReferenceType(ParameterTypeInfo elementType)
=> elementType;
public ParameterTypeInfo GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind)
{
var type = reader.GetTypeDefinition(handle);
var name = reader.GetString(type.Name);
return new ParameterTypeInfo(name, isComplex: false);
}
public ParameterTypeInfo GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind)
{
var type = reader.GetTypeReference(handle);
var name = reader.GetString(type.Name);
return new ParameterTypeInfo(name, isComplex: false);
}
public ParameterTypeInfo GetTypeFromSpecification(MetadataReader reader, object genericContext, TypeSpecificationHandle handle, byte rawTypeKind)
{
var sigReader = reader.GetBlobReader(reader.GetTypeSpecification(handle).Signature);
return new SignatureDecoder<ParameterTypeInfo, object>(Instance, reader, genericContext).DecodeType(ref sigReader);
}
public ParameterTypeInfo GetArrayType(ParameterTypeInfo elementType, ArrayShape shape) => ComplexInfo;
public ParameterTypeInfo GetSZArrayType(ParameterTypeInfo elementType) => ComplexInfo;
public ParameterTypeInfo GetFunctionPointerType(MethodSignature<ParameterTypeInfo> signature) => ComplexInfo;
public ParameterTypeInfo GetGenericMethodParameter(object genericContext, int index) => ComplexInfo;
public ParameterTypeInfo GetGenericTypeParameter(object genericContext, int index) => ComplexInfo;
public ParameterTypeInfo GetModifiedType(ParameterTypeInfo modifier, ParameterTypeInfo unmodifiedType, bool isRequired) => ComplexInfo;
public ParameterTypeInfo GetPinnedType(ParameterTypeInfo elementType) => ComplexInfo;
public ParameterTypeInfo GetPointerType(ParameterTypeInfo elementType) => ComplexInfo;
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
......@@ -49,6 +51,39 @@ internal partial class SymbolTreeInfo : IChecksummedObject
/// </summary>
private readonly OrderPreservingMultiDictionary<int, int> _inheritanceMap;
/// <summary>
/// Maps the name of target type name of simple extension methods to its <see cref="ExtensionMethodInfo" />.
/// <see cref="ParameterTypeInfo"/> for the definition of simple/complex methods.
/// </summary>
private readonly MultiDictionary<string, ExtensionMethodInfo>? _simpleTypeNameToExtensionMethodMap;
/// <summary>
/// A list of <see cref="ExtensionMethodInfo" /> for complex extension methods.
/// <see cref="ParameterTypeInfo"/> for the definition of simple/complex methods.
/// </summary>
private readonly ImmutableArray<ExtensionMethodInfo> _extensionMethodOfComplexType;
public bool ContainsExtensionMethod => _simpleTypeNameToExtensionMethodMap?.Count > 0 || _extensionMethodOfComplexType.Length > 0;
public ImmutableArray<ExtensionMethodInfo> GetMatchingExtensionMethodInfo(ImmutableArray<string> parameterTypeNames)
{
if (_simpleTypeNameToExtensionMethodMap == null)
{
return _extensionMethodOfComplexType;
}
var builder = ArrayBuilder<ExtensionMethodInfo>.GetInstance();
builder.AddRange(_extensionMethodOfComplexType);
foreach (var parameterTypeName in parameterTypeNames)
{
var simpleMethods = _simpleTypeNameToExtensionMethodMap[parameterTypeName];
builder.AddRange(simpleMethods);
}
return builder.ToImmutableAndFree();
}
/// <summary>
/// The task that produces the spell checker we use for fuzzy match queries.
/// We use a task so that we can generate the <see cref="SymbolTreeInfo"/>
......@@ -85,9 +120,12 @@ internal partial class SymbolTreeInfo : IChecksummedObject
string concatenatedNames,
ImmutableArray<Node> sortedNodes,
Task<SpellChecker> spellCheckerTask,
OrderPreservingMultiDictionary<string, string> inheritanceMap)
OrderPreservingMultiDictionary<string, string> inheritanceMap,
ImmutableArray<ExtensionMethodInfo> extensionMethodOfComplexType,
MultiDictionary<string, ExtensionMethodInfo> simpleTypeNameToExtensionMethodMap)
: this(checksum, concatenatedNames, sortedNodes, spellCheckerTask,
CreateIndexBasedInheritanceMap(concatenatedNames, sortedNodes, inheritanceMap))
CreateIndexBasedInheritanceMap(concatenatedNames, sortedNodes, inheritanceMap),
extensionMethodOfComplexType, simpleTypeNameToExtensionMethodMap)
{
}
......@@ -96,13 +134,17 @@ internal partial class SymbolTreeInfo : IChecksummedObject
string concatenatedNames,
ImmutableArray<Node> sortedNodes,
Task<SpellChecker> spellCheckerTask,
OrderPreservingMultiDictionary<int, int> inheritanceMap)
OrderPreservingMultiDictionary<int, int> inheritanceMap,
ImmutableArray<ExtensionMethodInfo> extensionMethodOfComplexType,
MultiDictionary<string, ExtensionMethodInfo>? simpleTypeNameToExtensionMethodMap)
{
Checksum = checksum;
_concatenatedNames = concatenatedNames;
_nodes = sortedNodes;
_spellCheckerTask = spellCheckerTask;
_inheritanceMap = inheritanceMap;
_extensionMethodOfComplexType = extensionMethodOfComplexType;
_simpleTypeNameToExtensionMethodMap = simpleTypeNameToExtensionMethodMap;
}
public static SymbolTreeInfo CreateEmpty(Checksum checksum)
......@@ -112,13 +154,15 @@ public static SymbolTreeInfo CreateEmpty(Checksum checksum)
return new SymbolTreeInfo(checksum, concatenatedNames, sortedNodes,
CreateSpellCheckerAsync(checksum, concatenatedNames, sortedNodes),
new OrderPreservingMultiDictionary<string, string>());
new OrderPreservingMultiDictionary<string, string>(),
ImmutableArray<ExtensionMethodInfo>.Empty,
new MultiDictionary<string, ExtensionMethodInfo>());
}
public SymbolTreeInfo WithChecksum(Checksum checksum)
{
return new SymbolTreeInfo(
checksum, _concatenatedNames, _nodes, _spellCheckerTask, _inheritanceMap);
checksum, _concatenatedNames, _nodes, _spellCheckerTask, _inheritanceMap, _extensionMethodOfComplexType, _simpleTypeNameToExtensionMethodMap);
}
public Task<ImmutableArray<SymbolAndProjectId>> FindAsync(
......@@ -206,7 +250,7 @@ public SymbolTreeInfo WithChecksum(Checksum checksum)
{
var comparer = GetComparer(ignoreCase);
var results = ArrayBuilder<ISymbol>.GetInstance();
IAssemblySymbol assemblySymbol = null;
IAssemblySymbol? assemblySymbol = null;
foreach (var node in FindNodeIndices(name, comparer))
{
......@@ -358,7 +402,7 @@ private static int BinarySearch(string concatenatedNames, ImmutableArray<Node> n
out ImmutableArray<Node> sortedNodes)
{
// Generate index numbers from 0 to Count-1
var tmp = new int[unsortedNodes.Length];
int[]? tmp = new int[unsortedNodes.Length];
for (var i = 0; i < tmp.Length; i++)
{
tmp[i] = i;
......@@ -383,7 +427,7 @@ private static int BinarySearch(string concatenatedNames, ImmutableArray<Node> n
result.Count = unsortedNodes.Length;
var concatenatedNamesBuilder = new StringBuilder();
string lastName = null;
string? lastName = null;
// Copy nodes into the result array in the appropriate order and fixing
// up parent indexes as we go.
......@@ -528,7 +572,9 @@ internal void AssertEquivalentTo(SymbolTreeInfo other)
private static SymbolTreeInfo CreateSymbolTreeInfo(
Solution solution, Checksum checksum,
string filePath, ImmutableArray<BuilderNode> unsortedNodes,
OrderPreservingMultiDictionary<string, string> inheritanceMap)
OrderPreservingMultiDictionary<string, string> inheritanceMap,
MultiDictionary<string, ExtensionMethodInfo> simpleMethods,
ImmutableArray<ExtensionMethodInfo> complexMethods)
{
SortNodes(unsortedNodes, out var concatenatedNames, out var sortedNodes);
var createSpellCheckerTask = GetSpellCheckerTask(
......@@ -536,7 +582,8 @@ internal void AssertEquivalentTo(SymbolTreeInfo other)
return new SymbolTreeInfo(
checksum, concatenatedNames,
sortedNodes, createSpellCheckerTask, inheritanceMap);
sortedNodes, createSpellCheckerTask, inheritanceMap,
complexMethods, simpleMethods);
}
private static OrderPreservingMultiDictionary<int, int> CreateIndexBasedInheritanceMap(
......
......@@ -5,13 +5,13 @@
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Runtime.CompilerServices;
using System.Reflection.Metadata.Ecma335;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Serialization;
using Microsoft.CodeAnalysis.Utilities;
......@@ -229,6 +229,16 @@ private struct MetadataInfoCreator : IDisposable
// The set of type definitions we've read out of the current metadata reader.
private readonly List<MetadataDefinition> _allTypeDefinitions;
// Map from node represents extension method to list of possible parameter type info.
// We can have more than one if there's multiple methods with same name but different target type.
// e.g.
//
// public static bool AnotherExtensionMethod1(this int x);
// public static bool AnotherExtensionMethod1(this bool x);
//
private MultiDictionary<MetadataNode, ParameterTypeInfo> _extensionMethodToParameterTypeInfo;
private bool _containsExtensionsMethod;
public MetadataInfoCreator(
Solution solution, Checksum checksum, PortableExecutableReference reference, CancellationToken cancellationToken)
{
......@@ -238,9 +248,11 @@ private struct MetadataInfoCreator : IDisposable
_cancellationToken = cancellationToken;
_metadataReader = null;
_allTypeDefinitions = new List<MetadataDefinition>();
_containsExtensionsMethod = false;
_inheritanceMap = OrderPreservingMultiDictionary<string, string>.GetInstance();
_parentToChildren = OrderPreservingMultiDictionary<MetadataNode, MetadataNode>.GetInstance();
_extensionMethodToParameterTypeInfo = new MultiDictionary<MetadataNode, ParameterTypeInfo>();
_rootNode = MetadataNode.Allocate(name: "");
}
......@@ -295,9 +307,12 @@ internal SymbolTreeInfo Create()
}
}
var unsortedNodes = GenerateUnsortedNodes();
var simpleMap = new MultiDictionary<string, ExtensionMethodInfo>();
var complexBuilder = ArrayBuilder<ExtensionMethodInfo>.GetInstance();
var unsortedNodes = GenerateUnsortedNodes(complexBuilder, simpleMap);
return CreateSymbolTreeInfo(
_solution, _checksum, _reference.FilePath, unsortedNodes, _inheritanceMap);
_solution, _checksum, _reference.FilePath, unsortedNodes, _inheritanceMap, simpleMap, complexBuilder.ToImmutableAndFree());
}
public void Dispose()
......@@ -356,6 +371,12 @@ private void GenerateMetadataNodes()
{
foreach (var definition in definitionsWithSameName)
{
if (definition.Kind == MetadataDefinitionKind.Member)
{
// We need to support having multiple methods with same name but different target type.
_extensionMethodToParameterTypeInfo.Add(childNode, definition.TargetTypeInfo);
}
LookupMetadataDefinitions(definition, definitionMap);
}
......@@ -407,12 +428,23 @@ private void GenerateMetadataNodes()
// we just pull in methods that have attributes on them.
if ((method.Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Public &&
(method.Attributes & MethodAttributes.Static) != 0 &&
method.GetParameters().Count > 0 &&
method.GetCustomAttributes().Count > 0)
{
var definition = new MetadataDefinition(
MetadataDefinitionKind.Member, _metadataReader.GetString(method.Name));
definitionMap.Add(definition.Name, definition);
// Decode method signature to get the target type name (i.e. type name for the first parameter)
var blob = _metadataReader.GetBlobReader(method.Signature);
var decoder = new SignatureDecoder<ParameterTypeInfo, object>(ParameterTypeInfoProvider.Instance, _metadataReader, genericContext: null);
var signature = decoder.DecodeMethodSignature(ref blob);
// It'd be good if we don't need to go through all parameters and make unnecessary allocations.
// However, this is not possible with meatadata reader API right now (although it's possible by copying code from meatadata reader implementaion)
if (signature.ParameterTypes.Length > 0)
{
_containsExtensionsMethod = true;
var firstParameterTypeInfo = signature.ParameterTypes[0];
var definition = new MetadataDefinition(MetadataDefinitionKind.Member, _metadataReader.GetString(method.Name), firstParameterTypeInfo);
definitionMap.Add(definition.Name, definition);
}
}
}
}
......@@ -665,26 +697,59 @@ private void EnsureParentsAndChildren(List<string> simpleNames)
return newChildNode;
}
private ImmutableArray<BuilderNode> GenerateUnsortedNodes()
private ImmutableArray<BuilderNode> GenerateUnsortedNodes(ArrayBuilder<ExtensionMethodInfo> complexBuilder, MultiDictionary<string, ExtensionMethodInfo> simpleTypeNameToMethodMap)
{
var unsortedNodes = ArrayBuilder<BuilderNode>.GetInstance();
unsortedNodes.Add(BuilderNode.RootNode);
AddUnsortedNodes(unsortedNodes, parentNode: _rootNode, parentIndex: 0);
AddUnsortedNodes(unsortedNodes, simpleTypeNameToMethodMap, complexBuilder, parentNode: _rootNode, parentIndex: 0, fullyQualifiedContainerName: _containsExtensionsMethod ? "" : null);
return unsortedNodes.ToImmutableAndFree();
}
private void AddUnsortedNodes(
ArrayBuilder<BuilderNode> unsortedNodes, MetadataNode parentNode, int parentIndex)
private void AddUnsortedNodes(ArrayBuilder<BuilderNode> unsortedNodes,
MultiDictionary<string, ExtensionMethodInfo> simpleBuilder,
ArrayBuilder<ExtensionMethodInfo> complexBuilder,
MetadataNode parentNode,
int parentIndex,
string fullyQualifiedContainerName)
{
foreach (var child in _parentToChildren[parentNode])
{
var childNode = new BuilderNode(child.Name, parentIndex);
var childNode = new BuilderNode(child.Name, parentIndex, _extensionMethodToParameterTypeInfo[child]);
var childIndex = unsortedNodes.Count;
unsortedNodes.Add(childNode);
AddUnsortedNodes(unsortedNodes, child, childIndex);
if (fullyQualifiedContainerName != null)
{
foreach (var parameterTypeInfo in _extensionMethodToParameterTypeInfo[child])
{
if (parameterTypeInfo.IsComplexType)
{
complexBuilder.Add(new ExtensionMethodInfo(fullyQualifiedContainerName, child.Name));
}
else
{
simpleBuilder.Add(parameterTypeInfo.Name, new ExtensionMethodInfo(fullyQualifiedContainerName, child.Name));
}
}
}
AddUnsortedNodes(unsortedNodes, simpleBuilder, complexBuilder, child, childIndex, Concat(fullyQualifiedContainerName, child.Name));
}
static string Concat(string containerName, string name)
{
if (containerName == null)
{
return null;
}
if (containerName.Length == 0)
{
return name;
}
return containerName + "." + name;
}
}
}
......@@ -727,14 +792,20 @@ private struct MetadataDefinition
public string Name { get; }
public MetadataDefinitionKind Kind { get; }
/// <summary>
/// Only applies to member kind. Represents the type info of the first parameter.
/// </summary>
public ParameterTypeInfo TargetTypeInfo { get; }
public NamespaceDefinition Namespace { get; private set; }
public TypeDefinition Type { get; private set; }
public MetadataDefinition(MetadataDefinitionKind kind, string name)
public MetadataDefinition(MetadataDefinitionKind kind, string name, ParameterTypeInfo targetTypeInfo = default)
: this()
{
Kind = kind;
Name = name;
TargetTypeInfo = targetTypeInfo;
}
public static MetadataDefinition Create(
......
......@@ -10,7 +10,6 @@
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Serialization;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Utilities;
using Roslyn.Utilities;
......@@ -20,7 +19,7 @@ namespace Microsoft.CodeAnalysis.FindSymbols
internal partial class SymbolTreeInfo : IObjectWritable
{
private const string PrefixMetadataSymbolTreeInfo = "<SymbolTreeInfo>";
private static readonly Checksum SerializationFormatChecksum = Checksum.Create("18");
private static readonly Checksum SerializationFormatChecksum = Checksum.Create("19");
/// <summary>
/// Loads the SpellChecker for a given assembly symbol (metadata or project). If the
......@@ -153,6 +152,35 @@ public void WriteTo(ObjectWriter writer)
writer.WriteInt32(v);
}
}
if (_simpleTypeNameToExtensionMethodMap == null)
{
writer.WriteInt32(0);
}
else
{
writer.WriteInt32(_simpleTypeNameToExtensionMethodMap.Count);
foreach (var key in _simpleTypeNameToExtensionMethodMap.Keys)
{
writer.WriteString(key);
var values = _simpleTypeNameToExtensionMethodMap[key];
writer.WriteInt32(values.Count);
foreach (var value in values)
{
writer.WriteString(value.FullyQualifiedContainerName);
writer.WriteString(value.Name);
}
}
}
writer.WriteInt32(_extensionMethodOfComplexType.Length);
foreach (var methodInfo in _extensionMethodOfComplexType)
{
writer.WriteString(methodInfo.FullyQualifiedContainerName);
writer.WriteString(methodInfo.Name);
}
}
internal static SymbolTreeInfo ReadSymbolTreeInfo_ForTestingPurposesOnly(
......@@ -197,9 +225,56 @@ public void WriteTo(ObjectWriter writer)
}
}
MultiDictionary<string, ExtensionMethodInfo> simpleTypeNameToExtensionMethodMap;
ImmutableArray<ExtensionMethodInfo> extensionMethodOfComplexType;
var keyCount = reader.ReadInt32();
if (keyCount == 0)
{
simpleTypeNameToExtensionMethodMap = null;
}
else
{
simpleTypeNameToExtensionMethodMap = new MultiDictionary<string, ExtensionMethodInfo>();
for (var i = 0; i < keyCount; i++)
{
var typeName = reader.ReadString();
var valueCount = reader.ReadInt32();
for (var j = 0; j < valueCount; j++)
{
var containerName = reader.ReadString();
var name = reader.ReadString();
simpleTypeNameToExtensionMethodMap.Add(typeName, new ExtensionMethodInfo(containerName, name));
}
}
}
var arrayLength = reader.ReadInt32();
if (arrayLength == 0)
{
extensionMethodOfComplexType = ImmutableArray<ExtensionMethodInfo>.Empty;
}
else
{
var builder = ArrayBuilder<ExtensionMethodInfo>.GetInstance(arrayLength);
for (var i = 0; i < arrayLength; ++i)
{
var containerName = reader.ReadString();
var name = reader.ReadString();
builder.Add(new ExtensionMethodInfo(containerName, name));
}
extensionMethodOfComplexType = builder.ToImmutableAndFree();
}
var nodeArray = nodes.ToImmutableAndFree();
var spellCheckerTask = createSpellCheckerTask(concatenatedNames, nodeArray);
return new SymbolTreeInfo(checksum, concatenatedNames, nodeArray, spellCheckerTask, inheritanceMap);
return new SymbolTreeInfo(
checksum, concatenatedNames, nodeArray, spellCheckerTask, inheritanceMap,
extensionMethodOfComplexType, simpleTypeNameToExtensionMethodMap);
}
catch
{
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
......@@ -117,7 +118,9 @@ private static async Task<Checksum> ComputeSourceSymbolsChecksumAsync(ProjectSta
return CreateSymbolTreeInfo(
project.Solution, checksum, project.FilePath, unsortedNodes.ToImmutableAndFree(),
inheritanceMap: new OrderPreservingMultiDictionary<string, string>());
inheritanceMap: new OrderPreservingMultiDictionary<string, string>(),
simpleMethods: null,
complexMethods: ImmutableArray<ExtensionMethodInfo>.Empty);
}
// generate nodes for the global namespace an all descendants
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable enable
using System;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.FindSymbols
{
internal partial class SyntaxTreeIndex
{
private readonly struct ExtensionMethodInfo
{
// We divide extension methods into two categories, simple and complex, 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.
//
// Complex methods include:
// - Method declared in the document which includes using alias directive
// - Generic method where the target type is a type-paramter (e.g. List<T> would be considered simple, not complex)
// - If the target type name is one of the following (i.e. name of the type for the first parameter)
// 1. Array type
// 2. ValueTuple type
// 3. Pointer type
//
// The rest of methods are considered simple.
/// <summary>
/// Name of the simple method's target type name to the index of its DeclaredSymbolInfo in `_declarationInfo`.
/// All predefined types are converted to its metadata form. e.g. int => Int32. For generic types, type parameters are ignored.
/// </summary>
public readonly ImmutableDictionary<string, ImmutableArray<int>> SimpleExtensionMethodInfo { get; }
/// <summary>
/// Indices of to all complex methods' DeclaredSymbolInfo in `_declarationInfo`.
/// </summary>
public readonly ImmutableArray<int> ComplexExtensionMethodInfo { get; }
public bool ContainsExtensionMethod => SimpleExtensionMethodInfo.Count > 0 || ComplexExtensionMethodInfo.Length > 0;
public ExtensionMethodInfo(
ImmutableDictionary<string, ImmutableArray<int>> simpleExtensionMethodInfo,
ImmutableArray<int> complexExtensionMethodInfo)
{
SimpleExtensionMethodInfo = simpleExtensionMethodInfo;
ComplexExtensionMethodInfo = complexExtensionMethodInfo;
}
public void WriteTo(ObjectWriter writer)
{
writer.WriteInt32(SimpleExtensionMethodInfo.Count);
foreach (var kvp in SimpleExtensionMethodInfo)
{
writer.WriteString(kvp.Key);
writer.WriteInt32(kvp.Value.Length);
foreach (var declaredSymbolInfoIndex in kvp.Value)
{
writer.WriteInt32(declaredSymbolInfoIndex);
}
}
writer.WriteInt32(ComplexExtensionMethodInfo.Length);
foreach (var declaredSymbolInfoIndex in ComplexExtensionMethodInfo)
{
writer.WriteInt32(declaredSymbolInfoIndex);
}
}
public static ExtensionMethodInfo? TryReadFrom(ObjectReader reader)
{
try
{
var simpleExtensionMethodInfo = ImmutableDictionary.CreateBuilder<string, ImmutableArray<int>>();
var count = reader.ReadInt32();
for (var i = 0; i < count; ++i)
{
var typeName = reader.ReadString();
var arrayLength = reader.ReadInt32();
var arrayBuilder = ArrayBuilder<int>.GetInstance(arrayLength);
for (var j = 0; j < arrayLength; ++j)
{
var declaredSymbolInfoIndex = reader.ReadInt32();
arrayBuilder.Add(declaredSymbolInfoIndex);
}
simpleExtensionMethodInfo[typeName] = arrayBuilder.ToImmutableAndFree();
}
count = reader.ReadInt32();
var complexExtensionMethodInfo = ArrayBuilder<int>.GetInstance(count);
for (var i = 0; i < count; ++i)
{
var declaredSymbolInfoIndex = reader.ReadInt32();
complexExtensionMethodInfo.Add(declaredSymbolInfoIndex);
}
return new ExtensionMethodInfo(simpleExtensionMethodInfo.ToImmutable(), complexExtensionMethodInfo.ToImmutableAndFree());
}
catch (Exception)
{
}
return null;
}
}
}
}
......@@ -160,6 +160,7 @@ internal enum FunctionId
Completion_KeywordCompletionProvider_GetItemsWorker,
Completion_SnippetCompletionProvider_GetItemsWorker_CSharp,
Completion_TypeImportCompletionProvider_GetCompletionItemsAsync,
Completion_ExtensionMethodImportCompletionProvider_GetCompletionItemsAsync,
SignatureHelp_ModelComputation_ComputeModelInBackground,
SignatureHelp_ModelComputation_UpdateModelInBackground,
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册