提交 612f5c3c 编写于 作者: G Gen Lu

Address review comments

上级 d5d152d8
......@@ -8,6 +8,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery;
namespace Microsoft.CodeAnalysis.Completion.Providers
......@@ -26,10 +27,10 @@ protected override bool ShouldProvideCompletion(Document document, SyntaxContext
bool isExpandedCompletion,
CancellationToken cancellationToken)
{
var syntaxFacts = completionContext.Document.Project.LanguageServices.GetRequiredService<ISyntaxFactsService>();
var syntaxFacts = completionContext.Document.GetRequiredLanguageService<ISyntaxFactsService>();
if (TryGetReceiverTypeSymbol(syntaxContext, syntaxFacts, out var receiverTypeSymbol))
{
var items = await ExtensionMethodImportCompletionService.GetUnimportExtensionMethodsAsync(
var items = await ExtensionMethodImportCompletionHelper.GetUnimportExtensionMethodsAsync(
completionContext.Document,
completionContext.Position,
receiverTypeSymbol,
......@@ -63,27 +64,18 @@ private static bool TryGetReceiverTypeSymbol(SyntaxContext syntaxContext, ISynta
}
private static ITypeSymbol? GetSymbolType(ISymbol symbol)
{
switch (symbol)
=> symbol switch
{
case ILocalSymbol localSymbol:
return localSymbol.Type;
case IFieldSymbol fieldSymbol:
return fieldSymbol.Type;
case IPropertySymbol propertySymbol:
return propertySymbol.Type;
case IParameterSymbol parameterSymbol:
return parameterSymbol.Type;
case IAliasSymbol aliasSymbol:
return aliasSymbol.Target as ITypeSymbol;
}
return symbol as ITypeSymbol;
}
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)
{
return ImportCompletionItem.Create(
private CompletionItem Convert(SerializableImportCompletionItem serializableItem)
=> ImportCompletionItem.Create(
serializableItem.Name,
serializableItem.Arity,
serializableItem.ContainingNamespace,
......@@ -91,6 +83,5 @@ private CompletionItem Convert(SerializableImportCompletionItem serializableItem
GenericSuffix,
CompletionItemFlags.Expanded,
serializableItem.SymbolKeyData);
}
}
}
......@@ -10,13 +10,13 @@
namespace Microsoft.CodeAnalysis.Completion.Providers.ImportCompletion
{
internal abstract class AbstractImportCompletionCacheServiceFactory<TProject, TPortableExecutable> : IWorkspaceServiceFactory
internal abstract class AbstractImportCompletionCacheServiceFactory<TProjectCacheEntry, TMetadataCacheEntry> : IWorkspaceServiceFactory
{
private readonly ConcurrentDictionary<string, TPortableExecutable> _peItemsCache
= new ConcurrentDictionary<string, TPortableExecutable>();
private readonly ConcurrentDictionary<string, TMetadataCacheEntry> _peItemsCache
= new ConcurrentDictionary<string, TMetadataCacheEntry>();
private readonly ConcurrentDictionary<ProjectId, TProject> _projectItemsCache
= new ConcurrentDictionary<ProjectId, TProject>();
private readonly ConcurrentDictionary<ProjectId, TProjectCacheEntry> _projectItemsCache
= new ConcurrentDictionary<ProjectId, TProjectCacheEntry>();
public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
{
......@@ -39,15 +39,15 @@ private void OnCacheFlushRequested(object sender, EventArgs e)
_projectItemsCache.Clear();
}
private class ImportCompletionCacheService : IImportCompletionCacheService<TProject, TPortableExecutable>
private class ImportCompletionCacheService : IImportCompletionCacheService<TProjectCacheEntry, TMetadataCacheEntry>
{
public IDictionary<string, TPortableExecutable> PEItemsCache { get; }
public IDictionary<string, TMetadataCacheEntry> PEItemsCache { get; }
public IDictionary<ProjectId, TProject> ProjectItemsCache { get; }
public IDictionary<ProjectId, TProjectCacheEntry> ProjectItemsCache { get; }
public ImportCompletionCacheService(
ConcurrentDictionary<string, TPortableExecutable> peCache,
ConcurrentDictionary<ProjectId, TProject> projectCache)
ConcurrentDictionary<string, TMetadataCacheEntry> peCache,
ConcurrentDictionary<ProjectId, TProjectCacheEntry> projectCache)
{
PEItemsCache = peCache;
ProjectItemsCache = projectCache;
......
......@@ -113,51 +113,49 @@ internal override async Task<CompletionChange> GetChangeAsync(Document document,
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);
// 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()));
}
// 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);
// 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()
{
......
......@@ -41,6 +41,11 @@ internal abstract partial class AbstractTypeImportCompletionService
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)
{
......
......@@ -18,13 +18,16 @@
namespace Microsoft.CodeAnalysis.Completion.Providers
{
internal static partial class ExtensionMethodImportCompletionService
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; }
......
......@@ -19,29 +19,11 @@
namespace Microsoft.CodeAnalysis.Completion.Providers
{
internal sealed class 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;
}
}
/// <summary>
/// Provides completion items for extension methods from unimported namespace.
/// </summary>
/// <remarks>It runs out-of-proc if it's enabled</remarks>
internal static partial class ExtensionMethodImportCompletionService
internal static partial class ExtensionMethodImportCompletionHelper
{
private static readonly char[] s_dotSeparator = new char[] { '.' };
......
......@@ -39,6 +39,7 @@ public static CompletionItem Create(string name, int arity, string containingNam
}
else
{
// We don't need arity to recover symbol if we have SymbolKeyData.
builder.Add(TypeAritySuffixName, AbstractDeclaredSymbolInfoFactoryService.GetMetadataAritySuffix(arity));
}
......@@ -97,7 +98,7 @@ public static string GetContainingNamespace(CompletionItem item)
public static async Task<CompletionDescription> GetCompletionDescriptionAsync(Document document, CompletionItem item, CancellationToken cancellationToken)
{
var compilation = (await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false))!;
var compilation = (await document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false));
var symbol = GetSymbol(item, compilation);
if (symbol != null)
......@@ -125,11 +126,13 @@ private static string GetFullyQualifiedName(string namespaceName, string typeNam
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 should have all the data to constrcut its metadata name
var containingNamespace = GetContainingNamespace(item);
var typeName = item.Properties.TryGetValue(AttributeFullName, out var attributeFullName) ? attributeFullName : item.DisplayText;
var fullyQualifiedName = GetFullyQualifiedName(containingNamespace, typeName);
......
// 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 sealed class 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;
}
}
}
......@@ -13,7 +13,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Completion.Providers
Protected Overrides ReadOnly Property GenericSuffix As String
Get
Return "(Of )"
Return "(Of ...)"
End Get
End Property
......
......@@ -82,7 +82,7 @@ private string GetDebuggerDisplay()
private readonly struct ParameterTypeInfo
{
/// <summary>
/// This is the type name of the parameter when <see cref="IsComplexType"/> is true.
/// This is the type name of the parameter when <see cref="IsComplexType"/> is false.
/// </summary>
public readonly string Name;
......
......@@ -430,6 +430,8 @@ private void GenerateMetadataNodes()
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;
......
......@@ -20,7 +20,7 @@ internal partial class SyntaxTreeIndex
//
// Complex methods include:
// - Method declared in the document which includes using alias directive
// - Generic method where the target type is generic
// - 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
......
......@@ -131,7 +131,7 @@ internal sealed partial class SyntaxTreeIndex
// }
//
// If we detect this, we will simply treat extension methods whose
// target type is this alais as complex method.
// target type is this alias as complex method.
if (usingAliases.ContainsKey(aliasName))
{
usingAliases[aliasName] = null;
......
......@@ -174,6 +174,19 @@ internal interface ISyntaxFactsService : ILanguageService
bool IsLeftSideOfDot(SyntaxNode node);
SyntaxNode GetRightSideOfDot(SyntaxNode node);
/// <summary>
/// Get the node on the left side of the dot if given a dotted expression.
/// </summary>
/// <param name="allowImplicitTarget">
/// In VB, we have a member access expression with a null expression, this may be one of the
/// following forms:
/// 1) new With { .a = 1, .b = .a <-- .a refers to the anonymous type
/// 2) With obj : .m <-- .m refers to the obj type
/// 3) new T() With { .a = 1, .b = .a <-- 'a refers to the T type
/// If `allowImplicitTarget` is set to true, the returned node will be set to approperiate node, otherwise, it will return null.
/// This parameter has no affect on C# node.
/// </param>
SyntaxNode GetLeftSideOfDot(SyntaxNode node, bool allowImplicitTarget = false);
bool IsRightSideOfQualifiedName(SyntaxNode node);
......
......@@ -27,6 +27,9 @@ internal static partial class DocumentExtensions
public static TLanguageService? GetLanguageService<TLanguageService>(this Document? document) where TLanguageService : class, ILanguageService
=> document?.Project?.LanguageServices?.GetService<TLanguageService>();
public static TLanguageService GetRequiredLanguageService<TLanguageService>(this Document document) where TLanguageService : class, ILanguageService
=> document.Project.LanguageServices.GetRequiredService<TLanguageService>();
public static bool IsOpen(this Document document)
{
var workspace = document.Project.Solution.Workspace as Workspace;
......
......@@ -2,12 +2,12 @@
#nullable enable
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Shared.Extensions
......@@ -122,5 +122,16 @@ public static async Task<VersionStamp> GetVersionAsync(this Project project, Can
var newSolution = project.Solution.AddAnalyzerConfigDocuments(ImmutableArray.Create(documentInfo));
return newSolution.GetProject(project.Id)?.GetAnalyzerConfigDocument(id);
}
public static async Task<Compilation> GetRequiredCompilationAsync(this Project project, CancellationToken cancellationToken)
{
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
if (compilation == null)
{
throw new InvalidOperationException(string.Format(WorkspacesResources.Project_0_does_not_support_compilation, project.Name));
}
return compilation;
}
}
}
......@@ -1366,6 +1366,15 @@ internal class WorkspacesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Project {0} does not support compilation..
/// </summary>
internal static string Project_0_does_not_support_compilation {
get {
return ResourceManager.GetString("Project_0_does_not_support_compilation", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Project file not found: &apos;{0}&apos;.
/// </summary>
......
......@@ -1479,4 +1479,7 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
<data name="Document_does_not_support_syntax_trees" xml:space="preserve">
<value>Document does not support syntax trees</value>
</data>
<data name="Project_0_does_not_support_compilation" xml:space="preserve">
<value>Project {0} does not support compilation.</value>
</data>
</root>
\ No newline at end of file
......@@ -2,18 +2,18 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Completion.Providers;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Shared.Extensions;
namespace Microsoft.CodeAnalysis.Remote
{
internal partial class CodeAnalysisService : IRemoteExtensionMethodImportCompletionService
{
public Task<(IList<SerializableImportCompletionItem>, StatisticCounter)> GetUnimportedExtensionMethodsAsync(
DocumentId documentId,
int position,
......@@ -28,20 +28,20 @@ internal partial class CodeAnalysisService : IRemoteExtensionMethodImportComplet
{
var solution = await GetSolutionAsync(cancellationToken).ConfigureAwait(false);
var document = solution.GetDocument(documentId)!;
var compilation = (await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false))!;
var compilation = (await document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false));
var symbol = SymbolKey.ResolveString(receiverTypeSymbolKeyData, compilation, cancellationToken: cancellationToken).GetAnySymbol();
if (symbol is ITypeSymbol receiverTypeSymbol)
{
var syntaxFacts = document.Project.LanguageServices.GetRequiredService<ISyntaxFactsService>();
var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>();
var namespaceInScopeSet = new HashSet<string>(namespaceInScope, syntaxFacts.StringComparer);
var (items, counter) = await ExtensionMethodImportCompletionService.GetUnimportExtensionMethodsInCurrentProcessAsync(
var (items, counter) = await ExtensionMethodImportCompletionHelper.GetUnimportExtensionMethodsInCurrentProcessAsync(
document, position, receiverTypeSymbol, namespaceInScopeSet, isExpandedCompletion, cancellationToken).ConfigureAwait(false);
return ((IList<SerializableImportCompletionItem>)items, counter);
}
return (new SerializableImportCompletionItem[0], new StatisticCounter());
return (Array.Empty<SerializableImportCompletionItem>(), new StatisticCounter());
}
}, cancellationToken);
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册