From 38b9f648c1eeb001d5a05c9af610c4613b40bbf4 Mon Sep 17 00:00:00 2001 From: tmat Date: Fri, 9 Apr 2021 17:33:14 -0700 Subject: [PATCH 1/7] Use compile-time solution for solution crawler and EnC --- .../SolutionCrawlerRegistrationService.cs | 4 +- .../WorkCoordinator.HighPriorityProcessor.cs | 2 +- ...oordinator.IncrementalAnalyzerProcessor.cs | 3 +- .../WorkCoordinator.LowPriorityProcessor.cs | 2 +- ...WorkCoordinator.NormalPriorityProcessor.cs | 11 ++- ...WorkCoordinator.SemanticChangeProcessor.cs | 4 +- .../SolutionCrawler/WorkCoordinator.cs | 14 +-- .../Workspace/CompileTimeSolutionProvider.cs | 96 +++++++++++++++++++ .../Workspace/ICompileTimeSolutionProvider.cs | 16 ++++ .../ManagedEditAndContinueLanguageService.cs | 16 ++-- 10 files changed, 144 insertions(+), 24 deletions(-) create mode 100644 src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs create mode 100644 src/Features/Core/Portable/Workspace/ICompileTimeSolutionProvider.cs diff --git a/src/Features/Core/Portable/SolutionCrawler/SolutionCrawlerRegistrationService.cs b/src/Features/Core/Portable/SolutionCrawler/SolutionCrawlerRegistrationService.cs index aa51f79e56732..c340a2bc283ec 100644 --- a/src/Features/Core/Portable/SolutionCrawler/SolutionCrawlerRegistrationService.cs +++ b/src/Features/Core/Portable/SolutionCrawler/SolutionCrawlerRegistrationService.cs @@ -9,6 +9,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Shared.TestHooks; @@ -306,7 +307,8 @@ public Registration(int correlationId, Workspace workspace, SolutionCrawlerProgr ProgressReporter = progressReporter; } - public Solution CurrentSolution => Workspace.CurrentSolution; + public Solution GetCurrentCompileTimeSolution() + => Workspace.Services.GetRequiredService().GetCurrentCompileTimeSolution(); } } } diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.HighPriorityProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.HighPriorityProcessor.cs index edcb55e313bd0..c90f76423952e 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.HighPriorityProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.HighPriorityProcessor.cs @@ -135,7 +135,7 @@ protected override async Task ExecuteAsync() // see whether we have work item for the document Contract.ThrowIfFalse(GetNextWorkItem(out var workItem, out var documentCancellation)); - var solution = _processor.CurrentSolution; + var solution = _processor._registration.GetCurrentCompileTimeSolution(); // okay now we have work to do await ProcessDocumentAsync(solution, Analyzers, workItem, documentCancellation).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs index a8380d1682143..ec55f309f5a52 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs @@ -204,8 +204,7 @@ public void Shutdown() public ImmutableArray Analyzers => _normalPriorityProcessor.Analyzers; - private Solution CurrentSolution => _registration.CurrentSolution; - private ProjectDependencyGraph DependencyGraph => CurrentSolution.GetProjectDependencyGraph(); + private ProjectDependencyGraph DependencyGraph => _registration.GetCurrentCompileTimeSolution().GetProjectDependencyGraph(); private IDiagnosticAnalyzerService? DiagnosticAnalyzerService => _lazyDiagnosticAnalyzerService?.Value; public Task AsyncProcessorTask diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.LowPriorityProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.LowPriorityProcessor.cs index 3c4eb2fca1e26..09e240967f6fe 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.LowPriorityProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.LowPriorityProcessor.cs @@ -126,7 +126,7 @@ private async Task ProcessProjectAsync(ImmutableArray anal // we do have work item for this project var projectId = workItem.ProjectId; var processedEverything = false; - var processingSolution = Processor.CurrentSolution; + var processingSolution = Processor._registration.GetCurrentCompileTimeSolution(); try { diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs index 6794c08832399..fe94dd505afb8 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs @@ -327,7 +327,7 @@ private async Task ProcessDocumentAsync(ImmutableArray ana // using later version of solution is always fine since, as long as there is new work item in the queue, // solution crawler will eventually call the last workitem with the lastest solution // making everything to catch up - var solution = Processor.CurrentSolution; + var solution = Processor._registration.GetCurrentCompileTimeSolution(); try { using (Logger.LogBlock(FunctionId.WorkCoordinator_ProcessDocumentAsync, w => w.ToString(), workItem, cancellationToken)) @@ -522,7 +522,10 @@ private async Task ResetStatesAsync() return; } - await Processor.RunAnalyzersAsync(Analyzers, Processor.CurrentSolution, workItem: new WorkItem(), (a, s, c) => a.NewSolutionSnapshotAsync(s, c), CancellationToken).ConfigureAwait(false); + await Processor.RunAnalyzersAsync( + Analyzers, + Processor._registration.GetCurrentCompileTimeSolution(), + workItem: new WorkItem(), (a, s, c) => a.NewSolutionSnapshotAsync(s, c), CancellationToken).ConfigureAwait(false); foreach (var id in Processor.GetOpenDocumentIds()) { @@ -538,7 +541,7 @@ private async Task ResetStatesAsync() bool IsSolutionChanged() { - var currentSolution = Processor.CurrentSolution; + var currentSolution = Processor._registration.GetCurrentCompileTimeSolution(); var oldSolution = _lastSolution; if (currentSolution == oldSolution) @@ -577,7 +580,7 @@ public override void Shutdown() { base.Shutdown(); - SolutionCrawlerLogger.LogIncrementalAnalyzerProcessorStatistics(Processor._registration.CorrelationId, Processor.CurrentSolution, Processor._logAggregator, Analyzers); + SolutionCrawlerLogger.LogIncrementalAnalyzerProcessorStatistics(Processor._registration.CorrelationId, Processor._registration.GetCurrentCompileTimeSolution(), Processor._logAggregator, Analyzers); _workItemQueue.Dispose(); diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.SemanticChangeProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.SemanticChangeProcessor.cs index 2940db02c61bb..62e24e1249fdb 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.SemanticChangeProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.SemanticChangeProcessor.cs @@ -417,7 +417,7 @@ protected override async Task ExecuteAsync() using (data.AsyncToken) { - var project = _registration.CurrentSolution.GetProject(data.ProjectId); + var project = _registration.GetCurrentCompileTimeSolution().GetProject(data.ProjectId); if (project == null) { return; @@ -430,7 +430,7 @@ protected override async Task ExecuteAsync() } // do dependency tracking here with current solution - var solution = _registration.CurrentSolution; + var solution = _registration.GetCurrentCompileTimeSolution(); foreach (var projectId in GetProjectsToAnalyze(solution, data.ProjectId)) { project = solution.GetProject(projectId); diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.cs index 670561dfe1c0c..40aa1a2531527 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.cs @@ -87,7 +87,7 @@ public WorkCoordinator( // subscribe to active document changed event for active file background analysis scope. if (_documentTrackingService != null) { - _lastActiveDocument = _documentTrackingService.GetActiveDocument(_registration.Workspace.CurrentSolution); + _lastActiveDocument = _documentTrackingService.GetActiveDocument(_registration.GetCurrentCompileTimeSolution()); _documentTrackingService.ActiveDocumentChanged += OnActiveDocumentChanged; } } @@ -100,7 +100,7 @@ public void AddAnalyzer(IIncrementalAnalyzer analyzer, bool highPriorityForActiv _documentAndProjectWorkerProcessor.AddAnalyzer(analyzer, highPriorityForActiveFile); // and ask to re-analyze whole solution for the given analyzer - var scope = new ReanalyzeScope(_registration.CurrentSolution.Id); + var scope = new ReanalyzeScope(_registration.GetCurrentCompileTimeSolution().Id); Reanalyze(analyzer, scope); } @@ -196,7 +196,7 @@ private void ReanalyzeOnOptionChange(object? sender, OptionChangedEventArgs e) { if (forceAnalyze || analyzer.NeedsReanalysisOnOptionChanged(sender, e)) { - var scope = new ReanalyzeScope(_registration.CurrentSolution.Id); + var scope = new ReanalyzeScope(_registration.GetCurrentCompileTimeSolution().Id); Reanalyze(analyzer, scope); } } @@ -212,7 +212,7 @@ public void Reanalyze(IIncrementalAnalyzer analyzer, ReanalyzeScope scope, bool { // log big reanalysis request from things like fix all, suppress all or option changes // we are not interested in 1 file re-analysis request which can happen from like venus typing - var solution = _registration.CurrentSolution; + var solution = _registration.GetCurrentCompileTimeSolution(); SolutionCrawlerLogger.LogReanalyze( CorrelationId, analyzer, scope.GetDocumentCount(solution), scope.GetLanguagesStringForTelemetry(solution), highPriority); } @@ -220,7 +220,7 @@ public void Reanalyze(IIncrementalAnalyzer analyzer, ReanalyzeScope scope, bool private void OnActiveDocumentChanged(object? sender, DocumentId activeDocumentId) { - var solution = _registration.Workspace.CurrentSolution; + var solution = _registration.GetCurrentCompileTimeSolution(); // Check if we are only performing backgroung analysis for active file. if (activeDocumentId != null) @@ -509,7 +509,7 @@ private async Task EnqueueWorkItemAsync(Project project, InvocationReasons invoc private async Task EnqueueWorkItemAsync(IIncrementalAnalyzer analyzer, ReanalyzeScope scope, bool highPriority) { - var solution = _registration.CurrentSolution; + var solution = _registration.GetCurrentCompileTimeSolution(); var invocationReasons = highPriority ? InvocationReasons.ReanalyzeHighPriority : InvocationReasons.Reanalyze; foreach (var document in scope.GetDocuments(solution)) @@ -683,7 +683,7 @@ internal TestAccessor(WorkCoordinator workCoordinator) internal void WaitUntilCompletion(ImmutableArray workers) { - var solution = _workCoordinator._registration.CurrentSolution; + var solution = _workCoordinator._registration.GetCurrentCompileTimeSolution(); var list = new List(); foreach (var project in solution.Projects) diff --git a/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs b/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs new file mode 100644 index 0000000000000..b79cddbef5551 --- /dev/null +++ b/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs @@ -0,0 +1,96 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices +{ + /// + /// Provides a compile-time view of the current workspace solution. + /// Workaround for Razor projects which generate both design-time and compile-time source files. + /// TODO: remove https://github.com/dotnet/roslyn/issues/51678 + /// + internal sealed class CompileTimeSolutionProvider : ICompileTimeSolutionProvider + { + [ExportWorkspaceServiceFactory(typeof(ICompileTimeSolutionProvider), WorkspaceKind.Host), Shared] + private sealed class Factory : IWorkspaceServiceFactory + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public Factory() + { + } + + [Obsolete(MefConstruction.FactoryMethodMessage, error: true)] + public IWorkspaceService? CreateService(HostWorkspaceServices workspaceServices) + => new CompileTimeSolutionProvider(workspaceServices.Workspace); + } + + private const string RazorEncConfigFileName = "RazorSourceGenerator.razorencconfig"; + + private readonly Workspace _workspace; + private readonly object _guard = new(); + + private Solution? _lazyCompileTimeSolution; + private int _correspondingDesignTimeSolutionVersion; + + public CompileTimeSolutionProvider(Workspace workspace) + { + _workspace = workspace; + } + + private static bool IsRazorAnalyzerConfig(TextDocumentState documentState) + => documentState.FilePath != null && documentState.FilePath.EndsWith(RazorEncConfigFileName, StringComparison.OrdinalIgnoreCase); + + public Solution GetCurrentCompileTimeSolution() + { + lock (_guard) + { + var currentDesignTimeSolution = _workspace.CurrentSolution; + + // Design time solution hasn't changed since we calculated the last compile-time solution: + if (currentDesignTimeSolution.WorkspaceVersion == _correspondingDesignTimeSolutionVersion) + { + Contract.ThrowIfNull(_lazyCompileTimeSolution); + return _lazyCompileTimeSolution; + } + + using var _1 = ArrayBuilder.GetInstance(out var configIdsToRemove); + using var _2 = ArrayBuilder.GetInstance(out var documentIdsToRemove); + + foreach (var (_, projectState) in currentDesignTimeSolution.State.ProjectStates) + { + foreach (var configState in projectState.AnalyzerConfigDocumentStates.States) + { + if (IsRazorAnalyzerConfig(configState)) + { + configIdsToRemove.Add(configState.Id); + } + } + + foreach (var documentState in projectState.DocumentStates.States) + { + if (documentState.Attributes.DesignTimeOnly) + { + documentIdsToRemove.Add(documentState.Id); + } + } + } + + _lazyCompileTimeSolution = currentDesignTimeSolution + .RemoveAnalyzerConfigDocuments(configIdsToRemove.ToImmutable()) + .RemoveDocuments(documentIdsToRemove.ToImmutable()); + + _correspondingDesignTimeSolutionVersion = currentDesignTimeSolution.WorkspaceVersion; + return _lazyCompileTimeSolution; + } + } + } +} diff --git a/src/Features/Core/Portable/Workspace/ICompileTimeSolutionProvider.cs b/src/Features/Core/Portable/Workspace/ICompileTimeSolutionProvider.cs new file mode 100644 index 0000000000000..8f70c0af00a45 --- /dev/null +++ b/src/Features/Core/Portable/Workspace/ICompileTimeSolutionProvider.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis.Host +{ + /// + /// Provides a compile-time view of the current workspace solution. + /// Workaround for Razor projects which generate both design-time and compile-time source files. + /// TODO: remove https://github.com/dotnet/roslyn/issues/51678 + /// + internal interface ICompileTimeSolutionProvider : IWorkspaceService + { + Solution GetCurrentCompileTimeSolution(); + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedEditAndContinueLanguageService.cs b/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedEditAndContinueLanguageService.cs index 23438f00e9fbc..4eea70c01554d 100644 --- a/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedEditAndContinueLanguageService.cs +++ b/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedEditAndContinueLanguageService.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.Editor.Implementation.EditAndContinue; using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; @@ -52,6 +53,9 @@ public ManagedEditAndContinueLanguageService( _diagnosticUpdateSource = diagnosticUpdateSource; } + private Solution GetCurrentCompileTimeSolution() + => _proxy.Workspace.Services.GetRequiredService().GetCurrentCompileTimeSolution(); + /// /// Called by the debugger when a debugging session starts and managed debugging is being used. /// @@ -67,7 +71,7 @@ public async Task StartDebuggingAsync(DebugSessionFlags flags, CancellationToken try { - var solution = _proxy.Workspace.CurrentSolution; + var solution = GetCurrentCompileTimeSolution(); _debuggingSessionConnection = await _proxy.StartDebuggingSessionAsync(solution, _debuggerService, captureMatchingDocuments: false, cancellationToken).ConfigureAwait(false); } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) @@ -85,7 +89,7 @@ public async Task EnterBreakStateAsync(CancellationToken cancellationToken) return; } - var solution = _proxy.Workspace.CurrentSolution; + var solution = GetCurrentCompileTimeSolution(); try { @@ -175,7 +179,7 @@ public async Task HasChangesAsync(string? sourceFilePath, CancellationToke { try { - var solution = _proxy.Workspace.CurrentSolution; + var solution = GetCurrentCompileTimeSolution(); var activeStatementSpanProvider = GetActiveStatementSpanProvider(solution); return await _proxy.HasChangesAsync(solution, activeStatementSpanProvider, sourceFilePath, cancellationToken).ConfigureAwait(false); } @@ -189,7 +193,7 @@ public async Task GetManagedModuleUpdatesAsync(Cancellatio { try { - var solution = _proxy.Workspace.CurrentSolution; + var solution = GetCurrentCompileTimeSolution(); var activeStatementSpanProvider = GetActiveStatementSpanProvider(solution); return await _proxy.EmitSolutionUpdateAsync(solution, activeStatementSpanProvider, _diagnosticService, _diagnosticUpdateSource, cancellationToken).ConfigureAwait(false); } @@ -203,7 +207,7 @@ public async Task GetManagedModuleUpdatesAsync(Cancellatio { try { - var solution = _proxy.Workspace.CurrentSolution; + var solution = GetCurrentCompileTimeSolution(); var activeStatementSpanProvider = new SolutionActiveStatementSpanProvider(async (documentId, cancellationToken) => { @@ -224,7 +228,7 @@ public async Task GetManagedModuleUpdatesAsync(Cancellatio { try { - var solution = _proxy.Workspace.CurrentSolution; + var solution = GetCurrentCompileTimeSolution(); return await _proxy.IsActiveStatementInExceptionRegionAsync(solution, instruction, cancellationToken).ConfigureAwait(false); } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) From d9262aee13b6ce4a6852f3399089aecb63352a66 Mon Sep 17 00:00:00 2001 From: tmat Date: Mon, 12 Apr 2021 12:21:53 -0700 Subject: [PATCH 2/7] Fix --- .../Core/Portable/Workspace/CompileTimeSolutionProvider.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs b/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs index b79cddbef5551..a568d3c6ca070 100644 --- a/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs +++ b/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs @@ -44,6 +44,7 @@ public Factory() public CompileTimeSolutionProvider(Workspace workspace) { _workspace = workspace; + _correspondingDesignTimeSolutionVersion = -1; } private static bool IsRazorAnalyzerConfig(TextDocumentState documentState) From d2a419ac14fba0f39cf5922919cbc7e8408556e3 Mon Sep 17 00:00:00 2001 From: tmat Date: Mon, 12 Apr 2021 15:03:46 -0700 Subject: [PATCH 3/7] Only remove design-time docs when enc config file is present --- .../Workspace/CompileTimeSolutionProvider.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs b/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs index a568d3c6ca070..f5a8ebc9e1852 100644 --- a/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs +++ b/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs @@ -68,19 +68,26 @@ public Solution GetCurrentCompileTimeSolution() foreach (var (_, projectState) in currentDesignTimeSolution.State.ProjectStates) { + var anyConfigs = false; + foreach (var configState in projectState.AnalyzerConfigDocumentStates.States) { if (IsRazorAnalyzerConfig(configState)) { configIdsToRemove.Add(configState.Id); + anyConfigs = true; } } - foreach (var documentState in projectState.DocumentStates.States) + // only remove design-time only documents when source-generated ones replace them + if (anyConfigs) { - if (documentState.Attributes.DesignTimeOnly) + foreach (var documentState in projectState.DocumentStates.States) { - documentIdsToRemove.Add(documentState.Id); + if (documentState.Attributes.DesignTimeOnly) + { + documentIdsToRemove.Add(documentState.Id); + } } } } From 7174fe5f40bf7872b80542c45ea286e0b2e60209 Mon Sep 17 00:00:00 2001 From: tmat Date: Mon, 12 Apr 2021 16:29:52 -0700 Subject: [PATCH 4/7] Only enable when Razor LSP editor is enabled --- .../Portable/Workspace/CompileTimeSolutionProvider.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs b/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs index f5a8ebc9e1852..4b5654e24d2fb 100644 --- a/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs +++ b/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs @@ -5,6 +5,7 @@ using System; using System.Composition; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Experiments; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; @@ -40,11 +41,13 @@ public Factory() private Solution? _lazyCompileTimeSolution; private int _correspondingDesignTimeSolutionVersion; + private readonly bool _enabled; public CompileTimeSolutionProvider(Workspace workspace) { _workspace = workspace; _correspondingDesignTimeSolutionVersion = -1; + _enabled = workspace.Services.GetRequiredService().IsExperimentEnabled(WellKnownExperimentNames.RazorLspEditorFeatureFlag) == true; } private static bool IsRazorAnalyzerConfig(TextDocumentState documentState) @@ -52,6 +55,11 @@ private static bool IsRazorAnalyzerConfig(TextDocumentState documentState) public Solution GetCurrentCompileTimeSolution() { + if (!_enabled) + { + return _workspace.CurrentSolution; + } + lock (_guard) { var currentDesignTimeSolution = _workspace.CurrentSolution; From c3d5f3f61f7671f69a03c55182f63f837f675a3d Mon Sep 17 00:00:00 2001 From: tmat Date: Mon, 12 Apr 2021 16:31:36 -0700 Subject: [PATCH 5/7] Missing commit --- .../Core/Portable/Experiments/IExperimentationService.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Workspaces/Core/Portable/Experiments/IExperimentationService.cs b/src/Workspaces/Core/Portable/Experiments/IExperimentationService.cs index bd8a5eff7d1d8..b4a32ecc1d2d4 100644 --- a/src/Workspaces/Core/Portable/Experiments/IExperimentationService.cs +++ b/src/Workspaces/Core/Portable/Experiments/IExperimentationService.cs @@ -38,5 +38,6 @@ internal static class WellKnownExperimentNames public const string RemoveUnusedReferences = "Roslyn.RemoveUnusedReferences"; public const string LSPCompletion = "Roslyn.LSP.Completion"; public const string UnnamedSymbolCompletionDisabled = "Roslyn.UnnamedSymbolCompletionDisabled"; + public const string RazorLspEditorFeatureFlag = "Razor.LSP.Editor"; } } From cfef4e4f0aac5a3e86d0e76023f8a951eaeba603 Mon Sep 17 00:00:00 2001 From: tmat Date: Mon, 12 Apr 2021 16:39:17 -0700 Subject: [PATCH 6/7] Cleanup --- .../Core/Portable/Workspace/CompileTimeSolutionProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs b/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs index 4b5654e24d2fb..6421683f20cc0 100644 --- a/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs +++ b/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs @@ -47,7 +47,7 @@ public CompileTimeSolutionProvider(Workspace workspace) { _workspace = workspace; _correspondingDesignTimeSolutionVersion = -1; - _enabled = workspace.Services.GetRequiredService().IsExperimentEnabled(WellKnownExperimentNames.RazorLspEditorFeatureFlag) == true; + _enabled = workspace.Services.GetRequiredService().IsExperimentEnabled(WellKnownExperimentNames.RazorLspEditorFeatureFlag); } private static bool IsRazorAnalyzerConfig(TextDocumentState documentState) From f7320e8d43e423b06162f7112d3c4a67557a468c Mon Sep 17 00:00:00 2001 From: tmat Date: Mon, 19 Apr 2021 10:01:25 -0700 Subject: [PATCH 7/7] Feedback --- .../SolutionCrawlerRegistrationService.cs | 2 +- .../WorkCoordinator.HighPriorityProcessor.cs | 2 +- ...oordinator.IncrementalAnalyzerProcessor.cs | 2 +- .../WorkCoordinator.LowPriorityProcessor.cs | 2 +- ...WorkCoordinator.NormalPriorityProcessor.cs | 8 +++---- ...WorkCoordinator.SemanticChangeProcessor.cs | 4 ++-- .../SolutionCrawler/WorkCoordinator.cs | 14 ++++++------- .../Workspace/CompileTimeSolutionProvider.cs | 21 +++++++++++++++---- 8 files changed, 34 insertions(+), 21 deletions(-) diff --git a/src/Features/Core/Portable/SolutionCrawler/SolutionCrawlerRegistrationService.cs b/src/Features/Core/Portable/SolutionCrawler/SolutionCrawlerRegistrationService.cs index c340a2bc283ec..a976f6b0d22f8 100644 --- a/src/Features/Core/Portable/SolutionCrawler/SolutionCrawlerRegistrationService.cs +++ b/src/Features/Core/Portable/SolutionCrawler/SolutionCrawlerRegistrationService.cs @@ -307,7 +307,7 @@ public Registration(int correlationId, Workspace workspace, SolutionCrawlerProgr ProgressReporter = progressReporter; } - public Solution GetCurrentCompileTimeSolution() + public Solution GetSolutionToAnalyze() => Workspace.Services.GetRequiredService().GetCurrentCompileTimeSolution(); } } diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.HighPriorityProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.HighPriorityProcessor.cs index c90f76423952e..78fc49ca54d23 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.HighPriorityProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.HighPriorityProcessor.cs @@ -135,7 +135,7 @@ protected override async Task ExecuteAsync() // see whether we have work item for the document Contract.ThrowIfFalse(GetNextWorkItem(out var workItem, out var documentCancellation)); - var solution = _processor._registration.GetCurrentCompileTimeSolution(); + var solution = _processor._registration.GetSolutionToAnalyze(); // okay now we have work to do await ProcessDocumentAsync(solution, Analyzers, workItem, documentCancellation).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs index ec55f309f5a52..fd82c0bbab9b8 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs @@ -204,7 +204,7 @@ public void Shutdown() public ImmutableArray Analyzers => _normalPriorityProcessor.Analyzers; - private ProjectDependencyGraph DependencyGraph => _registration.GetCurrentCompileTimeSolution().GetProjectDependencyGraph(); + private ProjectDependencyGraph DependencyGraph => _registration.GetSolutionToAnalyze().GetProjectDependencyGraph(); private IDiagnosticAnalyzerService? DiagnosticAnalyzerService => _lazyDiagnosticAnalyzerService?.Value; public Task AsyncProcessorTask diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.LowPriorityProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.LowPriorityProcessor.cs index 09e240967f6fe..4d85e49dd4577 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.LowPriorityProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.LowPriorityProcessor.cs @@ -126,7 +126,7 @@ private async Task ProcessProjectAsync(ImmutableArray anal // we do have work item for this project var projectId = workItem.ProjectId; var processedEverything = false; - var processingSolution = Processor._registration.GetCurrentCompileTimeSolution(); + var processingSolution = Processor._registration.GetSolutionToAnalyze(); try { diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs index fe94dd505afb8..ca88e5392bb8d 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs @@ -327,7 +327,7 @@ private async Task ProcessDocumentAsync(ImmutableArray ana // using later version of solution is always fine since, as long as there is new work item in the queue, // solution crawler will eventually call the last workitem with the lastest solution // making everything to catch up - var solution = Processor._registration.GetCurrentCompileTimeSolution(); + var solution = Processor._registration.GetSolutionToAnalyze(); try { using (Logger.LogBlock(FunctionId.WorkCoordinator_ProcessDocumentAsync, w => w.ToString(), workItem, cancellationToken)) @@ -524,7 +524,7 @@ private async Task ResetStatesAsync() await Processor.RunAnalyzersAsync( Analyzers, - Processor._registration.GetCurrentCompileTimeSolution(), + Processor._registration.GetSolutionToAnalyze(), workItem: new WorkItem(), (a, s, c) => a.NewSolutionSnapshotAsync(s, c), CancellationToken).ConfigureAwait(false); foreach (var id in Processor.GetOpenDocumentIds()) @@ -541,7 +541,7 @@ await Processor.RunAnalyzersAsync( bool IsSolutionChanged() { - var currentSolution = Processor._registration.GetCurrentCompileTimeSolution(); + var currentSolution = Processor._registration.GetSolutionToAnalyze(); var oldSolution = _lastSolution; if (currentSolution == oldSolution) @@ -580,7 +580,7 @@ public override void Shutdown() { base.Shutdown(); - SolutionCrawlerLogger.LogIncrementalAnalyzerProcessorStatistics(Processor._registration.CorrelationId, Processor._registration.GetCurrentCompileTimeSolution(), Processor._logAggregator, Analyzers); + SolutionCrawlerLogger.LogIncrementalAnalyzerProcessorStatistics(Processor._registration.CorrelationId, Processor._registration.GetSolutionToAnalyze(), Processor._logAggregator, Analyzers); _workItemQueue.Dispose(); diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.SemanticChangeProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.SemanticChangeProcessor.cs index 62e24e1249fdb..cc1ab544caf6e 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.SemanticChangeProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.SemanticChangeProcessor.cs @@ -417,7 +417,7 @@ protected override async Task ExecuteAsync() using (data.AsyncToken) { - var project = _registration.GetCurrentCompileTimeSolution().GetProject(data.ProjectId); + var project = _registration.GetSolutionToAnalyze().GetProject(data.ProjectId); if (project == null) { return; @@ -430,7 +430,7 @@ protected override async Task ExecuteAsync() } // do dependency tracking here with current solution - var solution = _registration.GetCurrentCompileTimeSolution(); + var solution = _registration.GetSolutionToAnalyze(); foreach (var projectId in GetProjectsToAnalyze(solution, data.ProjectId)) { project = solution.GetProject(projectId); diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.cs index 40aa1a2531527..cb37a85992512 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.cs @@ -87,7 +87,7 @@ public WorkCoordinator( // subscribe to active document changed event for active file background analysis scope. if (_documentTrackingService != null) { - _lastActiveDocument = _documentTrackingService.GetActiveDocument(_registration.GetCurrentCompileTimeSolution()); + _lastActiveDocument = _documentTrackingService.GetActiveDocument(_registration.GetSolutionToAnalyze()); _documentTrackingService.ActiveDocumentChanged += OnActiveDocumentChanged; } } @@ -100,7 +100,7 @@ public void AddAnalyzer(IIncrementalAnalyzer analyzer, bool highPriorityForActiv _documentAndProjectWorkerProcessor.AddAnalyzer(analyzer, highPriorityForActiveFile); // and ask to re-analyze whole solution for the given analyzer - var scope = new ReanalyzeScope(_registration.GetCurrentCompileTimeSolution().Id); + var scope = new ReanalyzeScope(_registration.GetSolutionToAnalyze().Id); Reanalyze(analyzer, scope); } @@ -196,7 +196,7 @@ private void ReanalyzeOnOptionChange(object? sender, OptionChangedEventArgs e) { if (forceAnalyze || analyzer.NeedsReanalysisOnOptionChanged(sender, e)) { - var scope = new ReanalyzeScope(_registration.GetCurrentCompileTimeSolution().Id); + var scope = new ReanalyzeScope(_registration.GetSolutionToAnalyze().Id); Reanalyze(analyzer, scope); } } @@ -212,7 +212,7 @@ public void Reanalyze(IIncrementalAnalyzer analyzer, ReanalyzeScope scope, bool { // log big reanalysis request from things like fix all, suppress all or option changes // we are not interested in 1 file re-analysis request which can happen from like venus typing - var solution = _registration.GetCurrentCompileTimeSolution(); + var solution = _registration.GetSolutionToAnalyze(); SolutionCrawlerLogger.LogReanalyze( CorrelationId, analyzer, scope.GetDocumentCount(solution), scope.GetLanguagesStringForTelemetry(solution), highPriority); } @@ -220,7 +220,7 @@ public void Reanalyze(IIncrementalAnalyzer analyzer, ReanalyzeScope scope, bool private void OnActiveDocumentChanged(object? sender, DocumentId activeDocumentId) { - var solution = _registration.GetCurrentCompileTimeSolution(); + var solution = _registration.GetSolutionToAnalyze(); // Check if we are only performing backgroung analysis for active file. if (activeDocumentId != null) @@ -509,7 +509,7 @@ private async Task EnqueueWorkItemAsync(Project project, InvocationReasons invoc private async Task EnqueueWorkItemAsync(IIncrementalAnalyzer analyzer, ReanalyzeScope scope, bool highPriority) { - var solution = _registration.GetCurrentCompileTimeSolution(); + var solution = _registration.GetSolutionToAnalyze(); var invocationReasons = highPriority ? InvocationReasons.ReanalyzeHighPriority : InvocationReasons.Reanalyze; foreach (var document in scope.GetDocuments(solution)) @@ -683,7 +683,7 @@ internal TestAccessor(WorkCoordinator workCoordinator) internal void WaitUntilCompletion(ImmutableArray workers) { - var solution = _workCoordinator._registration.GetCurrentCompileTimeSolution(); + var solution = _workCoordinator._registration.GetSolutionToAnalyze(); var list = new List(); foreach (var project in solution.Projects) diff --git a/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs b/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs index 6421683f20cc0..387a15328a824 100644 --- a/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs +++ b/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs @@ -37,17 +37,28 @@ public Factory() private const string RazorEncConfigFileName = "RazorSourceGenerator.razorencconfig"; private readonly Workspace _workspace; - private readonly object _guard = new(); + private readonly object _gate = new(); private Solution? _lazyCompileTimeSolution; - private int _correspondingDesignTimeSolutionVersion; + private int? _correspondingDesignTimeSolutionVersion; private readonly bool _enabled; public CompileTimeSolutionProvider(Workspace workspace) { _workspace = workspace; - _correspondingDesignTimeSolutionVersion = -1; _enabled = workspace.Services.GetRequiredService().IsExperimentEnabled(WellKnownExperimentNames.RazorLspEditorFeatureFlag); + + workspace.WorkspaceChanged += (s, e) => + { + if (e.Kind is WorkspaceChangeKind.SolutionCleared or WorkspaceChangeKind.SolutionRemoved) + { + lock (_gate) + { + _lazyCompileTimeSolution = null; + _correspondingDesignTimeSolutionVersion = null; + } + } + }; } private static bool IsRazorAnalyzerConfig(TextDocumentState documentState) @@ -60,7 +71,7 @@ public Solution GetCurrentCompileTimeSolution() return _workspace.CurrentSolution; } - lock (_guard) + lock (_gate) { var currentDesignTimeSolution = _workspace.CurrentSolution; @@ -74,6 +85,8 @@ public Solution GetCurrentCompileTimeSolution() using var _1 = ArrayBuilder.GetInstance(out var configIdsToRemove); using var _2 = ArrayBuilder.GetInstance(out var documentIdsToRemove); + var compileTimeSolution = currentDesignTimeSolution; + foreach (var (_, projectState) in currentDesignTimeSolution.State.ProjectStates) { var anyConfigs = false;