提交 e636d3df 编写于 作者: G Gen Lu

Add caching for syntax based filter per project

上级 e4f2f1d5
// 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<TProject, TPortableExecutable> : IWorkspaceServiceFactory
{
private readonly ConcurrentDictionary<string, TPortableExecutable> _peItemsCache
= new ConcurrentDictionary<string, TPortableExecutable>();
private readonly ConcurrentDictionary<ProjectId, TProject> _projectItemsCache
= new ConcurrentDictionary<ProjectId, TProject>();
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<TProject, TPortableExecutable>
{
public IDictionary<string, TPortableExecutable> PEItemsCache { get; }
public IDictionary<ProjectId, TProject> ProjectItemsCache { get; }
public ImportCompletionCacheService(
ConcurrentDictionary<string, TPortableExecutable> peCache,
ConcurrentDictionary<ProjectId, TProject> 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.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 IImportCompletionCacheService : IWorkspaceService
{
// PE references are keyed on assembly path.
IDictionary<string, ReferenceCacheEntry> PEItemsCache { get; }
IDictionary<ProjectId, ReferenceCacheEntry> ProjectItemsCache { get; }
}
[ExportWorkspaceServiceFactory(typeof(IImportCompletionCacheService), ServiceLayer.Editor), Shared]
private class ImportCompletionCacheServiceFactory : IWorkspaceServiceFactory
{
private readonly ConcurrentDictionary<string, ReferenceCacheEntry> _peItemsCache
= new ConcurrentDictionary<string, ReferenceCacheEntry>();
private readonly ConcurrentDictionary<ProjectId, ReferenceCacheEntry> _projectItemsCache
= new ConcurrentDictionary<ProjectId, ReferenceCacheEntry>();
[ImportingConstructor]
public ImportCompletionCacheServiceFactory()
{
}
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
{
public IDictionary<string, ReferenceCacheEntry> PEItemsCache { get; }
public IDictionary<ProjectId, ReferenceCacheEntry> ProjectItemsCache { get; }
public ImportCompletionCacheService(
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.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)
{
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>
{
[ImportingConstructor]
public CacheServiceFactory()
{
}
}
}
}
......@@ -5,20 +5,17 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
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 IImportCompletionCacheService CacheService { get; }
private IImportCompletionCacheService<CacheEntry, CacheEntry> CacheService { get; }
protected abstract string GenericTypeSuffix { get; }
......@@ -26,7 +23,7 @@ internal abstract partial class AbstractTypeImportCompletionService : ITypeImpor
internal AbstractTypeImportCompletionService(Workspace workspace)
{
CacheService = workspace.Services.GetRequiredService<IImportCompletionCacheService>();
CacheService = workspace.Services.GetRequiredService<IImportCompletionCacheService<CacheEntry, CacheEntry>>();
}
public async Task<ImmutableArray<CompletionItem>> GetTopLevelTypesAsync(
......@@ -98,7 +95,7 @@ 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);
......@@ -110,12 +107,12 @@ static string GetReferenceKey(PortableExecutableReference reference)
IsCaseSensitive);
}
private ReferenceCacheEntry GetCacheEntry<TKey>(
private CacheEntry GetCacheEntry<TKey>(
TKey key,
IAssemblySymbol assembly,
Checksum checksum,
SyntaxContext syntaxContext,
IDictionary<TKey, ReferenceCacheEntry> cache,
IDictionary<TKey, CacheEntry> cache,
CancellationToken cancellationToken)
{
var language = syntaxContext.SemanticModel.Language;
......@@ -124,7 +121,7 @@ static string GetReferenceKey(PortableExecutableReference reference)
if (!cache.TryGetValue(key, out var cacheEntry) ||
cacheEntry.Checksum != checksum)
{
using 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;
......@@ -135,7 +132,7 @@ static string GetReferenceKey(PortableExecutableReference reference)
private static void GetCompletionItemsForTopLevelTypeDeclarations(
INamespaceSymbol rootNamespaceSymbol,
ReferenceCacheEntry.Builder builder,
CacheEntry.Builder builder,
CancellationToken cancellationToken)
{
VisitNamespace(rootNamespaceSymbol, containingNamespace: null, builder, cancellationToken);
......@@ -144,7 +141,7 @@ static string GetReferenceKey(PortableExecutableReference reference)
static void VisitNamespace(
INamespaceSymbol symbol,
string? containingNamespace,
ReferenceCacheEntry.Builder builder,
CacheEntry.Builder builder,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
......@@ -233,124 +230,6 @@ public TypeOverloadInfo Aggregate(INamedTypeSymbol type)
}
}
private readonly struct ReferenceCacheEntry
{
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 ReferenceCacheEntry ToReferenceCacheEntry()
{
return new ReferenceCacheEntry(
_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();
}
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 = 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;
}
}
}
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.
#nullable enable
using System;
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.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Completion.Providers
{
internal static partial class ExtensionMethodImportCompletionService
{
private readonly struct CacheEntry
{
public Checksum Checksum { get; }
public readonly MultiDictionary<string, DeclaredSymbolInfo> SimpleExtensionMethodInfo { get; }
public readonly ImmutableArray<DeclaredSymbolInfo> ComplexExtensionMethodInfo { get; }
private CacheEntry(
Checksum checksum,
MultiDictionary<string, DeclaredSymbolInfo> simpleExtensionMethodInfo,
ImmutableArray<DeclaredSymbolInfo> complexExtensionMethodInfo)
{
Checksum = checksum;
SimpleExtensionMethodInfo = simpleExtensionMethodInfo;
ComplexExtensionMethodInfo = complexExtensionMethodInfo;
}
public class Builder : IDisposable
{
private readonly Checksum _checksum;
private readonly MultiDictionary<string, DeclaredSymbolInfo> _simpleItemBuilder;
private readonly ArrayBuilder<DeclaredSymbolInfo> _complexItemBuilder;
public Builder(Checksum checksum)
{
_checksum = checksum;
_simpleItemBuilder = new MultiDictionary<string, DeclaredSymbolInfo>();
_complexItemBuilder = ArrayBuilder<DeclaredSymbolInfo>.GetInstance();
}
public CacheEntry ToCacheEntry()
{
return new CacheEntry(
_checksum,
_simpleItemBuilder,
_complexItemBuilder.ToImmutable());
}
public void AddItem(SyntaxTreeIndex syntaxIndex)
{
foreach (var (targetType, symbolInfoIndices) in syntaxIndex.SimpleExtensionMethodInfo)
{
foreach (var index in symbolInfoIndices)
{
if (syntaxIndex.TryGetDeclaredSymbolInfo(index, out var methodInfo))
{
_simpleItemBuilder.Add(targetType, methodInfo);
}
}
}
foreach (var index in syntaxIndex.ComplexExtensionMethodInfo)
{
if (syntaxIndex.TryGetDeclaredSymbolInfo(index, out var methodInfo))
{
_complexItemBuilder.Add(methodInfo);
}
}
}
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>
{
[ImportingConstructor]
public CacheServiceFactory()
{
}
}
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)
{
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)
{
using var builder = new CacheEntry.Builder(checksum);
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; }
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册