From e7ac5bacfa6066906adeb583d3e0d56be8bf0f35 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Thu, 17 Jun 2021 17:40:42 -0700 Subject: [PATCH] Optimize calculation of remote supported languages Fixes #53769 --- .../Workspace/Solution/SolutionState.cs | 60 ++++++++++++++++++- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs index fcd5eab374c23..88fea62b1620c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs @@ -41,6 +41,7 @@ internal partial class SolutionState private readonly SolutionInfo.SolutionAttributes _solutionAttributes; private readonly SolutionServices _solutionServices; private readonly ImmutableDictionary _projectIdToProjectStateMap; + private readonly ImmutableHashSet _remoteSupportedLanguages; private readonly ImmutableDictionary> _filePathToDocumentIdsMap; private readonly ProjectDependencyGraph _dependencyGraph; @@ -74,6 +75,7 @@ private SolutionState( SerializableOptionSet options, IReadOnlyList analyzerReferences, ImmutableDictionary idToProjectStateMap, + ImmutableHashSet remoteSupportedLanguages, ImmutableDictionary projectIdToTrackerMap, ImmutableDictionary> filePathToDocumentIdsMap, ProjectDependencyGraph dependencyGraph, @@ -88,6 +90,7 @@ private SolutionState( Options = options; AnalyzerReferences = analyzerReferences; _projectIdToProjectStateMap = idToProjectStateMap; + _remoteSupportedLanguages = remoteSupportedLanguages; _projectIdToTrackerMap = projectIdToTrackerMap; _filePathToDocumentIdsMap = filePathToDocumentIdsMap; _dependencyGraph = dependencyGraph; @@ -119,6 +122,7 @@ public SolutionState( options, analyzerReferences, idToProjectStateMap: ImmutableDictionary.Empty, + remoteSupportedLanguages: ImmutableHashSet.Empty, projectIdToTrackerMap: ImmutableDictionary.Empty, filePathToDocumentIdsMap: ImmutableDictionary.Create>(StringComparer.OrdinalIgnoreCase), dependencyGraph: ProjectDependencyGraph.Empty, @@ -203,6 +207,8 @@ private void CheckInvariants() // project ids must be the same: Debug.Assert(_projectIdToProjectStateMap.Keys.SetEquals(ProjectIds)); Debug.Assert(_projectIdToProjectStateMap.Keys.SetEquals(_dependencyGraph.ProjectIds)); + + Debug.Assert(_remoteSupportedLanguages.SetEquals(GetRemoteSupportedProjectLanguages(_projectIdToProjectStateMap))); } private SolutionState Branch( @@ -211,6 +217,7 @@ private SolutionState Branch( SerializableOptionSet? options = null, IReadOnlyList? analyzerReferences = null, ImmutableDictionary? idToProjectStateMap = null, + ImmutableHashSet? remoteSupportedProjectLanguages = null, ImmutableDictionary? projectIdToTrackerMap = null, ImmutableDictionary>? filePathToDocumentIdsMap = null, ProjectDependencyGraph? dependencyGraph = null, @@ -218,10 +225,17 @@ private SolutionState Branch( { var branchId = GetBranchId(); + if (idToProjectStateMap is not null) + { + Contract.ThrowIfNull(remoteSupportedProjectLanguages); + } + solutionAttributes ??= _solutionAttributes; projectIds ??= ProjectIds; idToProjectStateMap ??= _projectIdToProjectStateMap; - options ??= Options.WithLanguages(GetRemoteSupportedProjectLanguages(idToProjectStateMap)); + remoteSupportedProjectLanguages ??= _remoteSupportedLanguages; + Debug.Assert(remoteSupportedProjectLanguages.SetEquals(GetRemoteSupportedProjectLanguages(idToProjectStateMap))); + options ??= Options.WithLanguages(remoteSupportedProjectLanguages); analyzerReferences ??= AnalyzerReferences; projectIdToTrackerMap ??= _projectIdToTrackerMap; filePathToDocumentIdsMap ??= _filePathToDocumentIdsMap; @@ -253,6 +267,7 @@ private SolutionState Branch( options, analyzerReferences, idToProjectStateMap, + remoteSupportedProjectLanguages, projectIdToTrackerMap, filePathToDocumentIdsMap, dependencyGraph, @@ -281,6 +296,7 @@ private SolutionState CreatePrimarySolution( Options, AnalyzerReferences, _projectIdToProjectStateMap, + _remoteSupportedLanguages, _projectIdToTrackerMap, _filePathToDocumentIdsMap, _dependencyGraph, @@ -461,6 +477,9 @@ private SolutionState AddProject(ProjectId projectId, ProjectState projectState) var newProjectIds = ProjectIds.ToImmutableArray().Add(projectId); var newStateMap = _projectIdToProjectStateMap.Add(projectId, projectState); + var newLanguages = RemoteSupportedLanguages.IsSupported(projectState.Language) + ? _remoteSupportedLanguages.Add(projectState.Language) + : _remoteSupportedLanguages; var newDependencyGraph = _dependencyGraph .WithAdditionalProject(projectId) @@ -490,6 +509,7 @@ private SolutionState AddProject(ProjectId projectId, ProjectState projectState) solutionAttributes: newSolutionAttributes, projectIds: newProjectIds, idToProjectStateMap: newStateMap, + remoteSupportedProjectLanguages: newLanguages, projectIdToTrackerMap: newTrackerMap, filePathToDocumentIdsMap: newFilePathToDocumentIdsMap, dependencyGraph: newDependencyGraph); @@ -573,6 +593,31 @@ public SolutionState RemoveProject(ProjectId projectId) var newProjectIds = ProjectIds.ToImmutableArray().Remove(projectId); var newStateMap = _projectIdToProjectStateMap.Remove(projectId); + + // Remote supported languages only changes if the removed project is the last project of a supported language. + var newLanguages = _remoteSupportedLanguages; + if (_projectIdToProjectStateMap.TryGetValue(projectId, out var projectState) + && RemoteSupportedLanguages.IsSupported(projectState.Language)) + { + var stillSupportsLanguage = false; + foreach (var (id, state) in _projectIdToProjectStateMap) + { + if (id == projectId) + continue; + + if (state.Language == projectState.Language) + { + stillSupportsLanguage = true; + break; + } + } + + if (!stillSupportsLanguage) + { + newLanguages = newLanguages.Remove(projectState.Language); + } + } + var newDependencyGraph = _dependencyGraph.WithProjectRemoved(projectId); var newTrackerMap = CreateCompilationTrackerMap(projectId, newDependencyGraph); var newFilePathToDocumentIdsMap = CreateFilePathToDocumentIdsMapWithRemovedDocuments(GetDocumentStates(_projectIdToProjectStateMap[projectId])); @@ -581,6 +626,7 @@ public SolutionState RemoveProject(ProjectId projectId) solutionAttributes: newSolutionAttributes, projectIds: newProjectIds, idToProjectStateMap: newStateMap, + remoteSupportedProjectLanguages: newLanguages, projectIdToTrackerMap: newTrackerMap.Remove(projectId), filePathToDocumentIdsMap: newFilePathToDocumentIdsMap, dependencyGraph: newDependencyGraph); @@ -1468,6 +1514,13 @@ private SolutionState ForkProject( var projectId = newProjectState.Id; var newStateMap = _projectIdToProjectStateMap.SetItem(projectId, newProjectState); + + // Remote supported languages can only change if the project changes language. This is an unexpected edge + // case, so it's not optimized for incremental updates. + var newLanguages = !_projectIdToProjectStateMap.TryGetValue(projectId, out var projectState) || projectState.Language != newProjectState.Language + ? GetRemoteSupportedProjectLanguages(newStateMap) + : _remoteSupportedLanguages; + newDependencyGraph ??= _dependencyGraph; var newTrackerMap = CreateCompilationTrackerMap(projectId, newDependencyGraph); // If we have a tracker for this project, then fork it as well (along with the @@ -1484,6 +1537,7 @@ private SolutionState ForkProject( return this.Branch( idToProjectStateMap: newStateMap, + remoteSupportedProjectLanguages: newLanguages, projectIdToTrackerMap: newTrackerMap, dependencyGraph: newDependencyGraph, filePathToDocumentIdsMap: newFilePathToDocumentIdsMap ?? _filePathToDocumentIdsMap); @@ -1671,10 +1725,12 @@ public SolutionState WithFrozenPartialCompilationIncludingSpecificDocument(Docum var newTracker = tracker.FreezePartialStateWithTree(this, doc, tree, cancellationToken); var newIdToProjectStateMap = _projectIdToProjectStateMap.SetItem(documentId.ProjectId, newTracker.ProjectState); + var newLanguages = _remoteSupportedLanguages; var newIdToTrackerMap = _projectIdToTrackerMap.SetItem(documentId.ProjectId, newTracker); currentPartialSolution = this.Branch( idToProjectStateMap: newIdToProjectStateMap, + remoteSupportedProjectLanguages: newLanguages, projectIdToTrackerMap: newIdToTrackerMap, dependencyGraph: CreateDependencyGraph(ProjectIds, newIdToProjectStateMap)); @@ -2002,7 +2058,7 @@ internal bool ContainsTransitiveReference(ProjectId fromProjectId, ProjectId toP => _dependencyGraph.GetProjectsThatThisProjectTransitivelyDependsOn(fromProjectId).Contains(toProjectId); internal ImmutableHashSet GetRemoteSupportedProjectLanguages() - => GetRemoteSupportedProjectLanguages(ProjectStates); + => _remoteSupportedLanguages; private static ImmutableHashSet GetRemoteSupportedProjectLanguages(ImmutableDictionary projectStates) {