提交 b0b7515c 编写于 作者: C CyrusNajmabadi

Merge pull request #7830 from CyrusNajmabadi/addUsingFuzzyTimeout

Produce SymbolTreeInfo indices in the background.  only search them if they're available.
......@@ -407,7 +407,6 @@
<Compile Include="Implementation\Intellisense\Completion\FileSystem\CurrentWorkingDirectoryDiscoveryService.cs" />
<Compile Include="Implementation\Intellisense\Completion\FileSystem\FileSystemCompletionHelper.cs" />
<Compile Include="Implementation\Intellisense\Completion\FileSystem\ICurrentWorkingDirectoryDiscoveryService.cs" />
<Compile Include="Implementation\Intellisense\Completion\FileSystem\IOUtilities.cs" />
<Compile Include="Implementation\Intellisense\Completion\FileSystem\PathCompletionUtilities.cs" />
<Compile Include="Implementation\KeywordHighlighting\AbstractKeywordHighlighter.cs" />
<Compile Include="Implementation\KeywordHighlighting\HighlighterViewTaggerProvider.cs" />
......
......@@ -9,6 +9,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
......
......@@ -108,7 +108,8 @@ private static Type[] GetNeutralAndCSharpAndVisualBasicTypes()
typeof(CodeAnalysis.CSharp.CodeGeneration.CSharpSyntaxGenerator),
typeof(CodeAnalysis.VisualBasic.CodeGeneration.VisualBasicSyntaxGenerator),
typeof(CSharp.LanguageServices.CSharpContentTypeLanguageService),
typeof(VisualBasic.LanguageServices.VisualBasicContentTypeLanguageService)
typeof(VisualBasic.LanguageServices.VisualBasicContentTypeLanguageService),
typeof(IncrementalCaches.SymbolTreeInfoIncrementalAnalyzerProvider),
};
return MinimalTestExportProvider.GetLanguageNeutralTypes()
......
Option Strict Off
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Option Strict Off
Imports System.Threading
Imports System.Threading.Tasks
Imports Microsoft.CodeAnalysis
......@@ -27,12 +27,15 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics
End Function
Protected Async Function TestAsync(definition As XElement,
Optional expected As String = Nothing,
Optional codeActionIndex As Integer = 0,
Optional verifyTokens As Boolean = True,
Optional fileNameToExpected As Dictionary(Of String, String) = Nothing,
Optional verifySolutions As Action(Of Solution, Solution) = Nothing) As Task
Using workspace = Await TestWorkspace.CreateAsync(definition)
Optional expected As String = Nothing,
Optional codeActionIndex As Integer = 0,
Optional verifyTokens As Boolean = True,
Optional fileNameToExpected As Dictionary(Of String, String) = Nothing,
Optional verifySolutions As Action(Of Solution, Solution) = Nothing,
Optional onAfterWorkspaceCreated As Action(Of TestWorkspace) = Nothing) As Task
Using workspace = TestWorkspace.CreateWorkspace(definition)
onAfterWorkspaceCreated?.Invoke(workspace)
Dim diagnosticAndFix = Await GetDiagnosticAndFixAsync(workspace)
Dim codeAction = diagnosticAndFix.Item2.Fixes.ElementAt(codeActionIndex).Action
Dim operations = Await codeAction.GetOperationsAsync(CancellationToken.None)
......
' 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.Tasks
Imports Microsoft.CodeAnalysis.CodeFixes
Imports Microsoft.CodeAnalysis.CSharp.CodeFixes.AddImport
Imports Microsoft.CodeAnalysis.Diagnostics
Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces
Imports Microsoft.CodeAnalysis.Host.Mef
Imports Microsoft.CodeAnalysis.IncrementalCaches
Imports Microsoft.CodeAnalysis.SolutionCrawler
Imports Microsoft.CodeAnalysis.UnitTests
Imports Microsoft.CodeAnalysis.VisualBasic.CodeFixes.AddImport
......@@ -259,7 +264,7 @@ namespace CSAssembly2
}
</text>.Value.Trim()
Await TestAsync(input, expected, codeActionIndex:=0, addedReference:="CSAssembly1")
Await TestAsync(input, expected, codeActionIndex:=0, addedReference:="CSAssembly1", onAfterWorkspaceCreated:=AddressOf WaitForSolutionCrawler)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddImport)>
......@@ -297,9 +302,18 @@ Namespace VBAssembly2
End Namespace
</text>.Value.Trim()
Await TestAsync(input, expected, codeActionIndex:=0, addedReference:="VBAssembly1")
Await TestAsync(input, expected, codeActionIndex:=0, addedReference:="VBAssembly1", onAfterWorkspaceCreated:=AddressOf WaitForSolutionCrawler)
End Function
Private Sub WaitForSolutionCrawler(workspace As TestWorkspace)
Dim solutionCrawler = DirectCast(workspace.Services.GetService(Of ISolutionCrawlerRegistrationService), SolutionCrawlerRegistrationService)
solutionCrawler.Register(workspace)
Dim provider = DirectCast(workspace.ExportProvider.GetExports(Of IWorkspaceServiceFactory).First(
Function(f) TypeOf f.Value Is SymbolTreeInfoIncrementalAnalyzerProvider).Value, SymbolTreeInfoIncrementalAnalyzerProvider)
Dim analyzer = provider.CreateIncrementalAnalyzer(workspace)
solutionCrawler.WaitUntilCompletion_ForTestingPurposesOnly(workspace, ImmutableArray.Create(analyzer))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddImport)>
Public Async Function TestAddProjectReferenceMissingForCircularReference() As Task
Dim input =
......@@ -333,11 +347,11 @@ namespace CSAssembly2
Await TestMissing(input)
End Function
Protected Overloads Async Function TestAsync(definition As XElement,
Optional expected As String = Nothing,
Optional codeActionIndex As Integer = 0,
Optional addedReference As String = Nothing) As Task
Friend Overloads Async Function TestAsync(definition As XElement,
Optional expected As String = Nothing,
Optional codeActionIndex As Integer = 0,
Optional addedReference As String = Nothing,
Optional onAfterWorkspaceCreated As Action(Of TestWorkspace) = Nothing) As Task
Dim verifySolutions As Action(Of Solution, Solution) = Nothing
If addedReference IsNot Nothing Then
verifySolutions =
......@@ -359,7 +373,7 @@ namespace CSAssembly2
End Sub
End If
Await TestAsync(definition, expected, codeActionIndex, verifySolutions:=verifySolutions)
Await TestAsync(definition, expected, codeActionIndex, verifySolutions:=verifySolutions, onAfterWorkspaceCreated:=onAfterWorkspaceCreated)
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.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.FindSymbols.SymbolTree;
using Microsoft.CodeAnalysis.Host;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CodeFixes.AddImport
{
internal abstract partial class AbstractAddImportCodeFixProvider<TSimpleNameSyntax>
{
/// <summary>
/// SearchScope is used to control where the <see cref="AbstractAddImportCodeFixProvider{TSimpleNameSyntax}"/>
/// searches. We search different scopes in different ways. For example we use
/// SymbolTreeInfos to search unreferenced projects and metadata dlls. However,
/// for the current project we're editing we defer to the compiler to do the
/// search.
/// </summary>
private abstract class SearchScope
{
public readonly bool Exact;
......@@ -61,38 +71,93 @@ public async Task<IEnumerable<SearchResult<ISymbol>>> FindDeclarationsAsync(stri
}
}
private class ProjectSearchScope : SearchScope
private abstract class ProjectSearchScope : SearchScope
{
private readonly bool _includeDirectReferences;
private readonly Project _project;
protected readonly Project _project;
public ProjectSearchScope(Project project, bool includeDirectReferences, bool ignoreCase, CancellationToken cancellationToken)
public ProjectSearchScope(Project project, bool ignoreCase, CancellationToken cancellationToken)
: base(ignoreCase, cancellationToken)
{
_project = project;
_includeDirectReferences = includeDirectReferences;
}
public override SymbolReference CreateReference<T>(SearchResult<T> searchResult)
{
return new ProjectSymbolReference(
searchResult.WithSymbol<INamespaceOrTypeSymbol>(searchResult.Symbol), _project.Id);
}
}
/// <summary>
/// SearchScope used for searching *all* the symbols contained within a project/compilation.
/// i.e. the symbols created from source *and* symbols from references (both project and
/// metadata).
/// </summary>
private class AllSymbolsProjectSearchScope : ProjectSearchScope
{
public AllSymbolsProjectSearchScope(Project project, bool ignoreCase, CancellationToken cancellationToken)
: base(project, ignoreCase, cancellationToken)
{
}
protected override Task<IEnumerable<ISymbol>> FindDeclarationsAsync(string name, SymbolFilter filter, SearchQuery searchQuery)
{
return SymbolFinder.FindDeclarationsAsync(
_project, searchQuery, filter, _includeDirectReferences, cancellationToken);
return SymbolFinder.FindDeclarationsAsync(_project, searchQuery, filter, cancellationToken);
}
}
public override SymbolReference CreateReference<T>(SearchResult<T> searchResult)
/// <summary>
/// SearchScope used for searching *only* the source symbols contained within a project/compilation.
/// i.e. symbols from metadata will not be searched.
/// </summary>
private class SourceSymbolsProjectSearchScope : ProjectSearchScope
{
private readonly ConcurrentDictionary<Project, AsyncLazy<IAssemblySymbol>> _projectToAssembly;
public SourceSymbolsProjectSearchScope(
ConcurrentDictionary<Project, AsyncLazy<IAssemblySymbol>> projectToAssembly,
Project project, bool ignoreCase, CancellationToken cancellationToken)
: base(project, ignoreCase, cancellationToken)
{
return new ProjectSymbolReference(
searchResult.WithSymbol<INamespaceOrTypeSymbol>(searchResult.Symbol), _project.Id);
_projectToAssembly = projectToAssembly;
}
protected override async Task<IEnumerable<ISymbol>> FindDeclarationsAsync(string name, SymbolFilter filter, SearchQuery searchQuery)
{
var service = _project.Solution.Workspace.Services.GetService<ISymbolTreeInfoCacheService>();
var info = await service.TryGetSymbolTreeInfoAsync(_project, cancellationToken).ConfigureAwait(false);
if (info == null)
{
// Looks like there was nothing in the cache. Return no results for now.
return SpecializedCollections.EmptyEnumerable<ISymbol>();
}
// Don't create the assembly until it is actually needed by the SymbolTreeInfo.FindAsync
// code. Creating the assembly can be costly and we want to avoid it until it is actually
// needed.
var lazyAssembly = _projectToAssembly.GetOrAdd(_project, CreateLazyAssembly);
return await info.FindAsync(searchQuery, lazyAssembly, cancellationToken).ConfigureAwait(false);
}
private static AsyncLazy<IAssemblySymbol> CreateLazyAssembly(Project project)
{
return new AsyncLazy<IAssemblySymbol>(
async c =>
{
var compilation = await project.GetCompilationAsync(c).ConfigureAwait(false);
return compilation.Assembly;
}, cacheResult: true);
}
}
private class MetadataSearchScope : SearchScope
private class MetadataSymbolsSearchScope : SearchScope
{
private readonly IAssemblySymbol _assembly;
private readonly PortableExecutableReference _metadataReference;
private readonly Solution _solution;
public MetadataSearchScope(
public MetadataSymbolsSearchScope(
Solution solution,
IAssemblySymbol assembly,
PortableExecutableReference metadataReference,
......@@ -112,11 +177,17 @@ public override SymbolReference CreateReference<T>(SearchResult<T> searchResult)
_metadataReference);
}
protected override Task<IEnumerable<ISymbol>> FindDeclarationsAsync(string name, SymbolFilter filter, SearchQuery searchQuery)
protected override async Task<IEnumerable<ISymbol>> FindDeclarationsAsync(string name, SymbolFilter filter, SearchQuery searchQuery)
{
return SymbolFinder.FindDeclarationsAsync(
_solution, _assembly, _metadataReference, searchQuery, filter, cancellationToken);
var service = _solution.Workspace.Services.GetService<ISymbolTreeInfoCacheService>();
var info = await service.TryGetSymbolTreeInfoAsync(_solution, _assembly, _metadataReference, cancellationToken).ConfigureAwait(false);
if (info == null)
{
return SpecializedCollections.EmptyEnumerable<ISymbol>();
}
return await info.FindAsync(searchQuery, _assembly, cancellationToken).ConfigureAwait(false);
}
}
}
}
}
\ No newline at end of file
// 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.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Options;
using Roslyn.Utilities;
using static Roslyn.Utilities.PortableShim;
namespace Microsoft.CodeAnalysis.CodeFixes.AddImport
{
......@@ -75,12 +74,17 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
var finder = new SymbolReferenceFinder(this, document, semanticModel, diagnostic, node, cancellationToken);
// Caches so we don't produce the same data multiple times while searching
// all over the solution.
var projectToAssembly = new ConcurrentDictionary<Project, AsyncLazy<IAssemblySymbol>>(concurrencyLevel: 2, capacity: project.Solution.ProjectIds.Count);
var referenceToCompilation = new ConcurrentDictionary<PortableExecutableReference, Compilation>(concurrencyLevel: 2, capacity: project.Solution.Projects.Sum(p => p.MetadataReferences.Count));
// Look for exact matches first:
await FindResults(project, allSymbolReferences, finder, exact: true, cancellationToken: cancellationToken).ConfigureAwait(false);
await FindResults(projectToAssembly, referenceToCompilation, project, allSymbolReferences, finder, exact: true, cancellationToken: cancellationToken).ConfigureAwait(false);
if (allSymbolReferences.Count == 0)
{
// No exact matches found. Fall back to fuzzy searching.
await FindResults(project, allSymbolReferences, finder, exact: false, cancellationToken: cancellationToken).ConfigureAwait(false);
await FindResults(projectToAssembly, referenceToCompilation, project, allSymbolReferences, finder, exact: false, cancellationToken: cancellationToken).ConfigureAwait(false);
}
// Nothing found at all. No need to proceed.
......@@ -106,17 +110,28 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
}
}
private async Task FindResults(Project project, List<SymbolReference> allSymbolReferences, SymbolReferenceFinder finder, bool exact, CancellationToken cancellationToken)
private async Task FindResults(
ConcurrentDictionary<Project, AsyncLazy<IAssemblySymbol>> projectToAssembly,
ConcurrentDictionary<PortableExecutableReference, Compilation> referenceToCompilation,
Project project, List<SymbolReference> allSymbolReferences, SymbolReferenceFinder finder, bool exact, CancellationToken cancellationToken)
{
await FindResultsInCurrentProject(project, allSymbolReferences, finder, exact).ConfigureAwait(false);
await FindResultsInUnreferencedProjects(project, allSymbolReferences, finder, exact, cancellationToken).ConfigureAwait(false);
await FindResultsInUnreferencedMetadataReferences(project, allSymbolReferences, finder, exact, cancellationToken).ConfigureAwait(false);
// First search the current project to see if any symbols (source or metadata) match the
// search string.
await FindResultsInAllProjectSymbolsAsync(project, allSymbolReferences, finder, exact).ConfigureAwait(false);
// Now search unreferenced projects, and see if they have any source symbols that match
// the search string.
await FindResultsInUnreferencedProjectSourceSymbolsAsync(projectToAssembly, project, allSymbolReferences, finder, exact, cancellationToken).ConfigureAwait(false);
// Finally, check and see if we have any metadata symbols that match the search string.
await FindResultsInUnreferencedMetadataSymbolsAsync(referenceToCompilation, project, allSymbolReferences, finder, exact, cancellationToken).ConfigureAwait(false);
}
private async Task FindResultsInCurrentProject(
private async Task FindResultsInAllProjectSymbolsAsync(
Project project, List<SymbolReference> allSymbolReferences, SymbolReferenceFinder finder, bool exact)
{
AddRange(allSymbolReferences, await finder.FindInProjectAsync(project, includeDirectReferences: true, exact: exact).ConfigureAwait(false));
var references = await finder.FindInAllProjectSymbolsAsync(project, exact).ConfigureAwait(false);
AddRange(allSymbolReferences, references);
}
private async Task<Solution> AddImportAndReferenceAsync(
......@@ -159,7 +174,8 @@ private async Task FindResults(Project project, List<SymbolReference> allSymbolR
}
}
private async Task FindResultsInUnreferencedProjects(
private async Task FindResultsInUnreferencedProjectSourceSymbolsAsync(
ConcurrentDictionary<Project, AsyncLazy<IAssemblySymbol>> projectToAssembly,
Project project, List<SymbolReference> allSymbolReferences, SymbolReferenceFinder finder, bool exact, CancellationToken cancellationToken)
{
// If we didn't find enough hits searching just in the project, then check
......@@ -176,7 +192,7 @@ private async Task FindResults(Project project, List<SymbolReference> allSymbolR
// direct references. i.e. we don't want to search in its metadata references
// or in the projects it references itself. We'll be searching those entities
// individually.
AddRange(allSymbolReferences, await finder.FindInProjectAsync(unreferencedProject, includeDirectReferences: false, exact: exact).ConfigureAwait(false));
AddRange(allSymbolReferences, await finder.FindInSourceProjectSymbolsAsync(projectToAssembly, unreferencedProject, exact: exact).ConfigureAwait(false));
if (allSymbolReferences.Count >= MaxResults)
{
return;
......@@ -184,8 +200,10 @@ private async Task FindResults(Project project, List<SymbolReference> allSymbolR
}
}
private async Task FindResultsInUnreferencedMetadataReferences(
Project project, List<SymbolReference> allSymbolReferences, SymbolReferenceFinder finder, bool exact, CancellationToken cancellationToken)
private async Task FindResultsInUnreferencedMetadataSymbolsAsync(
ConcurrentDictionary<PortableExecutableReference, Compilation> referenceToCompilation,
Project project, List<SymbolReference> allSymbolReferences, SymbolReferenceFinder finder, bool exact,
CancellationToken cancellationToken)
{
if (allSymbolReferences.Count > 0)
{
......@@ -201,54 +219,44 @@ private async Task FindResults(Project project, List<SymbolReference> allSymbolR
var seenReferences = new HashSet<PortableExecutableReference>(comparer: this);
seenReferences.AddAll(project.MetadataReferences.OfType<PortableExecutableReference>());
// Check all the other projects in the system so see if they have a metadata reference
// with a potential result.
foreach (var otherProject in project.Solution.Projects)
var newReferences =
project.Solution.Projects.Where(p => p != project)
.SelectMany(p => p.MetadataReferences.OfType<PortableExecutableReference>())
.Distinct(comparer: this)
.Where(r => !seenReferences.Contains(r));
// Search all metadata references in parallel.
var findTasks = new HashSet<Task<List<SymbolReference>>>();
foreach (var reference in newReferences)
{
if (otherProject == project)
{
continue;
}
var compilation = referenceToCompilation.GetOrAdd(reference, r => CreateCompilation(project, r));
await FindResultsInMetadataReferences(
otherProject, allSymbolReferences, finder, seenReferences, exact, cancellationToken).ConfigureAwait(false);
if (allSymbolReferences.Count >= MaxResults)
// Ignore netmodules. First, they're incredibly esoteric and barely used.
// Second, the SymbolFinder api doesn't even support searching them.
var assembly = compilation.GetAssemblyOrModuleSymbol(reference) as IAssemblySymbol;
if (assembly != null)
{
break;
findTasks.Add(finder.FindInMetadataAsync(project.Solution, assembly, reference, exact));
}
}
}
private async Task FindResultsInMetadataReferences(
Project otherProject,
List<SymbolReference> allSymbolReferences,
SymbolReferenceFinder finder,
HashSet<PortableExecutableReference> seenReferences,
bool exact,
CancellationToken cancellationToken)
{
// See if this project has a metadata reference we haven't already looked at.
var newMetadataReferences = otherProject.MetadataReferences.OfType<PortableExecutableReference>();
Compilation compilation = null;
foreach (var reference in newMetadataReferences)
while (findTasks.Count > 0)
{
// Make sure we don't check the same metadata reference multiple times from
// different projects.
if (seenReferences.Add(reference))
{
// Defer making the compilation until necessary.
compilation = compilation ?? await otherProject.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
// Keep on looping through the 'find' tasks, processing each when they finish.
cancellationToken.ThrowIfCancellationRequested();
var doneTask = await Task.WhenAny(findTasks).ConfigureAwait(false);
// Ignore netmodules. First, they're incredibly esoteric and barely used.
// Second, the SymbolFinder api doesn't even support searching them.
var assembly = compilation.GetAssemblyOrModuleSymbol(reference) as IAssemblySymbol;
if (assembly != null)
{
AddRange(allSymbolReferences, await finder.FindInMetadataAsync(otherProject.Solution, assembly, reference, exact).ConfigureAwait(false));
}
}
// One of the tasks finished. Remove it from the list we're waiting on.
findTasks.Remove(doneTask);
// Add its results to the final result set we're keeping.
AddRange(allSymbolReferences, await doneTask.ConfigureAwait(false));
// If we've got enough, no need to keep searching.
// Note: We do not cancel the existing tasks that are still executing. These tasks will
// cause our indices to be created if necessary. And that's good for future searches which
// we will invariably perform.
if (allSymbolReferences.Count >= MaxResults)
{
break;
......@@ -256,6 +264,23 @@ private async Task FindResults(Project project, List<SymbolReference> allSymbolR
}
}
/// <summary>
/// Called when when we want to search a metadata reference. We create a dummy compilation
/// containing just that reference and we search that. That way we can get actual symbols
/// returned.
///
/// We don't want to use the project that the reference is actually associated with as
/// getting the compilation for that project may be extremely expensive. For example,
/// in a large solution it may cause us to build an enormous amount of skeleton assemblies.
/// </summary>
private Compilation CreateCompilation(Project project, PortableExecutableReference reference)
{
var compilationService = project.LanguageServices.GetService<ICompilationFactoryService>();
var compilation = compilationService.CreateCompilation("TempAssembly", compilationService.GetDefaultCompilationOptions());
return compilation.WithReferences(reference);
}
bool IEqualityComparer<PortableExecutableReference>.Equals(PortableExecutableReference x, PortableExecutableReference y)
{
return StringComparer.OrdinalIgnoreCase.Equals(
......@@ -378,16 +403,25 @@ private class SymbolReferenceFinder
_syntaxFacts = document.Project.LanguageServices.GetService<ISyntaxFactsService>();
}
internal Task<List<SymbolReference>> FindInProjectAsync(Project project, bool includeDirectReferences, bool exact)
internal Task<List<SymbolReference>> FindInAllProjectSymbolsAsync(
Project project, bool exact)
{
var searchScope = new AllSymbolsProjectSearchScope(project, exact, _cancellationToken);
return DoAsync(searchScope);
}
internal Task<List<SymbolReference>> FindInSourceProjectSymbolsAsync(
ConcurrentDictionary<Project, AsyncLazy<IAssemblySymbol>> projectToAssembly,
Project project, bool exact)
{
var searchScope = new ProjectSearchScope(project, includeDirectReferences, exact, _cancellationToken);
var searchScope = new SourceSymbolsProjectSearchScope(projectToAssembly, project, exact, _cancellationToken);
return DoAsync(searchScope);
}
internal Task<List<SymbolReference>> FindInMetadataAsync(
Solution solution, IAssemblySymbol assembly, PortableExecutableReference metadataReference, bool exact)
{
var searchScope = new MetadataSearchScope(solution, assembly, metadataReference, exact, _cancellationToken);
var searchScope = new MetadataSymbolsSearchScope(solution, assembly, metadataReference, exact, _cancellationToken);
return DoAsync(searchScope);
}
......
......@@ -256,6 +256,7 @@
<Compile Include="Shared\Options\ServiceComponentOnOffOptionsProvider.cs" />
<Compile Include="Shared\Options\ServiceFeatureOnOffOptions.cs" />
<Compile Include="Shared\Options\ServiceFeatureOnOffOptionsProvider.cs" />
<Compile Include="Shared\Utilities\IOUtilities.cs" />
<Compile Include="Shared\Utilities\LinkedFilesSymbolEquivalenceComparer.cs" />
<Compile Include="Shared\Utilities\SupportedPlatformData.cs" />
<Compile Include="Completion\Providers\SymbolCompletionItem.cs" />
......
// 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 System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.FindSymbols.SymbolTree;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.SolutionCrawler;
using Roslyn.Utilities;
using static Roslyn.Utilities.PortableShim;
namespace Microsoft.CodeAnalysis.IncrementalCaches
{
[ExportIncrementalAnalyzerProvider(WorkspaceKind.Host), Shared]
internal class SymbolTreeInfoIncrementalAnalyzerProvider : IIncrementalAnalyzerProvider
/// <summary>
/// Features like add-using want to be able to quickly search symbol indices for projects and
/// metadata. However, creating those indices can be expensive. As such, we don't want to
/// construct them during the add-using process itself. Instead, we expose this type as an
/// Incremental-Analyzer to walk our projects/metadata in the background to keep the indices
/// up to date.
///
/// We also then export this type as a service that can give back the index for a project or
/// metadata dll on request. If the index has been produced then it will be returned and
/// can be used by add-using. Otherwise, nothing is returned and no results will be found.
///
/// This means that as the project is being indexed, partial results may be returned. However
/// once it is fully indexed, then total results will be returned.
/// </summary>
[Shared]
[ExportIncrementalAnalyzerProvider(WorkspaceKind.Host)]
[ExportWorkspaceServiceFactory(typeof(ISymbolTreeInfoCacheService))]
internal class SymbolTreeInfoIncrementalAnalyzerProvider : IIncrementalAnalyzerProvider, IWorkspaceServiceFactory
{
private class ProjectInfo
{
public readonly VersionStamp VersionStamp;
public readonly SymbolTreeInfo SymbolTreeInfo;
public ProjectInfo(VersionStamp versionStamp, SymbolTreeInfo info)
{
VersionStamp = versionStamp;
SymbolTreeInfo = info;
}
}
private class MetadataInfo
{
public readonly DateTime TimeStamp;
public readonly SymbolTreeInfo SymbolTreeInfo;
/// <summary>
/// Note: the Incremental-Analyzer infrastructure guarantees that it will call all the methods
/// on <see cref="IncrementalAnalyzer"/> in a serial fashion. As that is the only type that
/// reads/writes these <see cref="MetadataInfo"/> objects, we don't need to lock this.
/// </summary>
public readonly HashSet<ProjectId> ReferencingProjects;
public MetadataInfo(DateTime timeStamp, SymbolTreeInfo info, HashSet<ProjectId> referencingProjects)
{
TimeStamp = timeStamp;
SymbolTreeInfo = info;
ReferencingProjects = referencingProjects;
}
}
// Concurrent dictionaries so they can be read from the SymbolTreeInfoCacheService while
// they are being populated/updated by the IncrementalAnalyzer.
private readonly ConcurrentDictionary<ProjectId, ProjectInfo> _projectToInfo = new ConcurrentDictionary<ProjectId, ProjectInfo>();
private readonly ConcurrentDictionary<string, MetadataInfo> _metadataPathToInfo = new ConcurrentDictionary<string, MetadataInfo>();
public IIncrementalAnalyzer CreateIncrementalAnalyzer(Workspace workspace)
{
return new IncrementalAnalyzer();
return new IncrementalAnalyzer(_projectToInfo, _metadataPathToInfo);
}
private class IncrementalAnalyzer : IncrementalAnalyzerBase
public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
{
private SolutionId _solutionId;
private Dictionary<ProjectId, int> _symbolCountByProjectMap = new Dictionary<ProjectId, int>();
return new SymbolTreeInfoCacheService(_projectToInfo, _metadataPathToInfo);
}
public override async Task AnalyzeProjectAsync(Project project, bool semanticsChanged, CancellationToken cancellationToken)
private static string GetReferenceKey(PortableExecutableReference reference)
{
return reference.FilePath ?? reference.Display;
}
private static bool TryGetLastWriteTime(string path, out DateTime time)
{
var succeeded = false;
time = IOUtilities.PerformIO(
() =>
{
var result = File.GetLastWriteTimeUtc(path);
succeeded = true;
return result;
},
default(DateTime));
return succeeded;
}
private class SymbolTreeInfoCacheService : ISymbolTreeInfoCacheService
{
private readonly ConcurrentDictionary<ProjectId, ProjectInfo> _projectToInfo;
private readonly ConcurrentDictionary<string, MetadataInfo> _metadataPathToInfo;
public SymbolTreeInfoCacheService(
ConcurrentDictionary<ProjectId, ProjectInfo> projectToInfo,
ConcurrentDictionary<string, MetadataInfo> metadataPathToInfo)
{
_projectToInfo = projectToInfo;
_metadataPathToInfo = metadataPathToInfo;
}
public async Task<SymbolTreeInfo> TryGetSymbolTreeInfoAsync(
Solution solution,
IAssemblySymbol assembly,
PortableExecutableReference reference,
CancellationToken cancellationToken)
{
if (_symbolCountByProjectMap == null || !project.SupportsCompilation || !semanticsChanged)
var key = GetReferenceKey(reference);
if (key != null)
{
return;
MetadataInfo metadataInfo;
if (_metadataPathToInfo.TryGetValue(key, out metadataInfo))
{
DateTime writeTime;
if (TryGetLastWriteTime(key, out writeTime) && writeTime == metadataInfo.TimeStamp)
{
return metadataInfo.SymbolTreeInfo;
}
}
}
// we do this just to report total symbol numbers
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
// If we didn't have it in our cache, see if we can load it from disk.
// Note: pass 'loadOnly' so we only attempt to load from disk, not to actually
// try to create the metadata.
var info = await SymbolTreeInfo.TryGetInfoForMetadataAssemblyAsync(
solution, assembly, reference, loadOnly: true, cancellationToken: cancellationToken).ConfigureAwait(false);
return info;
}
var info = SymbolTreeInfo.Create(VersionStamp.Default, compilation.Assembly, cancellationToken);
if (info != null)
public async Task<SymbolTreeInfo> TryGetSymbolTreeInfoAsync(
Project project, CancellationToken cancellationToken)
{
ProjectInfo projectInfo;
if (_projectToInfo.TryGetValue(project.Id, out projectInfo))
{
RecordCount(project.Id, info.Count);
var version = await project.GetSemanticVersionAsync(cancellationToken).ConfigureAwait(false);
if (version == projectInfo.VersionStamp)
{
return projectInfo.SymbolTreeInfo;
}
}
return null;
}
}
private class IncrementalAnalyzer : IncrementalAnalyzerBase
{
private readonly ConcurrentDictionary<ProjectId, ProjectInfo> _projectToInfo;
private readonly ConcurrentDictionary<string, MetadataInfo> _metadataPathToInfo;
private readonly Dictionary<ProjectId, Project> _idTolastSeenProject = new Dictionary<ProjectId, Project>();
public IncrementalAnalyzer(
ConcurrentDictionary<ProjectId, ProjectInfo> projectToInfo,
ConcurrentDictionary<string, MetadataInfo> metadataPathToInfo)
{
_projectToInfo = projectToInfo;
_metadataPathToInfo = metadataPathToInfo;
}
public override Task NewSolutionSnapshotAsync(Solution solution, CancellationToken cancellationToken)
public override Task AnalyzeDocumentAsync(Document document, SyntaxNode bodyOpt, CancellationToken cancellationToken)
{
// check whether we are good to report total symbol numbers
if (_symbolCountByProjectMap == null || _symbolCountByProjectMap.Count < solution.ProjectIds.Count || string.IsNullOrEmpty(solution.FilePath))
if (!document.SupportsSyntaxTree)
{
// Not a language we can produce indices for (i.e. TypeScript). Bail immediately.
return SpecializedTasks.EmptyTask;
}
if (_solutionId != null && _solutionId != solution.Id)
if (bodyOpt != null)
{
ReportCount();
// This was a method level edit. This can't change the symbol tree info
// for this project. Bail immediately.
return SpecializedTasks.EmptyTask;
}
_solutionId = solution.Id;
foreach (var projectId in solution.ProjectIds)
return UpdateSymbolTreeInfoAsync(document.Project, cancellationToken);
}
public override Task AnalyzeProjectAsync(Project project, bool semanticsChanged, CancellationToken cancellationToken)
{
return UpdateSymbolTreeInfoAsync(project, cancellationToken);
}
private async Task UpdateSymbolTreeInfoAsync(Project project, CancellationToken cancellationToken)
{
if (!project.SupportsCompilation)
{
if (!_symbolCountByProjectMap.ContainsKey(projectId))
{
return SpecializedTasks.EmptyTask;
}
return;
}
Project lastSeenProject;
if (_idTolastSeenProject.TryGetValue(project.Id, out lastSeenProject) && lastSeenProject == project)
{
// We already saw this project. No need to do anything;
return;
}
ReportCount();
return SpecializedTasks.EmptyTask;
// Check the semantic version of this project. The semantic version will change
// if any of the source files changed, or if the project version itself changed.
// (The latter happens when something happens to the project like metadata
// changing on disk).
var version = await project.GetSemanticVersionAsync(cancellationToken).ConfigureAwait(false);
ProjectInfo projectInfo;
if (!_projectToInfo.TryGetValue(project.Id, out projectInfo) || projectInfo.VersionStamp != version)
{
await UpdateReferencesAync(project, cancellationToken).ConfigureAwait(false);
var info = await SymbolTreeInfo.GetInfoForSourceAssemblyAsync(project, cancellationToken).ConfigureAwait(false);
projectInfo = new ProjectInfo(version, info);
_projectToInfo.AddOrUpdate(project.Id, projectInfo, (_1, _2) => projectInfo);
}
// Mark that we've completed processing this project.
_idTolastSeenProject[project.Id] = project;
}
public override void RemoveProject(ProjectId projectId)
private async Task UpdateReferencesAync(Project project, CancellationToken cancellationToken)
{
if (_symbolCountByProjectMap != null)
foreach (var reference in project.MetadataReferences.OfType<PortableExecutableReference>())
{
_symbolCountByProjectMap.Remove(projectId);
await UpdateReferenceAsync(project, reference, cancellationToken).ConfigureAwait(false);
}
}
private void ReportCount()
private async Task UpdateReferenceAsync(
Project project, PortableExecutableReference reference, CancellationToken cancellationToken)
{
var sourceSymbolCount = _symbolCountByProjectMap.Sum(kv => kv.Value);
Logger.Log(FunctionId.Run_Environment, KeyValueLogMessage.Create(m => m["SourceSymbolCount"] = sourceSymbolCount));
var key = GetReferenceKey(reference);
if (key == null)
{
return;
}
// we only report it once
_symbolCountByProjectMap = null;
_solutionId = null;
}
DateTime lastWriteTime;
if (!TryGetLastWriteTime(key, out lastWriteTime))
{
// Couldn't get the write time. Just ignore this reference.
return;
}
private void RecordCount(ProjectId id, int count)
{
if (_symbolCountByProjectMap == null)
MetadataInfo metadataInfo;
if (_metadataPathToInfo.TryGetValue(key, out metadataInfo) && metadataInfo.TimeStamp == lastWriteTime)
{
// We've already computed and cached the info for this reference.
return;
}
_symbolCountByProjectMap[id] = count;
var compilationService = project.LanguageServices.GetService<ICompilationFactoryService>();
var compilation = compilationService.CreateCompilation("TempAssembly", compilationService.GetDefaultCompilationOptions())
.WithReferences(reference);
var assembly = compilation.GetAssemblyOrModuleSymbol(reference) as IAssemblySymbol;
if (assembly == null)
{
return;
}
var info = await SymbolTreeInfo.TryGetInfoForMetadataAssemblyAsync(project.Solution, assembly, reference, loadOnly: false, cancellationToken: cancellationToken).ConfigureAwait(false);
metadataInfo = metadataInfo ?? new MetadataInfo(lastWriteTime, info, new HashSet<ProjectId>());
// Keep track that this dll is referenced by this project.
metadataInfo.ReferencingProjects.Add(project.Id);
_metadataPathToInfo.AddOrUpdate(key, metadataInfo, (_1, _2) => metadataInfo);
}
public override void RemoveProject(ProjectId projectId)
{
ProjectInfo info;
_projectToInfo.TryRemove(projectId, out info);
_idTolastSeenProject.Remove(projectId);
RemoveMetadataReferences(projectId);
}
private void RemoveMetadataReferences(ProjectId projectId)
{
foreach (var kvp in _metadataPathToInfo.ToArray())
{
if (kvp.Value.ReferencingProjects.Remove(projectId))
{
if (kvp.Value.ReferencingProjects.Count == 0)
{
// This metadata dll isn't referenced by any project. We can just dump it.
MetadataInfo unneeded;
_metadataPathToInfo.TryRemove(kvp.Key, out unneeded);
}
}
}
}
}
}
......
......@@ -4,7 +4,7 @@
using System.IO;
using System.Security;
namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.Completion.FileSystem
namespace Microsoft.CodeAnalysis.Shared.Utilities
{
internal static class IOUtilities
{
......
......@@ -7,6 +7,7 @@
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.Completion.FileSystem;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
......
......@@ -13,7 +13,7 @@ internal class AssemblySerializationInfoService : IAssemblySerializationInfoServ
{
public bool Serializable(Solution solution, string assemblyFilePath)
{
if (assemblyFilePath == null || !File.Exists(assemblyFilePath) || !ReferencePathUtilities.PartOfFrameworkOrReferencePaths(assemblyFilePath))
if (assemblyFilePath == null || !File.Exists(assemblyFilePath))
{
return false;
}
......
......@@ -126,13 +126,13 @@ public static Task<IEnumerable<ISymbol>> FindDeclarationsAsync(Project project,
return SpecializedTasks.EmptyEnumerable<ISymbol>();
}
return FindDeclarationsAsync(project, SearchQuery.Create(name, ignoreCase), includeDirectReferences: true, cancellationToken: cancellationToken);
return FindDeclarationsAsync(project, SearchQuery.Create(name, ignoreCase), cancellationToken: cancellationToken);
}
internal static Task<IEnumerable<ISymbol>> FindDeclarationsAsync(
Project project, SearchQuery query, bool includeDirectReferences, CancellationToken cancellationToken)
Project project, SearchQuery query, CancellationToken cancellationToken)
{
return FindDeclarationsAsync(project, query, SymbolFilter.All, includeDirectReferences, cancellationToken);
return FindDeclarationsAsync(project, query, SymbolFilter.All, cancellationToken);
}
/// <summary>
......@@ -151,11 +151,11 @@ public static Task<IEnumerable<ISymbol>> FindDeclarationsAsync(Project project,
return SpecializedTasks.EmptyEnumerable<ISymbol>();
}
return FindDeclarationsAsync(project, SearchQuery.Create(name, ignoreCase), filter, includeDirectReferences: true, cancellationToken: cancellationToken);
return FindDeclarationsAsync(project, SearchQuery.Create(name, ignoreCase), filter, cancellationToken: cancellationToken);
}
internal static Task<IEnumerable<ISymbol>> FindDeclarationsAsync(
Project project, SearchQuery query, SymbolFilter filter, bool includeDirectReferences, CancellationToken cancellationToken)
Project project, SearchQuery query, SymbolFilter filter, CancellationToken cancellationToken)
{
if (project == null)
{
......@@ -169,12 +169,12 @@ public static Task<IEnumerable<ISymbol>> FindDeclarationsAsync(Project project,
using (Logger.LogBlock(FunctionId.SymbolFinder_FindDeclarationsAsync, cancellationToken))
{
return FindDeclarationsAsyncImpl(project, query, filter, includeDirectReferences, cancellationToken);
return FindDeclarationsAsyncImpl(project, query, filter, cancellationToken);
}
}
private static async Task<IEnumerable<ISymbol>> FindDeclarationsAsyncImpl(
Project project, SearchQuery query, SymbolFilter criteria, bool includeDirectReferences, CancellationToken cancellationToken)
Project project, SearchQuery query, SymbolFilter criteria, CancellationToken cancellationToken)
{
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
......@@ -184,21 +184,18 @@ public static Task<IEnumerable<ISymbol>> FindDeclarationsAsync(Project project,
await AddDeclarationsAsync(project, query, criteria, list, cancellationToken).ConfigureAwait(false);
// get declarations from directly referenced projects and metadata
if (includeDirectReferences)
foreach (var assembly in compilation.GetReferencedAssemblySymbols())
{
foreach (var assembly in compilation.GetReferencedAssemblySymbols())
var assemblyProject = project.Solution.GetProject(assembly, cancellationToken);
if (assemblyProject != null)
{
var assemblyProject = project.Solution.GetProject(assembly, cancellationToken);
if (assemblyProject != null)
{
await AddDeclarationsAsync(assemblyProject, query, criteria, list, compilation, assembly, cancellationToken).ConfigureAwait(false);
}
else
{
await AddDeclarationsAsync(
project.Solution, assembly, compilation.GetMetadataReference(assembly) as PortableExecutableReference,
query, criteria, list, cancellationToken).ConfigureAwait(false);
}
await AddDeclarationsAsync(assemblyProject, query, criteria, list, compilation, assembly, cancellationToken).ConfigureAwait(false);
}
else
{
await AddDeclarationsAsync(
project.Solution, assembly, compilation.GetMetadataReference(assembly) as PortableExecutableReference,
query, criteria, list, cancellationToken).ConfigureAwait(false);
}
}
......@@ -291,35 +288,16 @@ private static IEnumerable<ISymbol> TranslateNamespaces(List<ISymbol> symbols, C
{
if (referenceOpt != null)
{
var info = await SymbolTreeInfo.TryGetInfoForAssemblyAsync(solution, assembly, referenceOpt, cancellationToken).ConfigureAwait(false);
var info = await SymbolTreeInfo.TryGetInfoForMetadataAssemblyAsync(solution, assembly, referenceOpt, loadOnly: false, cancellationToken: cancellationToken).ConfigureAwait(false);
if (info != null)
{
list.AddRange(FilterByCriteria(Find(query, info, assembly, cancellationToken), filter));
var symbols = await info.FindAsync(query, assembly, cancellationToken).ConfigureAwait(false);
list.AddRange(FilterByCriteria(symbols, filter));
}
}
}
}
private static IEnumerable<ISymbol> Find(SearchQuery query, SymbolTreeInfo info, IAssemblySymbol assembly, CancellationToken cancellationToken)
{
// If the query has a specific string provided, then call into the SymbolTreeInfo
// helpers optimized for lookup based on an exact name.
switch (query.Kind)
{
case SearchKind.Exact:
return info.Find(assembly, query.Name, ignoreCase: false, cancellationToken: cancellationToken);
case SearchKind.ExactIgnoreCase:
return info.Find(assembly, query.Name, ignoreCase: true, cancellationToken: cancellationToken);
case SearchKind.Fuzzy:
return info.FuzzyFind(assembly, query.Name, cancellationToken);
case SearchKind.Custom:
// Otherwise, we'll have to do a slow linear search over all possible symbols.
return info.Find(assembly, query.GetPredicate(), cancellationToken);
}
throw new InvalidOperationException();
}
/// <summary>
/// Find the symbols for declarations made in source with the specified name.
/// </summary>
......@@ -500,7 +478,7 @@ internal static async Task<IEnumerable<ISymbol>> FindSourceDeclarationsAsync(Pro
}
}
private static IEnumerable<ISymbol> FilterByCriteria(IEnumerable<ISymbol> symbols, SymbolFilter criteria)
internal static IEnumerable<ISymbol> FilterByCriteria(IEnumerable<ISymbol> symbols, SymbolFilter criteria)
{
foreach (var symbol in symbols)
{
......
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.FindSymbols.SymbolTree
{
internal interface ISymbolTreeInfoCacheService : IWorkspaceService
{
/// <summary>
/// Returns null if the info cannot be retrieved from the cache.
/// </summary>
Task<SymbolTreeInfo> TryGetSymbolTreeInfoAsync(Project project, CancellationToken cancellationToken);
/// <summary>
/// Returns null if the info cannot be retrieved from the cache.
/// </summary>
Task<SymbolTreeInfo> TryGetSymbolTreeInfoAsync(Solution solution, IAssemblySymbol assembly, PortableExecutableReference reference, CancellationToken cancellationToken);
}
}
\ No newline at end of file
......@@ -54,55 +54,92 @@ private SymbolTreeInfo(VersionStamp version, IReadOnlyList<Node> orderedNodes, S
public int Count => _nodes.Count;
public Task<IEnumerable<ISymbol>> FindAsync(SearchQuery query, IAssemblySymbol assembly, CancellationToken cancellationToken)
{
return FindAsync(query, new AsyncLazy<IAssemblySymbol>(assembly), cancellationToken);
}
public Task<IEnumerable<ISymbol>> FindAsync(SearchQuery query, AsyncLazy<IAssemblySymbol> lazyAssembly, CancellationToken cancellationToken)
{
// If the query has a specific string provided, then call into the SymbolTreeInfo
// helpers optimized for lookup based on an exact name.
switch (query.Kind)
{
case SearchKind.Exact:
return this.FindAsync(lazyAssembly, query.Name, ignoreCase: false, cancellationToken: cancellationToken);
case SearchKind.ExactIgnoreCase:
return this.FindAsync(lazyAssembly, query.Name, ignoreCase: true, cancellationToken: cancellationToken);
case SearchKind.Fuzzy:
return this.FuzzyFindAsync(lazyAssembly, query.Name, cancellationToken);
case SearchKind.Custom:
// Otherwise, we'll have to do a slow linear search over all possible symbols.
return this.FindAsync(lazyAssembly, query.GetPredicate(), cancellationToken);
}
throw new InvalidOperationException();
}
/// <summary>
/// Finds symbols in this assembly that match the provided name in a fuzzy manner.
/// </summary>
public IEnumerable<ISymbol> FuzzyFind(IAssemblySymbol assembly, string name, CancellationToken cancellationToken)
public async Task<IEnumerable<ISymbol>> FuzzyFindAsync(AsyncLazy<IAssemblySymbol> lazyAssembly, string name, CancellationToken cancellationToken)
{
var similarNames = _spellChecker.FindSimilarWords(name);
return similarNames.SelectMany(n => Find(assembly, n, ignoreCase: true, cancellationToken: cancellationToken));
var result = new List<ISymbol>();
foreach (var similarName in similarNames)
{
var symbols = await FindAsync(lazyAssembly, similarName, ignoreCase: true, cancellationToken: cancellationToken).ConfigureAwait(false);
result.AddRange(symbols);
}
return result;
}
/// <summary>
/// Get all symbols that have a name matching the specified name.
/// </summary>
public IEnumerable<ISymbol> Find(
IAssemblySymbol assembly,
public async Task<IEnumerable<ISymbol>> FindAsync(
AsyncLazy<IAssemblySymbol> lazyAssembly,
string name,
bool ignoreCase,
CancellationToken cancellationToken)
{
var comparer = GetComparer(ignoreCase);
var result = new List<ISymbol>();
IAssemblySymbol assemblySymbol = null;
foreach (var node in FindNodes(name, comparer))
{
cancellationToken.ThrowIfCancellationRequested();
foreach (var symbol in Bind(node, assembly.GlobalNamespace, cancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
yield return symbol;
}
assemblySymbol = assemblySymbol ?? await lazyAssembly.GetValueAsync(cancellationToken).ConfigureAwait(false);
result.AddRange(Bind(node, assemblySymbol.GlobalNamespace, cancellationToken));
}
return result;
}
/// <summary>
/// Slow, linear scan of all the symbols in this assembly to look for matches.
/// </summary>
public IEnumerable<ISymbol> Find(IAssemblySymbol assembly, Func<string, bool> predicate, CancellationToken cancellationToken)
public async Task<IEnumerable<ISymbol>> FindAsync(AsyncLazy<IAssemblySymbol> lazyAssembly, Func<string, bool> predicate, CancellationToken cancellationToken)
{
var result = new List<ISymbol>();
IAssemblySymbol assembly = null;
for (int i = 0, n = _nodes.Count; i < n; i++)
{
cancellationToken.ThrowIfCancellationRequested();
var node = _nodes[i];
if (predicate(node.Name))
{
foreach (var symbol in Bind(i, assembly.GlobalNamespace, cancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
yield return symbol;
}
assembly = assembly ?? await lazyAssembly.GetValueAsync(cancellationToken).ConfigureAwait(false);
result.AddRange(Bind(i, assembly.GlobalNamespace, cancellationToken));
}
}
return result;
}
private static StringComparer GetComparer(bool ignoreCase)
......@@ -199,7 +236,12 @@ private int BinarySearch(string name)
/// <summary>
/// this gives you SymbolTreeInfo for a metadata
/// </summary>
public static async Task<SymbolTreeInfo> TryGetInfoForAssemblyAsync(Solution solution, IAssemblySymbol assembly, PortableExecutableReference reference, CancellationToken cancellationToken)
public static async Task<SymbolTreeInfo> TryGetInfoForMetadataAssemblyAsync(
Solution solution,
IAssemblySymbol assembly,
PortableExecutableReference reference,
bool loadOnly,
CancellationToken cancellationToken)
{
var metadata = assembly.GetMetadata();
if (metadata == null)
......@@ -220,11 +262,25 @@ public static async Task<SymbolTreeInfo> TryGetInfoForAssemblyAsync(Solution sol
return info;
}
info = await LoadOrCreateAsync(solution, assembly, reference.FilePath, cancellationToken).ConfigureAwait(false);
info = await LoadOrCreateAsync(solution, assembly, reference.FilePath, loadOnly, cancellationToken).ConfigureAwait(false);
if (info == null && loadOnly)
{
return null;
}
return s_metadataIdToInfo.GetValue(metadata.Id, _ => info);
}
}
public static async Task<SymbolTreeInfo> GetInfoForSourceAssemblyAsync(
Project project, CancellationToken cancellationToken)
{
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
return await LoadOrCreateAsync(
project.Solution, compilation.Assembly, project.FilePath, loadOnly: false, cancellationToken: cancellationToken).ConfigureAwait(false);
}
internal static SymbolTreeInfo Create(VersionStamp version, IAssemblySymbol assembly, CancellationToken cancellationToken)
{
if (assembly == null)
......
......@@ -15,28 +15,52 @@ internal partial class SymbolTreeInfo : IObjectWritable
private const string PrefixMetadataSymbolTreeInfo = "<MetadataSymbolTreeInfoPersistence>_";
private const string SerializationFormat = "9";
/// <summary>
/// this is for a metadata reference in a solution
/// </summary>
private static async Task<SymbolTreeInfo> LoadOrCreateAsync(Solution solution, IAssemblySymbol assembly, string filePath, CancellationToken cancellationToken)
private static bool ShouldCreateFromScratch(
Solution solution,
IAssemblySymbol assembly,
string filePath,
out string prefix,
out VersionStamp version,
CancellationToken cancellationToken)
{
prefix = null;
version = default(VersionStamp);
var service = solution.Workspace.Services.GetService<IAssemblySerializationInfoService>();
if (service == null)
{
return Create(VersionStamp.Default, assembly, cancellationToken);
return true;
}
// check whether the assembly that belong to a solution is something we can serialize
if (!service.Serializable(solution, filePath))
{
return Create(VersionStamp.Default, assembly, cancellationToken);
return true;
}
if (!service.TryGetSerializationPrefixAndVersion(solution, filePath, out prefix, out version))
{
return true;
}
return false;
}
/// <summary>
/// this is for a metadata reference in a solution
/// </summary>
private static async Task<SymbolTreeInfo> LoadOrCreateAsync(
Solution solution,
IAssemblySymbol assembly,
string filePath,
bool loadOnly,
CancellationToken cancellationToken)
{
string prefix;
VersionStamp version;
if (!service.TryGetSerializationPrefixAndVersion(solution, filePath, out prefix, out version))
if (ShouldCreateFromScratch(solution, assembly, filePath, out prefix, out version, cancellationToken))
{
return Create(VersionStamp.Default, assembly, cancellationToken);
return loadOnly ? null : Create(VersionStamp.Default, assembly, cancellationToken);
}
var persistentStorageService = solution.Workspace.Services.GetService<IPersistentStorageService>();
......@@ -64,6 +88,11 @@ private static async Task<SymbolTreeInfo> LoadOrCreateAsync(Solution solution, I
cancellationToken.ThrowIfCancellationRequested();
if (loadOnly)
{
return null;
}
// compute it if we couldn't load it from cache
info = Create(version, assembly, cancellationToken);
if (info != null)
......
......@@ -261,7 +261,7 @@ public CompilationTracker FreezePartialStateWithTree(Solution solution, Document
if (metadata == null)
{
// if we failed to get the metadata, check to see if we previously had existing metadata and reuse it instead.
metadata = inProgressCompilation.References.FirstOrDefault(r => solution.GetProjectId(r) == projectReference.ProjectId);
metadata = inProgressCompilation.ExternalReferences.FirstOrDefault(r => solution.GetProjectId(r) == projectReference.ProjectId);
}
if (metadata != null)
......@@ -274,7 +274,7 @@ public CompilationTracker FreezePartialStateWithTree(Solution solution, Document
}
inProgressProject = inProgressProject.AddProjectReferences(newProjectReferences);
if (!Enumerable.SequenceEqual(inProgressCompilation.References, metadataReferences))
if (!Enumerable.SequenceEqual(inProgressCompilation.ExternalReferences, metadataReferences))
{
inProgressCompilation = inProgressCompilation.WithReferences(metadataReferences);
}
......@@ -645,7 +645,7 @@ public CompilationInfo(Compilation compilation, bool hasCompleteReferences)
}
}
if (!Enumerable.SequenceEqual(compilation.References, newReferences))
if (!Enumerable.SequenceEqual(compilation.ExternalReferences, newReferences))
{
compilation = compilation.WithReferences(newReferences);
}
......
......@@ -383,6 +383,7 @@
<Compile Include="ExtensionManager\IErrorReportingService.cs" />
<Compile Include="FindSymbols\DeclaredSymbolInfo.cs" />
<Compile Include="FindSymbols\FindReferences\Finders\ILanguageServiceReferenceFinder.cs" />
<Compile Include="FindSymbols\SymbolTree\ISymbolTreeInfoCacheService.cs" />
<Compile Include="Utilities\ArraySlice.cs" />
<Compile Include="Utilities\BKTree.cs" />
<Compile Include="FindSymbols\SyntaxTree\AbstractSyntaxTreeInfo.cs" />
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册