diff --git a/src/Features/Core/Portable/EditAndContinue/EditSession.cs b/src/Features/Core/Portable/EditAndContinue/EditSession.cs index e4ffe3939bed5..03083aa251704 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditSession.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditSession.cs @@ -424,11 +424,11 @@ internal static async Task PopulateChangedAndAddedDocumentsAsync(Project oldProj cancellationToken.ThrowIfCancellationRequested(); - var oldSourceGeneratedDocumentStates = await oldProject.Solution.State.GetSourceGeneratedDocumentStatesAsync(oldProject.State, cancellationToken).ConfigureAwait(false); + var oldSourceGeneratedDocumentStates = await oldProject.Solution.CompilationState.GetSourceGeneratedDocumentStatesAsync(oldProject.State, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); - var newSourceGeneratedDocumentStates = await newProject.Solution.State.GetSourceGeneratedDocumentStatesAsync(newProject.State, cancellationToken).ConfigureAwait(false); + var newSourceGeneratedDocumentStates = await newProject.Solution.CompilationState.GetSourceGeneratedDocumentStatesAsync(newProject.State, cancellationToken).ConfigureAwait(false); foreach (var documentId in newSourceGeneratedDocumentStates.GetChangedStateIds(oldSourceGeneratedDocumentStates, ignoreUnchangedContent: true)) { @@ -488,11 +488,11 @@ internal static async IAsyncEnumerable GetChangedDocumentsAsync(Proj cancellationToken.ThrowIfCancellationRequested(); - var oldSourceGeneratedDocumentStates = await oldProject.Solution.State.GetSourceGeneratedDocumentStatesAsync(oldProject.State, cancellationToken).ConfigureAwait(false); + var oldSourceGeneratedDocumentStates = await oldProject.Solution.CompilationState.GetSourceGeneratedDocumentStatesAsync(oldProject.State, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); - var newSourceGeneratedDocumentStates = await newProject.Solution.State.GetSourceGeneratedDocumentStatesAsync(newProject.State, cancellationToken).ConfigureAwait(false); + var newSourceGeneratedDocumentStates = await newProject.Solution.CompilationState.GetSourceGeneratedDocumentStatesAsync(newProject.State, cancellationToken).ConfigureAwait(false); foreach (var documentId in newSourceGeneratedDocumentStates.GetChangedStateIds(oldSourceGeneratedDocumentStates, ignoreUnchangedContent: true)) { diff --git a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensRefreshQueue.cs b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensRefreshQueue.cs index f7da5f648e32f..70428f3cbbe8d 100644 --- a/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensRefreshQueue.cs +++ b/src/Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensRefreshQueue.cs @@ -106,7 +106,7 @@ public async Task TryEnqueueRefreshComputationAsync(Project project, Cancellatio { // Determine the checksum for this project cone. Note: this should be fast in practice because this is // the same project-cone-checksum we used to even call into OOP above when we computed semantic tokens. - var projectChecksum = await project.Solution.State.GetChecksumAsync(project.Id, cancellationToken).ConfigureAwait(false); + var projectChecksum = await project.Solution.CompilationState.GetChecksumAsync(project.Id, cancellationToken).ConfigureAwait(false); lock (_gate) { diff --git a/src/VisualStudio/Core/Test.Next/Remote/RemoteHostClientServiceFactoryTests.cs b/src/VisualStudio/Core/Test.Next/Remote/RemoteHostClientServiceFactoryTests.cs index 851bcbb2d7674..312e6d9e2519d 100644 --- a/src/VisualStudio/Core/Test.Next/Remote/RemoteHostClientServiceFactoryTests.cs +++ b/src/VisualStudio/Core/Test.Next/Remote/RemoteHostClientServiceFactoryTests.cs @@ -66,7 +66,7 @@ public async Task UpdaterService() await listener.ExpeditedWaitAsync(); // checksum should already exist - Assert.True(workspace.CurrentSolution.State.TryGetStateChecksums(out _)); + Assert.True(workspace.CurrentSolution.CompilationState.TryGetStateChecksums(out _)); checksumUpdater.Shutdown(); } diff --git a/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs b/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs index e4a1bc5235948..c66f1de7d1b63 100644 --- a/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs +++ b/src/VisualStudio/Core/Test.Next/Remote/SerializationValidator.cs @@ -221,7 +221,7 @@ internal async Task VerifyAssetSerializationAsync( internal async Task VerifySolutionStateSerializationAsync(Solution solution, Checksum solutionChecksum) { var solutionObjectFromSyncObject = await GetValueAsync(solutionChecksum); - Contract.ThrowIfFalse(solution.State.TryGetStateChecksums(out var solutionObjectFromSolution)); + Contract.ThrowIfFalse(solution.CompilationState.TryGetStateChecksums(out var solutionObjectFromSolution)); SolutionStateEqual(solutionObjectFromSolution, solutionObjectFromSyncObject); } diff --git a/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs b/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs index a400453c63dc9..b8831a714782b 100644 --- a/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/AssetProviderTests.cs @@ -72,7 +72,7 @@ public async Task TestAssetSynchronization() var solution = workspace.CurrentSolution; // build checksum - await solution.State.GetChecksumAsync(CancellationToken.None); + await solution.CompilationState.GetChecksumAsync(CancellationToken.None); var map = await solution.GetAssetMapAsync(CancellationToken.None); @@ -100,7 +100,7 @@ public async Task TestSolutionSynchronization() var solution = workspace.CurrentSolution; // build checksum - await solution.State.GetChecksumAsync(CancellationToken.None); + await solution.CompilationState.GetChecksumAsync(CancellationToken.None); var map = await solution.GetAssetMapAsync(CancellationToken.None); @@ -111,7 +111,7 @@ public async Task TestSolutionSynchronization() var assetSource = new SimpleAssetSource(workspace.Services.GetService(), map); var service = new AssetProvider(sessionId, storage, assetSource, remoteWorkspace.Services.GetService()); - await service.SynchronizeSolutionAssetsAsync(await solution.State.GetChecksumAsync(CancellationToken.None), CancellationToken.None); + await service.SynchronizeSolutionAssetsAsync(await solution.CompilationState.GetChecksumAsync(CancellationToken.None), CancellationToken.None); TestUtils.VerifyAssetStorage(map, storage); } diff --git a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs index 743977580ce9f..8abc7248826ca 100644 --- a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs @@ -62,8 +62,8 @@ public async Task TestRemoteHostSynchronize() var remoteWorkpace = client.GetRemoteWorkspace(); Assert.Equal( - await solution.State.GetChecksumAsync(CancellationToken.None), - await remoteWorkpace.CurrentSolution.State.GetChecksumAsync(CancellationToken.None)); + await solution.CompilationState.GetChecksumAsync(CancellationToken.None), + await remoteWorkpace.CurrentSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); } [Fact] @@ -107,7 +107,7 @@ await client.TryInvokeAsync( private static async Task GetAssetProviderAsync(Workspace workspace, Workspace remoteWorkspace, Solution solution, Dictionary map = null) { // make sure checksum is calculated - await solution.State.GetChecksumAsync(CancellationToken.None); + await solution.CompilationState.GetChecksumAsync(CancellationToken.None); map ??= new Dictionary(); await solution.AppendAssetMapAsync(map, CancellationToken.None); @@ -140,7 +140,7 @@ await client.TryInvokeAsync( // Ensure remote workspace is in sync with normal workspace. var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); - var solutionChecksum = await solution.State.GetChecksumAsync(CancellationToken.None); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); await remoteWorkspace.UpdatePrimaryBranchSolutionAsync(assetProvider, solutionChecksum, solution.WorkspaceVersion, CancellationToken.None); var callback = new DesignerAttributeComputerCallback(); @@ -196,14 +196,14 @@ public async Task TestUnknownProject() // No serializable remote options affect options checksum, so the checksums should match. Assert.Equal( - await solution.State.GetChecksumAsync(CancellationToken.None), - await remoteWorkspace.CurrentSolution.State.GetChecksumAsync(CancellationToken.None)); + await solution.CompilationState.GetChecksumAsync(CancellationToken.None), + await remoteWorkspace.CurrentSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); solution = solution.RemoveProject(solution.ProjectIds.Single()); Assert.Equal( - await solution.State.GetChecksumAsync(CancellationToken.None), - await remoteWorkspace.CurrentSolution.State.GetChecksumAsync(CancellationToken.None)); + await solution.CompilationState.GetChecksumAsync(CancellationToken.None), + await remoteWorkspace.CurrentSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); } [Theory] @@ -225,24 +225,24 @@ public async Task TestRemoteHostSynchronizeIncrementalUpdate(bool applyInBatch) await VerifyAssetStorageAsync(client, solution); Assert.Equal( - await solution.State.GetChecksumAsync(CancellationToken.None), - await remoteWorkspace.CurrentSolution.State.GetChecksumAsync(CancellationToken.None)); + await solution.CompilationState.GetChecksumAsync(CancellationToken.None), + await remoteWorkspace.CurrentSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); // incrementally update solution = await VerifyIncrementalUpdatesAsync( workspace, remoteWorkspace, client, solution, applyInBatch, csAddition: " ", vbAddition: " "); Assert.Equal( - await solution.State.GetChecksumAsync(CancellationToken.None), - await remoteWorkspace.CurrentSolution.State.GetChecksumAsync(CancellationToken.None)); + await solution.CompilationState.GetChecksumAsync(CancellationToken.None), + await remoteWorkspace.CurrentSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); // incrementally update solution = await VerifyIncrementalUpdatesAsync( workspace, remoteWorkspace, client, solution, applyInBatch, csAddition: "\r\nclass Addition { }", vbAddition: "\r\nClass VB\r\nEnd Class"); Assert.Equal( - await solution.State.GetChecksumAsync(CancellationToken.None), - await remoteWorkspace.CurrentSolution.State.GetChecksumAsync(CancellationToken.None)); + await solution.CompilationState.GetChecksumAsync(CancellationToken.None), + await remoteWorkspace.CurrentSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52578")] @@ -270,8 +270,8 @@ public async Task TestIncrementalUpdateHandlesReferenceReversal() await VerifyAssetStorageAsync(client, solution); Assert.Equal( - await solution.State.GetChecksumAsync(CancellationToken.None), - await remoteWorkspace.CurrentSolution.State.GetChecksumAsync(CancellationToken.None)); + await solution.CompilationState.GetChecksumAsync(CancellationToken.None), + await remoteWorkspace.CurrentSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); // reverse project references and incrementally update solution = solution.RemoveProjectReference(projectId1, project1ToProject2); @@ -281,8 +281,8 @@ await solution.State.GetChecksumAsync(CancellationToken.None), await UpdatePrimaryWorkspace(client, solution); Assert.Equal( - await solution.State.GetChecksumAsync(CancellationToken.None), - await remoteWorkspace.CurrentSolution.State.GetChecksumAsync(CancellationToken.None)); + await solution.CompilationState.GetChecksumAsync(CancellationToken.None), + await remoteWorkspace.CurrentSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); // reverse project references again and incrementally update solution = solution.RemoveProjectReference(projectId2, project2ToProject1); @@ -292,8 +292,8 @@ await solution.State.GetChecksumAsync(CancellationToken.None), await UpdatePrimaryWorkspace(client, solution); Assert.Equal( - await solution.State.GetChecksumAsync(CancellationToken.None), - await remoteWorkspace.CurrentSolution.State.GetChecksumAsync(CancellationToken.None)); + await solution.CompilationState.GetChecksumAsync(CancellationToken.None), + await remoteWorkspace.CurrentSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); } [Fact] @@ -750,8 +750,8 @@ async Task UpdateAndVerifyAsync() remoteSolution = currentRemoteSolution; Assert.Equal( - await solution.State.GetChecksumAsync(CancellationToken.None), - await remoteSolution.State.GetChecksumAsync(CancellationToken.None)); + await solution.CompilationState.GetChecksumAsync(CancellationToken.None), + await remoteSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); } } diff --git a/src/VisualStudio/Core/Test.Next/Services/SolutionAssetCacheTests.cs b/src/VisualStudio/Core/Test.Next/Services/SolutionAssetCacheTests.cs index 06ae77da3dfeb..fd59d87098bf3 100644 --- a/src/VisualStudio/Core/Test.Next/Services/SolutionAssetCacheTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/SolutionAssetCacheTests.cs @@ -68,7 +68,7 @@ public async Task TestSolutionKeepsAssetPinned() { var workspace = new RemoteWorkspace(FeaturesTestCompositions.RemoteHost.GetHostServices()); var solution = workspace.CurrentSolution; - var checksums = await solution.State.GetStateChecksumsAsync(CancellationToken.None); + var checksums = await solution.CompilationState.GetStateChecksumsAsync(CancellationToken.None); // Ensure the lazy has computed its value. var storage = new SolutionAssetCache( diff --git a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs index 67b3b8fa712e4..433c7063c8ce9 100644 --- a/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/SolutionServiceTests.cs @@ -46,10 +46,10 @@ public async Task TestCreation() var solution = workspace.CurrentSolution; var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); - var solutionChecksum = await solution.State.GetChecksumAsync(CancellationToken.None); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); var synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); - Assert.Equal(solutionChecksum, await synched.State.GetChecksumAsync(CancellationToken.None)); + Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); } [Theory] @@ -62,11 +62,11 @@ public async Task TestGetSolutionWithPrimaryFlag(bool updatePrimaryBranch) using var remoteWorkspace = CreateRemoteWorkspace(); var solution = workspace.CurrentSolution; - var solutionChecksum = await solution.State.GetChecksumAsync(CancellationToken.None); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); var synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch, solution.WorkspaceVersion, cancellationToken: CancellationToken.None); - Assert.Equal(solutionChecksum, await synched.State.GetChecksumAsync(CancellationToken.None)); + Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); Assert.Equal(WorkspaceKind.RemoteWorkspace, synched.WorkspaceKind); } @@ -86,7 +86,7 @@ public async Task TestStrongNameProvider() var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, workspace.CurrentSolution); - var solutionChecksum = await workspace.CurrentSolution.State.GetChecksumAsync(CancellationToken.None); + var solutionChecksum = await workspace.CurrentSolution.CompilationState.GetChecksumAsync(CancellationToken.None); var solution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); var compilationOptions = solution.Projects.First().CompilationOptions; @@ -115,7 +115,7 @@ public async Task TestStrongNameProviderEmpty() var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, workspace.CurrentSolution); - var solutionChecksum = await workspace.CurrentSolution.State.GetChecksumAsync(CancellationToken.None); + var solutionChecksum = await workspace.CurrentSolution.CompilationState.GetChecksumAsync(CancellationToken.None); var solution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); var compilationOptions = solution.Projects.First().CompilationOptions; @@ -138,7 +138,7 @@ public async Task TestCache() var solution = workspace.CurrentSolution; var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); - var solutionChecksum = await solution.State.GetChecksumAsync(CancellationToken.None); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); var first = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); var second = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); @@ -345,7 +345,7 @@ public async Task TestRemoteWorkspaceSolutionCrawler() var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); // update primary workspace - var solutionChecksum = await solution.State.GetChecksumAsync(CancellationToken.None); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); await remoteWorkspace.UpdatePrimaryBranchSolutionAsync(assetProvider, solutionChecksum, solution.WorkspaceVersion, CancellationToken.None); // get solution in remote host @@ -408,7 +408,7 @@ public async Task TestRemoteWorkspace() await Verify(remoteWorkspace, currentSolution, remoteSolution3, expectRemoteSolutionToCurrent: true); // move to new solution backward - var solutionInfo2 = await assetProvider.CreateSolutionInfoAsync(await solution1.State.GetChecksumAsync(CancellationToken.None), CancellationToken.None); + var solutionInfo2 = await assetProvider.CreateSolutionInfoAsync(await solution1.CompilationState.GetChecksumAsync(CancellationToken.None), CancellationToken.None); var solution2 = remoteWorkspace.GetTestAccessor().CreateSolutionFromInfo(solutionInfo2); Assert.False((await remoteWorkspace.GetTestAccessor().TryUpdateWorkspaceCurrentSolutionAsync( solution2, solution1.WorkspaceVersion)).updated); @@ -423,7 +423,7 @@ public async Task TestRemoteWorkspace() static async Task GetInitialOOPSolutionAsync(RemoteWorkspace remoteWorkspace, AssetProvider assetProvider, Solution solution) { // set up initial solution - var solutionChecksum = await solution.State.GetChecksumAsync(CancellationToken.None); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); await remoteWorkspace.UpdatePrimaryBranchSolutionAsync(assetProvider, solutionChecksum, solution.WorkspaceVersion, CancellationToken.None); // get solution in remote host @@ -433,7 +433,7 @@ static async Task GetInitialOOPSolutionAsync(RemoteWorkspace remoteWor static async Task Verify(RemoteWorkspace remoteWorkspace, Solution givenSolution, Solution remoteSolution, bool expectRemoteSolutionToCurrent) { // verify we got solution expected - Assert.Equal(await givenSolution.State.GetChecksumAsync(CancellationToken.None), await remoteSolution.State.GetChecksumAsync(CancellationToken.None)); + Assert.Equal(await givenSolution.CompilationState.GetChecksumAsync(CancellationToken.None), await remoteSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); // verify remote workspace got updated Assert.True(expectRemoteSolutionToCurrent == (remoteSolution == remoteWorkspace.CurrentSolution)); @@ -451,9 +451,9 @@ public async Task TestAddingProjectsWithExplicitOptions(bool useDefaultOptionVal var solution = workspace.CurrentSolution; solution = solution.RemoveProject(solution.ProjectIds.Single()); var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); - var solutionChecksum = await solution.State.GetChecksumAsync(CancellationToken.None); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); var synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, workspaceVersion: 0, CancellationToken.None); - Assert.Equal(solutionChecksum, await synched.State.GetChecksumAsync(CancellationToken.None)); + Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); // Add a C# project and a VB project, set some options, and check again var csharpDocument = new TestHostDocument("public class C { }"); @@ -473,9 +473,9 @@ public async Task TestAddingProjectsWithExplicitOptions(bool useDefaultOptionVal .WithChangedOption(FormattingOptions.NewLine, LanguageNames.VisualBasic, newOptionValue)); assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); - solutionChecksum = await solution.State.GetChecksumAsync(CancellationToken.None); + solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, workspaceVersion: 2, CancellationToken.None); - Assert.Equal(solutionChecksum, await synched.State.GetChecksumAsync(CancellationToken.None)); + Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); } [Fact] @@ -491,9 +491,9 @@ public async Task TestFrozenSourceGeneratedDocument() // First sync the solution over that has a generator var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); - var solutionChecksum = await solution.State.GetChecksumAsync(CancellationToken.None); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); var synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, workspaceVersion: 0, CancellationToken.None); - Assert.Equal(solutionChecksum, await synched.State.GetChecksumAsync(CancellationToken.None)); + Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); // Now freeze with some content var documentIdentity = (await solution.Projects.Single().GetSourceGeneratedDocumentsAsync()).First().Identity; @@ -501,18 +501,18 @@ public async Task TestFrozenSourceGeneratedDocument() var frozenSolution1 = solution.WithFrozenSourceGeneratedDocument(documentIdentity, frozenText1).Project.Solution; assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, frozenSolution1); - solutionChecksum = await frozenSolution1.State.GetChecksumAsync(CancellationToken.None); + solutionChecksum = await frozenSolution1.CompilationState.GetChecksumAsync(CancellationToken.None); synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, workspaceVersion: 1, CancellationToken.None); - Assert.Equal(solutionChecksum, await synched.State.GetChecksumAsync(CancellationToken.None)); + Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); // Try freezing with some different content from the original solution var frozenText2 = SourceText.From("// Hello, World! A second time!"); var frozenSolution2 = solution.WithFrozenSourceGeneratedDocument(documentIdentity, frozenText2).Project.Solution; assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, frozenSolution2); - solutionChecksum = await frozenSolution2.State.GetChecksumAsync(CancellationToken.None); + solutionChecksum = await frozenSolution2.CompilationState.GetChecksumAsync(CancellationToken.None); synched = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: true, workspaceVersion: 2, CancellationToken.None); - Assert.Equal(solutionChecksum, await synched.State.GetChecksumAsync(CancellationToken.None)); + Assert.Equal(solutionChecksum, await synched.CompilationState.GetChecksumAsync(CancellationToken.None)); } [Fact] @@ -532,19 +532,19 @@ public async Task TestPartialProjectSync_GetSolutionFirst() var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); - var solutionChecksum = await solution.State.GetChecksumAsync(CancellationToken.None); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); var syncedFullSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); - Assert.Equal(solutionChecksum, await syncedFullSolution.State.GetChecksumAsync(CancellationToken.None)); + Assert.Equal(solutionChecksum, await syncedFullSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); Assert.Equal(2, syncedFullSolution.Projects.Count()); // Syncing project1 should do nothing as syncing the solution already synced it over. - var project1Checksum = await solution.State.GetChecksumAsync(project1.Id, CancellationToken.None); + var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); Assert.Equal(2, project1SyncedSolution.Projects.Count()); // Syncing project2 should do nothing as syncing the solution already synced it over. - var project2Checksum = await solution.State.GetChecksumAsync(project2.Id, CancellationToken.None); + var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); Assert.Equal(2, project2SyncedSolution.Projects.Count()); } @@ -570,23 +570,23 @@ public async Task TestPartialProjectSync_GetSolutionLast() // Syncing project 1 should just since it over. await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); - var project1Checksum = await solution.State.GetChecksumAsync(project1.Id, CancellationToken.None); + var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); Assert.Equal(1, project1SyncedSolution.Projects.Count()); Assert.Equal(project1.Name, project1SyncedSolution.Projects.Single().Name); // Syncing project 2 should end up with p1 and p2 synced over. await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); - var project2Checksum = await solution.State.GetChecksumAsync(project2.Id, CancellationToken.None); + var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); Assert.Equal(2, project2SyncedSolution.Projects.Count()); // then syncing the whole project should have no effect. await solution.AppendAssetMapAsync(map, CancellationToken.None); - var solutionChecksum = await solution.State.GetChecksumAsync(CancellationToken.None); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); var syncedFullSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, solutionChecksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); - Assert.Equal(solutionChecksum, await syncedFullSolution.State.GetChecksumAsync(CancellationToken.None)); + Assert.Equal(solutionChecksum, await syncedFullSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); Assert.Equal(2, syncedFullSolution.Projects.Count()); } @@ -611,14 +611,14 @@ public async Task TestPartialProjectSync_GetDependentProjects1() Checksum.Create(ImmutableArray.CreateRange(Guid.NewGuid().ToByteArray())), new SolutionAssetCache(), new SimpleAssetSource(workspace.Services.GetService(), map), remoteWorkspace.Services.GetService()); await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); - var project2Checksum = await solution.State.GetChecksumAsync(project2.Id, CancellationToken.None); + var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); Assert.Equal(1, project2SyncedSolution.Projects.Count()); Assert.Equal(project2.Name, project2SyncedSolution.Projects.Single().Name); // syncing project 3 should sync project 2 as well because of the p2p ref await solution.AppendAssetMapAsync(map, project3.Id, CancellationToken.None); - var project3Checksum = await solution.State.GetChecksumAsync(project3.Id, CancellationToken.None); + var project3Checksum = await solution.CompilationState.GetChecksumAsync(project3.Id, CancellationToken.None); var project3SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project3Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); Assert.Equal(2, project3SyncedSolution.Projects.Count()); } @@ -645,20 +645,20 @@ public async Task TestPartialProjectSync_GetDependentProjects2() // syncing P3 should since project P2 as well because of the p2p ref await solution.AppendAssetMapAsync(map, project3.Id, CancellationToken.None); - var project3Checksum = await solution.State.GetChecksumAsync(project3.Id, CancellationToken.None); + var project3Checksum = await solution.CompilationState.GetChecksumAsync(project3.Id, CancellationToken.None); var project3SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project3Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); Assert.Equal(2, project3SyncedSolution.Projects.Count()); // if we then sync just P2, we should still have P2 and P3 from the prior sync await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); - var project2Checksum = await solution.State.GetChecksumAsync(project2.Id, CancellationToken.None); + var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); Assert.Equal(2, project2SyncedSolution.Projects.Count()); AssertEx.SetEqual(new[] { project2.Name, project3.Name }, project2SyncedSolution.Projects.Select(p => p.Name)); // if we then sync just P1, we should have 3 projects synved over now. await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); - var project1Checksum = await solution.State.GetChecksumAsync(project1.Id, CancellationToken.None); + var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); Assert.Equal(3, project1SyncedSolution.Projects.Count()); AssertEx.SetEqual(new[] { project1.Name, project2.Name, project3.Name }, project1SyncedSolution.Projects.Select(p => p.Name)); @@ -687,17 +687,17 @@ public async Task TestPartialProjectSync_GetDependentProjects3() // syncing project3 should since project2 and project1 as well because of the p2p ref await solution.AppendAssetMapAsync(map, project3.Id, CancellationToken.None); - var project3Checksum = await solution.State.GetChecksumAsync(project3.Id, CancellationToken.None); + var project3Checksum = await solution.CompilationState.GetChecksumAsync(project3.Id, CancellationToken.None); var project3SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project3Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); Assert.Equal(3, project3SyncedSolution.Projects.Count()); // syncing project2 should do nothing as everything is already synced - var project2Checksum = await solution.State.GetChecksumAsync(project2.Id, CancellationToken.None); + var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); Assert.Equal(3, project2SyncedSolution.Projects.Count()); // syncing project1 should do nothing as everything is already synced - var project1Checksum = await solution.State.GetChecksumAsync(project1.Id, CancellationToken.None); + var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); Assert.Equal(3, project1SyncedSolution.Projects.Count()); } @@ -725,17 +725,17 @@ public async Task TestPartialProjectSync_GetDependentProjects4() // syncing project3 should since project2 and project1 as well because of the p2p ref await solution.AppendAssetMapAsync(map, project3.Id, CancellationToken.None); - var project3Checksum = await solution.State.GetChecksumAsync(project3.Id, CancellationToken.None); + var project3Checksum = await solution.CompilationState.GetChecksumAsync(project3.Id, CancellationToken.None); var project3SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project3Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); Assert.Equal(3, project3SyncedSolution.Projects.Count()); // Syncing project2 should do nothing as it's already synced - var project2Checksum = await solution.State.GetChecksumAsync(project2.Id, CancellationToken.None); + var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); Assert.Equal(3, project2SyncedSolution.Projects.Count()); // Syncing project1 should do nothing as it's already synced - var project1Checksum = await solution.State.GetChecksumAsync(project1.Id, CancellationToken.None); + var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); Assert.Equal(3, project1SyncedSolution.Projects.Count()); } @@ -761,14 +761,14 @@ public async Task TestPartialProjectSync_Options1() // Syncing over project1 should give us 1 set of options on the OOP side. await solution.AppendAssetMapAsync(map, project1.Id, CancellationToken.None); - var project1Checksum = await solution.State.GetChecksumAsync(project1.Id, CancellationToken.None); + var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); var project1SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project1Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); Assert.Equal(1, project1SyncedSolution.Projects.Count()); Assert.Equal(project1.Name, project1SyncedSolution.Projects.Single().Name); // Syncing over project2 should now give two sets of options. await solution.AppendAssetMapAsync(map, project2.Id, CancellationToken.None); - var project2Checksum = await solution.State.GetChecksumAsync(project2.Id, CancellationToken.None); + var project2Checksum = await solution.CompilationState.GetChecksumAsync(project2.Id, CancellationToken.None); var project2SyncedSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, project2Checksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); Assert.Equal(2, project2SyncedSolution.Projects.Count()); } @@ -793,7 +793,7 @@ public async Task TestPartialProjectSync_ReferenceToNonExistentProject() var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution); - var project1Checksum = await solution.State.GetChecksumAsync(project1.Id, CancellationToken.None); + var project1Checksum = await solution.CompilationState.GetChecksumAsync(project1.Id, CancellationToken.None); } private static async Task VerifySolutionUpdate(string code, Func newSolutionGetter) @@ -815,7 +815,7 @@ private static async Task VerifySolutionUpdate( using var remoteWorkspace = CreateRemoteWorkspace(); var assetProvider = await GetAssetProviderAsync(workspace, remoteWorkspace, solution, map); - var solutionChecksum = await solution.State.GetChecksumAsync(CancellationToken.None); + var solutionChecksum = await solution.CompilationState.GetChecksumAsync(CancellationToken.None); // update primary workspace await remoteWorkspace.UpdatePrimaryBranchSolutionAsync(assetProvider, solutionChecksum, solution.WorkspaceVersion, CancellationToken.None); @@ -823,23 +823,23 @@ private static async Task VerifySolutionUpdate( oldSolutionValidator?.Invoke(recoveredSolution); Assert.Equal(WorkspaceKind.RemoteWorkspace, recoveredSolution.WorkspaceKind); - Assert.Equal(solutionChecksum, await recoveredSolution.State.GetChecksumAsync(CancellationToken.None)); + Assert.Equal(solutionChecksum, await recoveredSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); // get new solution var newSolution = newSolutionGetter(solution); - var newSolutionChecksum = await newSolution.State.GetChecksumAsync(CancellationToken.None); + var newSolutionChecksum = await newSolution.CompilationState.GetChecksumAsync(CancellationToken.None); await newSolution.AppendAssetMapAsync(map, CancellationToken.None); // get solution without updating primary workspace var recoveredNewSolution = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, newSolutionChecksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); - Assert.Equal(newSolutionChecksum, await recoveredNewSolution.State.GetChecksumAsync(CancellationToken.None)); + Assert.Equal(newSolutionChecksum, await recoveredNewSolution.CompilationState.GetChecksumAsync(CancellationToken.None)); // do same once updating primary workspace await remoteWorkspace.UpdatePrimaryBranchSolutionAsync(assetProvider, newSolutionChecksum, solution.WorkspaceVersion + 1, CancellationToken.None); var third = await remoteWorkspace.GetTestAccessor().GetSolutionAsync(assetProvider, newSolutionChecksum, updatePrimaryBranch: false, workspaceVersion: -1, CancellationToken.None); - Assert.Equal(newSolutionChecksum, await third.State.GetChecksumAsync(CancellationToken.None)); + Assert.Equal(newSolutionChecksum, await third.CompilationState.GetChecksumAsync(CancellationToken.None)); newSolutionValidator?.Invoke(recoveredNewSolution); } @@ -847,7 +847,7 @@ private static async Task VerifySolutionUpdate( private static async Task GetAssetProviderAsync(Workspace workspace, RemoteWorkspace remoteWorkspace, Solution solution, Dictionary map = null) { // make sure checksum is calculated - await solution.State.GetChecksumAsync(CancellationToken.None); + await solution.CompilationState.GetChecksumAsync(CancellationToken.None); map ??= new Dictionary(); await solution.AppendAssetMapAsync(map, CancellationToken.None); diff --git a/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingSolutionExtensions.cs b/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingSolutionExtensions.cs index 6e4c2792b46f8..59ef0fc593d8f 100644 --- a/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingSolutionExtensions.cs +++ b/src/Workspaces/Core/Portable/ExternalAccess/UnitTesting/Api/UnitTestingSolutionExtensions.cs @@ -14,6 +14,6 @@ public static int GetWorkspaceVersion(this Solution solution) => solution.WorkspaceVersion; public static async Task GetChecksumAsync(this Solution solution, CancellationToken cancellationToken) - => new UnitTestingChecksumWrapper(await solution.State.GetChecksumAsync(cancellationToken).ConfigureAwait(false)); + => new UnitTestingChecksumWrapper(await solution.CompilationState.GetChecksumAsync(cancellationToken).ConfigureAwait(false)); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder_ProjectIndex.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder_ProjectIndex.cs index 0348337a95009..9125e9626560e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder_ProjectIndex.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder_ProjectIndex.cs @@ -55,7 +55,7 @@ private static async Task CreateIndexAsync(Project project, Cancel var solutionKey = SolutionKey.ToSolutionKey(project.Solution); var regularDocumentStates = project.State.DocumentStates; - var sourceGeneratorDocumentStates = await project.Solution.State.GetSourceGeneratedDocumentStatesAsync(project.State, cancellationToken).ConfigureAwait(false); + var sourceGeneratorDocumentStates = await project.Solution.CompilationState.GetSourceGeneratedDocumentStatesAsync(project.State, cancellationToken).ConfigureAwait(false); var allStates = regularDocumentStates.States.Select(kvp => (kvp.Key, kvp.Value)).Concat( diff --git a/src/Workspaces/Core/Portable/Remote/IRemoteKeepAliveService.cs b/src/Workspaces/Core/Portable/Remote/IRemoteKeepAliveService.cs index 1260cc0da4f4d..5e2c760885f54 100644 --- a/src/Workspaces/Core/Portable/Remote/IRemoteKeepAliveService.cs +++ b/src/Workspaces/Core/Portable/Remote/IRemoteKeepAliveService.cs @@ -26,7 +26,7 @@ internal sealed class RemoteKeepAliveSession : IDisposable { private readonly CancellationTokenSource _cancellationTokenSource = new(); - private RemoteKeepAliveSession(SolutionState solution, IAsynchronousOperationListener listener) + private RemoteKeepAliveSession(SolutionCompilationState solution, IAsynchronousOperationListener listener) { var cancellationToken = _cancellationTokenSource.Token; var token = listener.BeginAsyncOperation(nameof(RemoteKeepAliveSession)); @@ -79,11 +79,11 @@ public void Dispose() /// any reason (for example for tracking down problems in testing scenarios). /// public static RemoteKeepAliveSession Create(Solution solution, IAsynchronousOperationListener listener) - => Create(solution.State, listener); + => Create(solution.CompilationState, listener); /// public static RemoteKeepAliveSession Create( - SolutionState solution, IAsynchronousOperationListener listener) + SolutionCompilationState solution, IAsynchronousOperationListener listener) { return new RemoteKeepAliveSession(solution, listener); } diff --git a/src/Workspaces/Core/Portable/Remote/RemoteHostClient.cs b/src/Workspaces/Core/Portable/Remote/RemoteHostClient.cs index 6b0d4a7f8d398..5eee9fba30452 100644 --- a/src/Workspaces/Core/Portable/Remote/RemoteHostClient.cs +++ b/src/Workspaces/Core/Portable/Remote/RemoteHostClient.cs @@ -110,11 +110,11 @@ public ValueTask TryInvokeAsync( CancellationToken cancellationToken) where TService : class { - return TryInvokeAsync(solution.State, invocation, cancellationToken); + return TryInvokeAsync(solution.CompilationState, invocation, cancellationToken); } public async ValueTask TryInvokeAsync( - SolutionState solution, + SolutionCompilationState solution, Func invocation, CancellationToken cancellationToken) where TService : class diff --git a/src/Workspaces/Core/Portable/Remote/RemoteServiceConnection.cs b/src/Workspaces/Core/Portable/Remote/RemoteServiceConnection.cs index f2c9eb3c51537..db3ba63107307 100644 --- a/src/Workspaces/Core/Portable/Remote/RemoteServiceConnection.cs +++ b/src/Workspaces/Core/Portable/Remote/RemoteServiceConnection.cs @@ -40,12 +40,12 @@ public abstract ValueTask> TryInvokeAsync( // solution, no callback public abstract ValueTask TryInvokeAsync( - SolutionState solution, + SolutionCompilationState solution, Func invocation, CancellationToken cancellationToken); public abstract ValueTask> TryInvokeAsync( - SolutionState solution, + SolutionCompilationState solution, Func> invocation, CancellationToken cancellationToken); @@ -53,24 +53,24 @@ public ValueTask TryInvokeAsync( Solution solution, Func invocation, CancellationToken cancellationToken) - => TryInvokeAsync(solution.State, invocation, cancellationToken); + => TryInvokeAsync(solution.CompilationState, invocation, cancellationToken); public ValueTask> TryInvokeAsync( Solution solution, Func> invocation, CancellationToken cancellationToken) - => TryInvokeAsync(solution.State, invocation, cancellationToken); + => TryInvokeAsync(solution.CompilationState, invocation, cancellationToken); // project, no callback public abstract ValueTask TryInvokeAsync( - SolutionState solution, + SolutionCompilationState solution, ProjectId projectId, Func invocation, CancellationToken cancellationToken); public abstract ValueTask> TryInvokeAsync( - SolutionState solution, + SolutionCompilationState solution, ProjectId projectId, Func> invocation, CancellationToken cancellationToken); @@ -79,23 +79,23 @@ public ValueTask TryInvokeAsync( Project project, Func invocation, CancellationToken cancellationToken) - => TryInvokeAsync(project.Solution.State, project.Id, invocation, cancellationToken); + => TryInvokeAsync(project.Solution.CompilationState, project.Id, invocation, cancellationToken); public ValueTask> TryInvokeAsync( Project project, Func> invocation, CancellationToken cancellationToken) - => TryInvokeAsync(project.Solution.State, project.Id, invocation, cancellationToken); + => TryInvokeAsync(project.Solution.CompilationState, project.Id, invocation, cancellationToken); // solution, callback public abstract ValueTask TryInvokeAsync( - SolutionState solution, + SolutionCompilationState solution, Func invocation, CancellationToken cancellationToken); public abstract ValueTask> TryInvokeAsync( - SolutionState solution, + SolutionCompilationState solution, Func> invocation, CancellationToken cancellationToken); @@ -103,24 +103,24 @@ public ValueTask TryInvokeAsync( Solution solution, Func invocation, CancellationToken cancellationToken) - => TryInvokeAsync(solution.State, invocation, cancellationToken); + => TryInvokeAsync(solution.CompilationState, invocation, cancellationToken); public ValueTask> TryInvokeAsync( Solution solution, Func> invocation, CancellationToken cancellationToken) - => TryInvokeAsync(solution.State, invocation, cancellationToken); + => TryInvokeAsync(solution.CompilationState, invocation, cancellationToken); // project, callback public abstract ValueTask TryInvokeAsync( - SolutionState solution, + SolutionCompilationState solution, ProjectId projectId, Func invocation, CancellationToken cancellationToken); public abstract ValueTask> TryInvokeAsync( - SolutionState solution, + SolutionCompilationState solution, ProjectId projectId, Func> invocation, CancellationToken cancellationToken); @@ -129,19 +129,19 @@ public ValueTask TryInvokeAsync( Project project, Func invocation, CancellationToken cancellationToken) - => TryInvokeAsync(project.Solution.State, project.Id, invocation, cancellationToken); + => TryInvokeAsync(project.Solution.CompilationState, project.Id, invocation, cancellationToken); public ValueTask> TryInvokeAsync( Project project, Func> invocation, CancellationToken cancellationToken) - => TryInvokeAsync(project.Solution.State, project.Id, invocation, cancellationToken); + => TryInvokeAsync(project.Solution.CompilationState, project.Id, invocation, cancellationToken); // multiple solution, no callback public abstract ValueTask TryInvokeAsync( - SolutionState solution1, - SolutionState solution2, + SolutionCompilationState solution1, + SolutionCompilationState solution2, Func invocation, CancellationToken cancellationToken); @@ -150,5 +150,5 @@ public ValueTask TryInvokeAsync( Solution solution2, Func invocation, CancellationToken cancellationToken) - => TryInvokeAsync(solution1.State, solution2.State, invocation, cancellationToken); + => TryInvokeAsync(solution1.CompilationState, solution2.CompilationState, invocation, cancellationToken); } diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs index 008d6065f2571..6a31963133f32 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs @@ -251,7 +251,7 @@ private void ChangeProjectProperty(ref T field, T newValue, Action(ref T field, T newValue, Action /// Reports a telemetry event if compilation information is being thrown away after being previously computed /// - private static void TryReportCompilationThrownAway(SolutionState solutionState, ProjectId projectId) + private static void TryReportCompilationThrownAway( + Solution solution, ProjectId projectId) { // We log the number of syntax trees that have been parsed even if there was no compilation created yet - var projectState = solutionState.GetRequiredProjectState(projectId); + var projectState = solution.State.GetRequiredProjectState(projectId); var parsedTrees = 0; foreach (var (_, documentState) in projectState.DocumentStates.States) { @@ -287,7 +288,7 @@ private static void TryReportCompilationThrownAway(SolutionState solutionState, } // But we also want to know if a compilation was created - var hadCompilation = solutionState.TryGetCompilation(projectId, out _); + var hadCompilation = solution.CompilationState.TryGetCompilation(projectId, out _); if (parsedTrees > 0 || hadCompilation) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs index ca5b0afdba85e..dd7ca0dfd16b0 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs @@ -281,7 +281,7 @@ public bool ContainsAnalyzerConfigDocument(DocumentId documentId) /// public async ValueTask> GetSourceGeneratedDocumentsAsync(CancellationToken cancellationToken = default) { - var generatedDocumentStates = await _solution.State.GetSourceGeneratedDocumentStatesAsync(this.State, cancellationToken).ConfigureAwait(false); + var generatedDocumentStates = await _solution.CompilationState.GetSourceGeneratedDocumentStatesAsync(this.State, cancellationToken).ConfigureAwait(false); // return an iterator to avoid eagerly allocating all the document instances return generatedDocumentStates.States.Values.Select(state => @@ -309,7 +309,7 @@ internal async ValueTask> GetAllRegularAndSourceGeneratedD return sourceGeneratedDocument; // We'll have to run generators if we haven't already and now try to find it. - var generatedDocumentStates = await _solution.State.GetSourceGeneratedDocumentStatesAsync(State, cancellationToken).ConfigureAwait(false); + var generatedDocumentStates = await _solution.CompilationState.GetSourceGeneratedDocumentStatesAsync(State, cancellationToken).ConfigureAwait(false); var generatedDocumentState = generatedDocumentStates.GetState(documentId); if (generatedDocumentState is null) return null; @@ -345,7 +345,7 @@ internal SourceGeneratedDocument GetOrCreateSourceGeneratedDocument(SourceGenera // Trickier case now: it's possible we generated this, but we don't actually have the SourceGeneratedDocument for it, so let's go // try to fetch the state. - var documentState = _solution.State.TryGetSourceGeneratedDocumentStateForAlreadyGeneratedId(documentId); + var documentState = _solution.CompilationState.TryGetSourceGeneratedDocumentStateForAlreadyGeneratedId(documentId); if (documentState == null) return null; @@ -354,7 +354,7 @@ internal SourceGeneratedDocument GetOrCreateSourceGeneratedDocument(SourceGenera internal ValueTask> GetSourceGeneratorDiagnosticsAsync(CancellationToken cancellationToken) { - return _solution.State.GetSourceGeneratorDiagnosticsAsync(this.State, cancellationToken); + return _solution.CompilationState.GetSourceGeneratorDiagnosticsAsync(this.State, cancellationToken); } internal Task ContainsSymbolsWithNameAsync( @@ -468,7 +468,7 @@ private async Task ContainsAsync(Func> predicateAsync /// or create a new one otherwise. /// public bool TryGetCompilation([NotNullWhen(returnValue: true)] out Compilation? compilation) - => _solution.State.TryGetCompilation(this.Id, out compilation); + => _solution.CompilationState.TryGetCompilation(this.Id, out compilation); /// /// Get the for this project asynchronously. @@ -479,14 +479,14 @@ public bool TryGetCompilation([NotNullWhen(returnValue: true)] out Compilation? /// return the same value if called multiple times. /// public Task GetCompilationAsync(CancellationToken cancellationToken = default) - => _solution.State.GetCompilationAsync(_projectState, cancellationToken); + => _solution.CompilationState.GetCompilationAsync(_projectState, cancellationToken); /// /// Determines if the compilation returned by and all its referenced compilation are from fully loaded projects. /// // TODO: make this public internal Task HasSuccessfullyLoadedAsync(CancellationToken cancellationToken = default) - => _solution.State.HasSuccessfullyLoadedAsync(_projectState, cancellationToken); + => _solution.CompilationState.HasSuccessfullyLoadedAsync(_projectState, cancellationToken); /// /// Gets an object that lists the added, changed and removed documents between this project and the specified project. @@ -516,14 +516,14 @@ public Task GetLatestDocumentVersionAsync(CancellationToken cancel /// The most recent version of the project, its documents and all dependent projects and documents. /// public Task GetDependentVersionAsync(CancellationToken cancellationToken = default) - => _solution.State.GetDependentVersionAsync(this.Id, cancellationToken); + => _solution.CompilationState.GetDependentVersionAsync(this.Id, cancellationToken); /// /// The semantic version of this project including the semantics of referenced projects. /// This version changes whenever the consumable declarations of this project and/or projects it depends on change. /// public Task GetDependentSemanticVersionAsync(CancellationToken cancellationToken = default) - => _solution.State.GetDependentSemanticVersionAsync(this.Id, cancellationToken); + => _solution.CompilationState.GetDependentSemanticVersionAsync(this.Id, cancellationToken); /// /// The semantic version of this project not including the semantics of referenced projects. @@ -558,7 +558,7 @@ public Task GetSemanticVersionAsync(CancellationToken cancellation /// /// internal Task GetDependentChecksumAsync(CancellationToken cancellationToken) - => _solution.State.GetDependentChecksumAsync(this.Id, cancellationToken); + => _solution.CompilationState.GetDependentChecksumAsync(this.Id, cancellationToken); /// /// Creates a new instance of this project updated to have the new assembly name. diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs index f575cbcd6a24e..e14117e9a17ab 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.ComponentModel; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; @@ -26,28 +25,37 @@ namespace Microsoft.CodeAnalysis /// public partial class Solution { - // SolutionState that doesn't hold onto Project/Document - private readonly SolutionState _state; + private readonly SolutionCompilationState _compilationState; // Values for all these are created on demand. private ImmutableHashMap _projectIdToProjectMap; - private Solution(SolutionState state) + private Solution(SolutionCompilationState compilationState) { _projectIdToProjectMap = ImmutableHashMap.Empty; - _state = state; + _compilationState = compilationState; } - internal Solution(Workspace workspace, SolutionInfo.SolutionAttributes solutionAttributes, SolutionOptionSet options, IReadOnlyList analyzerReferences) - : this(new SolutionState(workspace.Kind, workspace.PartialSemanticsEnabled, workspace.Services.SolutionServices, solutionAttributes, options, analyzerReferences)) + internal Solution( + Workspace workspace, + SolutionInfo.SolutionAttributes solutionAttributes, + SolutionOptionSet options, + IReadOnlyList analyzerReferences) + : this(new SolutionCompilationState( + new SolutionState(workspace.Kind, workspace.Services.SolutionServices, solutionAttributes, options, analyzerReferences), + workspace.PartialSemanticsEnabled)) { } - internal SolutionState State => _state; + // TODO(cyrusn): Remove and update references to access this.State. + private SolutionState _state => this.State; + internal SolutionState State => CompilationState.Solution; + + internal SolutionCompilationState CompilationState => _compilationState; internal int WorkspaceVersion => _state.WorkspaceVersion; - internal bool PartialSemanticsEnabled => _state.PartialSemanticsEnabled; + internal bool PartialSemanticsEnabled => _compilationState.PartialSemanticsEnabled; /// /// Per solution services provided by the host environment. Use this instead of @@ -165,7 +172,7 @@ private static Project CreateProject(ProjectId projectId, Solution solution) /// necessary to resolve symbols back to the actual project/compilation that produced them for correctness. /// internal ProjectId? GetOriginatingProjectId(ISymbol symbol) - => _state.GetOriginatingProjectId(symbol); + => _compilationState.GetOriginatingProjectId(symbol); /// internal Project? GetOriginatingProject(ISymbol symbol) @@ -195,9 +202,7 @@ private static Project CreateProject(ProjectId projectId, Solution solution) /// Gets the documentId in this solution with the specified syntax tree. /// public DocumentId? GetDocumentId(SyntaxTree? syntaxTree, ProjectId? projectId) - { - return State.GetDocumentState(syntaxTree, projectId)?.Id; - } + => _compilationState.GetDocumentState(syntaxTree, projectId)?.Id; /// /// Gets the document in this solution with the specified document ID. @@ -285,7 +290,7 @@ private static Project CreateProject(ProjectId projectId, Solution solution) { if (syntaxTree != null) { - var documentState = State.GetDocumentState(syntaxTree, projectId); + var documentState = _compilationState.GetDocumentState(syntaxTree, projectId); if (documentState is SourceGeneratedDocumentState) { @@ -326,13 +331,8 @@ public Solution AddProject(ProjectId projectId, string name, string assemblyName /// public Solution AddProject(ProjectInfo projectInfo) { - var newState = _state.AddProject(projectInfo); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.AddProject(projectInfo); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -340,13 +340,8 @@ public Solution AddProject(ProjectInfo projectInfo) /// public Solution RemoveProject(ProjectId projectId) { - var newState = _state.RemoveProject(projectId); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.RemoveProject(projectId); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -362,13 +357,8 @@ public Solution WithProjectAssemblyName(ProjectId projectId, string assemblyName throw new ArgumentNullException(nameof(assemblyName)); } - var newState = _state.WithProjectAssemblyName(projectId, assemblyName); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.WithProjectAssemblyName(projectId, assemblyName); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -378,13 +368,8 @@ public Solution WithProjectOutputFilePath(ProjectId projectId, string? outputFil { CheckContainsProject(projectId); - var newState = _state.WithProjectOutputFilePath(projectId, outputFilePath); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.WithProjectOutputFilePath(projectId, outputFilePath); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -394,13 +379,8 @@ public Solution WithProjectOutputRefFilePath(ProjectId projectId, string? output { CheckContainsProject(projectId); - var newState = _state.WithProjectOutputRefFilePath(projectId, outputRefFilePath); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.WithProjectOutputRefFilePath(projectId, outputRefFilePath); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -410,13 +390,8 @@ public Solution WithProjectCompilationOutputInfo(ProjectId projectId, in Compila { CheckContainsProject(projectId); - var newState = _state.WithProjectCompilationOutputInfo(projectId, info); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.WithProjectCompilationOutputInfo(projectId, info); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -426,13 +401,8 @@ public Solution WithProjectDefaultNamespace(ProjectId projectId, string? default { CheckContainsProject(projectId); - var newState = _state.WithProjectDefaultNamespace(projectId, defaultNamespace); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.WithProjectDefaultNamespace(projectId, defaultNamespace); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -442,13 +412,8 @@ internal Solution WithProjectChecksumAlgorithm(ProjectId projectId, SourceHashAl { CheckContainsProject(projectId); - var newState = _state.WithProjectChecksumAlgorithm(projectId, checksumAlgorithm); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.WithProjectChecksumAlgorithm(projectId, checksumAlgorithm); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -463,13 +428,8 @@ public Solution WithProjectName(ProjectId projectId, string name) throw new ArgumentNullException(nameof(name)); } - var newState = _state.WithProjectName(projectId, name); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.WithProjectName(projectId, name); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -479,13 +439,8 @@ public Solution WithProjectFilePath(ProjectId projectId, string? filePath) { CheckContainsProject(projectId); - var newState = _state.WithProjectFilePath(projectId, filePath); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.WithProjectFilePath(projectId, filePath); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -501,13 +456,8 @@ public Solution WithProjectCompilationOptions(ProjectId projectId, CompilationOp throw new ArgumentNullException(nameof(options)); } - var newState = _state.WithProjectCompilationOptions(projectId, options); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.WithProjectCompilationOptions(projectId, options); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -523,13 +473,8 @@ public Solution WithProjectParseOptions(ProjectId projectId, ParseOptions option throw new ArgumentNullException(nameof(options)); } - var newState = _state.WithProjectParseOptions(projectId, options); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.WithProjectParseOptions(projectId, options); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -541,13 +486,8 @@ internal Solution WithHasAllInformation(ProjectId projectId, bool hasAllInformat { CheckContainsProject(projectId); - var newState = _state.WithHasAllInformation(projectId, hasAllInformation); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.WithHasAllInformation(projectId, hasAllInformation); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -559,13 +499,8 @@ internal Solution WithRunAnalyzers(ProjectId projectId, bool runAnalyzers) { CheckContainsProject(projectId); - var newState = _state.WithRunAnalyzers(projectId, runAnalyzers); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.WithRunAnalyzers(projectId, runAnalyzers); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -586,13 +521,8 @@ public Solution WithProjectDocumentsOrder(ProjectId projectId, ImmutableList @@ -640,13 +570,8 @@ public Solution AddProjectReferences(ProjectId projectId, IEnumerable @@ -665,13 +590,15 @@ public Solution RemoveProjectReference(ProjectId projectId, ProjectReference pro CheckContainsProject(projectId); - var newState = _state.RemoveProjectReference(projectId, projectReference); - if (newState == _state) + // If the project didn't change itself, there's no need to change the compilation state. + var stateChange = _state.RemoveProjectReference(projectId, projectReference); + if (stateChange.NewSolutionState == _state) { throw new ArgumentException(WorkspacesResources.Project_does_not_contain_specified_reference, nameof(projectReference)); } - return new Solution(newState); + var newCompilationState = _compilationState.RemoveProjectReference(stateChange); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -694,13 +621,8 @@ public Solution WithProjectReferences(ProjectId projectId, IEnumerable @@ -743,13 +665,8 @@ public Solution AddMetadataReferences(ProjectId projectId, IEnumerable @@ -769,13 +686,15 @@ public Solution RemoveMetadataReference(ProjectId projectId, MetadataReference m throw new ArgumentNullException(nameof(metadataReference)); } - var newState = _state.RemoveMetadataReference(projectId, metadataReference); - if (newState == _state) + // If the project didn't change itself, there's no need to change the compilation state. + var stateChange = _state.RemoveMetadataReference(projectId, metadataReference); + if (stateChange.NewSolutionState == _state) { throw new InvalidOperationException(WorkspacesResources.Project_does_not_contain_specified_reference); } - return new Solution(newState); + var newCompilationState = _compilationState.RemoveMetadataReference(stateChange); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -790,16 +709,10 @@ public Solution WithProjectMetadataReferences(ProjectId projectId, IEnumerable @@ -846,13 +759,8 @@ public Solution AddAnalyzerReferences(ProjectId projectId, IEnumerable @@ -872,13 +780,15 @@ public Solution RemoveAnalyzerReference(ProjectId projectId, AnalyzerReference a throw new ArgumentNullException(nameof(analyzerReference)); } - var newState = _state.RemoveAnalyzerReference(projectId, analyzerReference); - if (newState == _state) + // If the project didn't change itself, there's no need to change the compilation state. + var stateChange = _state.RemoveAnalyzerReference(projectId, analyzerReference); + if (stateChange.NewSolutionState == _state) { throw new InvalidOperationException(WorkspacesResources.Project_does_not_contain_specified_reference); } - return new Solution(newState); + var newCompilationState = _compilationState.RemoveAnalyzerReference(stateChange, analyzerReference); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -893,16 +803,10 @@ public Solution WithProjectAnalyzerReferences(ProjectId projectId, IEnumerable @@ -937,13 +841,8 @@ public Solution AddAnalyzerReferences(IEnumerable analyzerRef } } - var newState = _state.AddAnalyzerReferences(collection); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.AddAnalyzerReferences(_state.AddAnalyzerReferences(collection)); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -965,7 +864,8 @@ public Solution RemoveAnalyzerReference(AnalyzerReference analyzerReference) throw new InvalidOperationException(WorkspacesResources.Solution_does_not_contain_specified_reference); } - return new Solution(newState); + var newCompilationState = _compilationState.RemoveAnalyzerReference(newState); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -975,15 +875,10 @@ public Solution RemoveAnalyzerReference(AnalyzerReference analyzerReference) /// contains duplicate items. public Solution WithAnalyzerReferences(IEnumerable analyzerReferences) { - var newState = _state.WithAnalyzerReferences( - PublicContract.ToBoxedImmutableArrayWithDistinctNonNullItems(analyzerReferences, nameof(analyzerReferences))); + var collection = PublicContract.ToBoxedImmutableArrayWithDistinctNonNullItems(analyzerReferences, nameof(analyzerReferences)); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.WithAnalyzerReferences(_state.WithAnalyzerReferences(collection)); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } private static SourceCodeKind GetSourceCodeKind(ProjectState project) @@ -1097,13 +992,8 @@ public Solution AddDocument(DocumentInfo documentInfo) /// A new with the documents added. public Solution AddDocuments(ImmutableArray documentInfos) { - var newState = _state.AddDocuments(documentInfos); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.AddDocuments(documentInfos); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -1143,13 +1033,8 @@ public Solution AddAdditionalDocument(DocumentInfo documentInfo) public Solution AddAdditionalDocuments(ImmutableArray documentInfos) { - var newState = _state.AddAdditionalDocuments(documentInfos); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.AddAdditionalDocuments(documentInfos); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -1203,13 +1088,8 @@ private ProjectState GetRequiredProjectState(ProjectId projectId) /// public Solution AddAnalyzerConfigDocuments(ImmutableArray documentInfos) { - var newState = _state.AddAnalyzerConfigDocuments(documentInfos); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.AddAnalyzerConfigDocuments(documentInfos); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -1232,13 +1112,8 @@ public Solution RemoveDocuments(ImmutableArray documentIds) private Solution RemoveDocumentsImpl(ImmutableArray documentIds) { - var newState = _state.RemoveDocuments(documentIds); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.RemoveDocuments(documentIds); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -1261,13 +1136,8 @@ public Solution RemoveAdditionalDocuments(ImmutableArray documentIds private Solution RemoveAdditionalDocumentsImpl(ImmutableArray documentIds) { - var newState = _state.RemoveAdditionalDocuments(documentIds); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.RemoveAdditionalDocuments(documentIds); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -1290,13 +1160,8 @@ public Solution RemoveAnalyzerConfigDocuments(ImmutableArray documen private Solution RemoveAnalyzerConfigDocumentsImpl(ImmutableArray documentIds) { - var newState = _state.RemoveAnalyzerConfigDocuments(documentIds); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.RemoveAnalyzerConfigDocuments(documentIds); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -1311,13 +1176,8 @@ public Solution WithDocumentName(DocumentId documentId, string name) throw new ArgumentNullException(nameof(name)); } - var newState = _state.WithDocumentName(documentId, name); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.WithDocumentName(documentId, name); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -1328,15 +1188,10 @@ public Solution WithDocumentFolders(DocumentId documentId, IEnumerable? { CheckContainsDocument(documentId); - var newState = _state.WithDocumentFolders(documentId, - PublicContract.ToBoxedImmutableArrayWithNonNullItems(folders, nameof(folders))); - - if (newState == _state) - { - return this; - } + var collection = PublicContract.ToBoxedImmutableArrayWithNonNullItems(folders, nameof(folders)); - return new Solution(newState); + var newCompilationState = _compilationState.WithDocumentFolders(documentId, collection); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -1354,13 +1209,8 @@ public Solution WithDocumentFilePath(DocumentId documentId, string filePath) throw new ArgumentNullException(nameof(filePath)); } - var newState = _state.WithDocumentFilePath(documentId, filePath); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.WithDocumentFilePath(documentId, filePath); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -1381,13 +1231,8 @@ public Solution WithDocumentText(DocumentId documentId, SourceText text, Preserv throw new ArgumentOutOfRangeException(nameof(mode)); } - var newState = _state.WithDocumentText(documentId, text, mode); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.WithDocumentText(documentId, text, mode); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -1408,13 +1253,8 @@ public Solution WithAdditionalDocumentText(DocumentId documentId, SourceText tex throw new ArgumentOutOfRangeException(nameof(mode)); } - var newState = _state.WithAdditionalDocumentText(documentId, text, mode); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.WithAdditionalDocumentText(documentId, text, mode); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -1435,13 +1275,8 @@ public Solution WithAnalyzerConfigDocumentText(DocumentId documentId, SourceText throw new ArgumentOutOfRangeException(nameof(mode)); } - var newState = _state.WithAnalyzerConfigDocumentText(documentId, text, mode); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.WithAnalyzerConfigDocumentText(documentId, text, mode); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -1462,13 +1297,8 @@ public Solution WithDocumentText(DocumentId documentId, TextAndVersion textAndVe throw new ArgumentOutOfRangeException(nameof(mode)); } - var newState = _state.WithDocumentText(documentId, textAndVersion, mode); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.WithDocumentText(documentId, textAndVersion, mode); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -1489,13 +1319,8 @@ public Solution WithAdditionalDocumentText(DocumentId documentId, TextAndVersion throw new ArgumentOutOfRangeException(nameof(mode)); } - var newState = _state.WithAdditionalDocumentText(documentId, textAndVersion, mode); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.WithAdditionalDocumentText(documentId, textAndVersion, mode); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -1516,13 +1341,8 @@ public Solution WithAnalyzerConfigDocumentText(DocumentId documentId, TextAndVer throw new ArgumentOutOfRangeException(nameof(mode)); } - var newState = _state.WithAnalyzerConfigDocumentText(documentId, textAndVersion, mode); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.WithAnalyzerConfigDocumentText(documentId, textAndVersion, mode); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -1543,19 +1363,14 @@ public Solution WithDocumentSyntaxRoot(DocumentId documentId, SyntaxNode root, P throw new ArgumentOutOfRangeException(nameof(mode)); } - var newState = _state.WithDocumentSyntaxRoot(documentId, root, mode); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.WithDocumentSyntaxRoot(documentId, root, mode); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } internal Solution WithDocumentContentsFrom(DocumentId documentId, DocumentState documentState) { - var newState = _state.WithDocumentContentsFrom(documentId, documentState); - return newState == _state ? this : new Solution(newState); + var newCompilationState = _compilationState.WithDocumentContentsFrom(documentId, documentState); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -1578,13 +1393,8 @@ public Solution WithDocumentSourceCodeKind(DocumentId documentId, SourceCodeKind throw new ArgumentOutOfRangeException(nameof(sourceCodeKind)); } - var newState = _state.WithDocumentSourceCodeKind(documentId, sourceCodeKind); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.WithDocumentSourceCodeKind(documentId, sourceCodeKind); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -1605,13 +1415,8 @@ public Solution WithDocumentTextLoader(DocumentId documentId, TextLoader loader, throw new ArgumentOutOfRangeException(nameof(mode)); } - var newState = _state.UpdateDocumentTextLoader(documentId, loader, mode); - - // Note: state is currently not reused. - // If UpdateDocumentTextLoader is changed to reuse the state replace this assert with Solution instance reusal. - Debug.Assert(newState != _state); - - return new Solution(newState); + var newCompilationState = _compilationState.UpdateDocumentTextLoader(documentId, loader, mode); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -1632,13 +1437,8 @@ public Solution WithAdditionalDocumentTextLoader(DocumentId documentId, TextLoad throw new ArgumentOutOfRangeException(nameof(mode)); } - var newState = _state.UpdateAdditionalDocumentTextLoader(documentId, loader, mode); - - // Note: state is currently not reused. - // If UpdateAdditionalDocumentTextLoader is changed to reuse the state replace this assert with Solution instance reusal. - Debug.Assert(newState != _state); - - return new Solution(newState); + var newCompilationState = _compilationState.UpdateAdditionalDocumentTextLoader(documentId, loader, mode); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -1659,13 +1459,8 @@ public Solution WithAnalyzerConfigDocumentTextLoader(DocumentId documentId, Text throw new ArgumentOutOfRangeException(nameof(mode)); } - var newState = _state.UpdateAnalyzerConfigDocumentTextLoader(documentId, loader, mode); - - // Note: state is currently not reused. - // If UpdateAnalyzerConfigDocumentTextLoader is changed to reuse the state replace this assert with Solution instance reusal. - Debug.Assert(newState != _state); - - return new Solution(newState); + var newCompilationState = _compilationState.UpdateAnalyzerConfigDocumentTextLoader(documentId, loader, mode); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -1678,8 +1473,8 @@ public Solution WithAnalyzerConfigDocumentTextLoader(DocumentId documentId, Text /// internal Solution WithFrozenPartialCompilationIncludingSpecificDocument(DocumentId documentId, CancellationToken cancellationToken) { - var newState = _state.WithFrozenPartialCompilationIncludingSpecificDocument(documentId, cancellationToken); - return new Solution(newState); + var newCompilationState = _compilationState.WithFrozenPartialCompilationIncludingSpecificDocument(documentId, cancellationToken); + return new Solution(newCompilationState); } internal async Task WithMergedLinkedFileChangesAsync( @@ -1701,13 +1496,8 @@ internal ImmutableArray GetRelatedDocumentIds(DocumentId documentId) internal Solution WithNewWorkspace(string? workspaceKind, int workspaceVersion, SolutionServices services) { - var newState = _state.WithNewWorkspace(workspaceKind, workspaceVersion, services); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.WithNewWorkspace(workspaceKind, workspaceVersion, services); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -1741,13 +1531,8 @@ public Solution WithDocumentText(IEnumerable documentIds, SourceTex throw new ArgumentOutOfRangeException(nameof(mode)); } - var newState = _state.WithDocumentText(documentIds, text, mode); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.WithDocumentText(documentIds, text, mode); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -1757,10 +1542,14 @@ public Solution WithDocumentText(IEnumerable documentIds, SourceTex /// internal Document WithFrozenSourceGeneratedDocument(SourceGeneratedDocumentIdentity documentIdentity, SourceText text) { - var newState = _state.WithFrozenSourceGeneratedDocument(documentIdentity, text); - var newSolution = newState != _state ? new Solution(newState) : this; - var newDocumentState = newState.TryGetSourceGeneratedDocumentStateForAlreadyGeneratedId(documentIdentity.DocumentId); + var newCompilationState = _compilationState.WithFrozenSourceGeneratedDocument(documentIdentity, text); + var newSolution = newCompilationState != _compilationState + ? new Solution(newCompilationState) + : this; + + var newDocumentState = newCompilationState.TryGetSourceGeneratedDocumentStateForAlreadyGeneratedId(documentIdentity.DocumentId); Contract.ThrowIfNull(newDocumentState, "Because we just froze this document, it should always exist."); + var newProject = newSolution.GetRequiredProject(newDocumentState.Id.ProjectId); return newProject.GetOrCreateSourceGeneratedDocument(newDocumentState); } @@ -1771,13 +1560,8 @@ internal Document WithFrozenSourceGeneratedDocument(SourceGeneratedDocumentIdent /// internal Solution WithoutFrozenSourceGeneratedDocuments() { - var newState = _state.WithoutFrozenSourceGeneratedDocuments(); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.WithoutFrozenSourceGeneratedDocuments(); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -1790,13 +1574,8 @@ internal Solution WithoutFrozenSourceGeneratedDocuments() /// internal Solution WithCachedSourceGeneratorState(ProjectId projectToUpdate, Project projectWithCachedGeneratorState) { - var newState = _state.WithCachedSourceGeneratorState(projectToUpdate, projectWithCachedGeneratorState); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.WithCachedSourceGeneratorState(projectToUpdate, projectWithCachedGeneratorState); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } /// @@ -1853,13 +1632,8 @@ public Solution WithOptions(OptionSet options) /// internal Solution WithOptions(SolutionOptionSet options) { - var newState = _state.WithOptions(options: options); - if (newState == _state) - { - return this; - } - - return new Solution(newState); + var newCompilationState = _compilationState.WithOptions(options); + return newCompilationState == _compilationState ? this : new Solution(newCompilationState); } private void CheckContainsProject(ProjectId projectId) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs new file mode 100644 index 0000000000000..c16df07570eaa --- /dev/null +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs @@ -0,0 +1,1317 @@ +// 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.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Logging; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; +using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; + +namespace Microsoft.CodeAnalysis; + +internal sealed partial class SolutionCompilationState +{ + /// + /// Symbols need to be either or . + /// + private static readonly ConditionalWeakTable s_assemblyOrModuleSymbolToProjectMap = new(); + + /// + /// Green version of the information about this Solution instance. Responsible for non-semantic information + /// about the solution structure. Specifically, the set of green s, with all their + /// green s. Contains the attributes, options and relationships between projects. + /// Effectively, everything specified in a project file. Does not contain anything related to s or semantics. + /// + public SolutionState Solution { get; } + + public bool PartialSemanticsEnabled { get; } + + // Values for all these are created on demand. + private ImmutableDictionary _projectIdToTrackerMap; + + /// + /// Cache we use to map between unrooted symbols (i.e. assembly, module and dynamic symbols) and the project + /// they came from. That way if we are asked about many symbols from the same assembly/module we can answer the + /// question quickly after computing for the first one. Created on demand. + /// + private ConditionalWeakTable? _unrootedSymbolToProjectId; + private static readonly Func> s_createTable = () => new ConditionalWeakTable(); + + private readonly SourceGeneratedDocumentState? _frozenSourceGeneratedDocumentState; + + // Lock for the partial compilation state listed below. + private NonReentrantLock? _stateLockBackingField; + private NonReentrantLock StateLock => LazyInitializer.EnsureInitialized(ref _stateLockBackingField, NonReentrantLock.Factory); + + private WeakReference? _latestSolutionWithPartialCompilation; + private DateTime _timeOfLatestSolutionWithPartialCompilation; + private DocumentId? _documentIdOfLatestSolutionWithPartialCompilation; + + private SolutionCompilationState( + SolutionState solution, + bool partialSemanticsEnabled, + ImmutableDictionary projectIdToTrackerMap, + SourceGeneratedDocumentState? frozenSourceGeneratedDocument) + { + Solution = solution; + PartialSemanticsEnabled = partialSemanticsEnabled; + _projectIdToTrackerMap = projectIdToTrackerMap; + _frozenSourceGeneratedDocumentState = frozenSourceGeneratedDocument; + + // when solution state is changed, we recalculate its checksum + _lazyChecksums = AsyncLazy.Create(c => ComputeChecksumsAsync(projectsToInclude: null, c)); + + CheckInvariants(); + } + + public SolutionCompilationState( + SolutionState solution, + bool partialSemanticsEnabled) + : this( + solution, + partialSemanticsEnabled, + projectIdToTrackerMap: ImmutableDictionary.Empty, + frozenSourceGeneratedDocument: null) + { + } + + public SolutionServices Services => this.Solution.Services; + + // Only run this in debug builds; even the .Any() call across all projects can be expensive when there's a lot of them. + [Conditional("DEBUG")] + private void CheckInvariants() + { + // An id shouldn't point at a tracker for a different project. + Contract.ThrowIfTrue(_projectIdToTrackerMap.Any(kvp => kvp.Key != kvp.Value.ProjectState.Id)); + } + + public SourceGeneratedDocumentState? FrozenSourceGeneratedDocumentState => _frozenSourceGeneratedDocumentState; + + private SolutionCompilationState Branch( + SolutionState newSolutionState, + ImmutableDictionary? projectIdToTrackerMap = null, + Optional frozenSourceGeneratedDocument = default) + { + projectIdToTrackerMap ??= _projectIdToTrackerMap; + var newFrozenSourceGeneratedDocumentState = frozenSourceGeneratedDocument.HasValue ? frozenSourceGeneratedDocument.Value : _frozenSourceGeneratedDocumentState; + + if (newSolutionState == this.Solution && + projectIdToTrackerMap == _projectIdToTrackerMap && + newFrozenSourceGeneratedDocumentState == _frozenSourceGeneratedDocumentState) + { + return this; + } + + return new SolutionCompilationState( + newSolutionState, + PartialSemanticsEnabled, + projectIdToTrackerMap, + newFrozenSourceGeneratedDocumentState); + } + + /// + private SolutionCompilationState ForkProject( + StateChange stateChange, + Func? translate, + bool forkTracker) + { + return ForkProject( + stateChange, + translate: static (stateChange, translate) => translate?.Invoke(stateChange), + forkTracker, + arg: translate); + } + + /// + private SolutionCompilationState ForkProject( + StateChange stateChange, + Func translate, + bool forkTracker, + TArg arg) + { + // If the solution didn't actually change, there's no need to change us. + if (stateChange.NewSolutionState == this.Solution) + return this; + + return ForceForkProject(stateChange, translate.Invoke(stateChange, arg), forkTracker); + } + + /// + /// Same as except that it will still fork even if newSolutionState is unchanged from . + /// + private SolutionCompilationState ForceForkProject( + StateChange stateChange, + CompilationAndGeneratorDriverTranslationAction? translate, + bool forkTracker) + { + var newSolutionState = stateChange.NewSolutionState; + var newProjectState = stateChange.NewProjectState; + var projectId = newProjectState.Id; + + var newDependencyGraph = newSolutionState.GetProjectDependencyGraph(); + var newTrackerMap = CreateCompilationTrackerMap(projectId, newDependencyGraph); + + // If we have a tracker for this project, then fork it as well (along with the + // translation action and store it in the tracker map. + if (newTrackerMap.TryGetValue(projectId, out var tracker)) + { + newTrackerMap = newTrackerMap.Remove(projectId); + + if (forkTracker) + { + newTrackerMap = newTrackerMap.Add(projectId, tracker.Fork(newProjectState, translate)); + } + } + + return this.Branch( + newSolutionState, + projectIdToTrackerMap: newTrackerMap); + } + + private ImmutableDictionary CreateCompilationTrackerMap(ProjectId changedProjectId, ProjectDependencyGraph dependencyGraph) + { + if (_projectIdToTrackerMap.Count == 0) + return _projectIdToTrackerMap; + + using var _ = ArrayBuilder>.GetInstance(_projectIdToTrackerMap.Count, out var newTrackerInfo); + var allReused = true; + foreach (var (id, tracker) in _projectIdToTrackerMap) + { + var localTracker = tracker; + if (!CanReuse(id)) + { + localTracker = tracker.Fork(tracker.ProjectState, translate: null); + allReused = false; + } + + newTrackerInfo.Add(new KeyValuePair(id, localTracker)); + } + + if (allReused) + return _projectIdToTrackerMap; + + return ImmutableDictionary.CreateRange(newTrackerInfo); + + // Returns true if 'tracker' can be reused for project 'id' + bool CanReuse(ProjectId id) + { + if (id == changedProjectId) + { + return true; + } + + return !dependencyGraph.DoesProjectTransitivelyDependOnProject(id, changedProjectId); + } + } + + /// + public SolutionCompilationState AddProject(ProjectInfo projectInfo) + { + var newSolutionState = this.Solution.AddProject(projectInfo); + var newTrackerMap = CreateCompilationTrackerMap(projectInfo.Id, newSolutionState.GetProjectDependencyGraph()); + + return Branch( + newSolutionState, + projectIdToTrackerMap: newTrackerMap); + } + + /// + public SolutionCompilationState RemoveProject(ProjectId projectId) + { + var newSolutionState = this.Solution.RemoveProject(projectId); + var newTrackerMap = CreateCompilationTrackerMap(projectId, newSolutionState.GetProjectDependencyGraph()); + + return this.Branch( + newSolutionState, + projectIdToTrackerMap: newTrackerMap.Remove(projectId)); + } + + /// + public SolutionCompilationState WithProjectAssemblyName( + ProjectId projectId, string assemblyName) + { + return ForkProject( + this.Solution.WithProjectAssemblyName(projectId, assemblyName), + static (stateChange, assemblyName) => new CompilationAndGeneratorDriverTranslationAction.ProjectAssemblyNameAction(assemblyName), + forkTracker: true, + arg: assemblyName); + } + + /// + public SolutionCompilationState WithProjectOutputFilePath(ProjectId projectId, string? outputFilePath) + { + return ForkProject( + this.Solution.WithProjectOutputFilePath(projectId, outputFilePath), + translate: null, + forkTracker: true); + } + + /// + public SolutionCompilationState WithProjectOutputRefFilePath( + ProjectId projectId, string? outputRefFilePath) + { + return ForkProject( + this.Solution.WithProjectOutputRefFilePath(projectId, outputRefFilePath), + translate: null, + forkTracker: true); + } + + /// + public SolutionCompilationState WithProjectCompilationOutputInfo( + ProjectId projectId, in CompilationOutputInfo info) + { + return ForkProject( + this.Solution.WithProjectCompilationOutputInfo(projectId, info), + translate: null, + forkTracker: true); + } + + /// + public SolutionCompilationState WithProjectDefaultNamespace( + ProjectId projectId, string? defaultNamespace) + { + return ForkProject( + this.Solution.WithProjectDefaultNamespace(projectId, defaultNamespace), + translate: null, + forkTracker: true); + } + + /// + public SolutionCompilationState WithProjectChecksumAlgorithm( + ProjectId projectId, SourceHashAlgorithm checksumAlgorithm) + { + return ForkProject( + this.Solution.WithProjectChecksumAlgorithm(projectId, checksumAlgorithm), + static stateChange => new CompilationAndGeneratorDriverTranslationAction.ReplaceAllSyntaxTreesAction(stateChange.NewProjectState, isParseOptionChange: false), + forkTracker: true); + } + + /// + public SolutionCompilationState WithProjectName( + ProjectId projectId, string name) + { + return ForkProject( + this.Solution.WithProjectName(projectId, name), + translate: null, + forkTracker: true); + } + + /// + public SolutionCompilationState WithProjectFilePath( + ProjectId projectId, string? filePath) + { + return ForkProject( + this.Solution.WithProjectFilePath(projectId, filePath), + translate: null, + forkTracker: true); + } + + /// + public SolutionCompilationState WithProjectCompilationOptions( + ProjectId projectId, CompilationOptions options) + { + return ForkProject( + this.Solution.WithProjectCompilationOptions(projectId, options), + static stateChange => new CompilationAndGeneratorDriverTranslationAction.ProjectCompilationOptionsAction(stateChange.NewProjectState, isAnalyzerConfigChange: false), + forkTracker: true); + } + + /// + public SolutionCompilationState WithProjectParseOptions( + ProjectId projectId, ParseOptions options) + { + var stateChange = this.Solution.WithProjectParseOptions(projectId, options); + + if (this.PartialSemanticsEnabled) + { + // don't fork tracker with queued action since access via partial semantics can become inconsistent (throw). + // Since changing options is rare event, it is okay to start compilation building from scratch. + return ForkProject( + stateChange, + translate: null, + forkTracker: false); + } + else + { + return ForkProject( + stateChange, + static stateChange => new CompilationAndGeneratorDriverTranslationAction.ReplaceAllSyntaxTreesAction(stateChange.NewProjectState, isParseOptionChange: true), + forkTracker: true); + } + } + + /// + public SolutionCompilationState WithHasAllInformation( + ProjectId projectId, bool hasAllInformation) + { + return ForkProject( + this.Solution.WithHasAllInformation(projectId, hasAllInformation), + translate: null, + forkTracker: true); + } + + /// + public SolutionCompilationState WithRunAnalyzers( + ProjectId projectId, bool runAnalyzers) + { + return ForkProject( + this.Solution.WithRunAnalyzers(projectId, runAnalyzers), + translate: null, + forkTracker: true); + } + + /// + public SolutionCompilationState WithProjectDocumentsOrder( + ProjectId projectId, ImmutableList documentIds) + { + return ForkProject( + this.Solution.WithProjectDocumentsOrder(projectId, documentIds), + static stateChange => new CompilationAndGeneratorDriverTranslationAction.ReplaceAllSyntaxTreesAction(stateChange.NewProjectState, isParseOptionChange: false), + forkTracker: true); + } + + /// + public SolutionCompilationState AddProjectReferences( + ProjectId projectId, IReadOnlyCollection projectReferences) + { + return ForkProject( + this.Solution.AddProjectReferences(projectId, projectReferences), + translate: null, + forkTracker: true); + } + + /// + public SolutionCompilationState RemoveProjectReference(StateChange stateChange) + { + return ForkProject( + stateChange, + translate: null, + forkTracker: true); + } + + /// + public SolutionCompilationState WithProjectReferences( + ProjectId projectId, IReadOnlyList projectReferences) + { + return ForkProject( + this.Solution.WithProjectReferences(projectId, projectReferences), + translate: null, + forkTracker: true); + } + + /// + public SolutionCompilationState AddMetadataReferences( + ProjectId projectId, IReadOnlyCollection metadataReferences) + { + return ForkProject( + this.Solution.AddMetadataReferences(projectId, metadataReferences), + translate: null, + forkTracker: true); + } + + /// + public SolutionCompilationState RemoveMetadataReference(StateChange stateChange) + { + return ForkProject( + stateChange, + translate: null, + forkTracker: true); + } + + /// + public SolutionCompilationState WithProjectMetadataReferences( + ProjectId projectId, IReadOnlyList metadataReferences) + { + return ForkProject( + this.Solution.WithProjectMetadataReferences(projectId, metadataReferences), + translate: null, + forkTracker: true); + } + + /// + public SolutionCompilationState AddAnalyzerReferences(StateChange stateChange, ImmutableArray analyzerReferences) + { + return ForkProject( + stateChange, + static (stateChange, analyzerReferences) => new CompilationAndGeneratorDriverTranslationAction.AddOrRemoveAnalyzerReferencesAction( + stateChange.OldProjectState.Language, referencesToAdd: analyzerReferences), + forkTracker: true, + arg: analyzerReferences); + } + + public SolutionCompilationState AddAnalyzerReferences(SolutionState newSolutionState) + { + // Note: This is the codepath for adding analyzers from vsixes. Importantly, we do not ever get SGs added + // from this codepath, and as such we do not need to update the compilation trackers. The methods that add SGs + // all come from entrypoints that are specific to a particular project. + return Branch(newSolutionState); + } + + public SolutionCompilationState RemoveAnalyzerReference(SolutionState newSolutionState) + { + // Note: This is the codepath for adding analyzers from vsixes. Importantly, we do not ever get SGs added + // from this codepath, and as such we do not need to update the compilation trackers. The methods that add SGs + // all come from entrypoints that are specific to a particular project. + return Branch(newSolutionState); + } + + public SolutionCompilationState WithAnalyzerReferences(SolutionState newSolutionState) + { + // Note: This is the codepath for adding analyzers from vsixes. Importantly, we do not ever get SGs added + // from this codepath, and as such we do not need to update the compilation trackers. The methods that add SGs + // all come from entrypoints that are specific to a particular project. + return Branch(newSolutionState); + } + + /// + public SolutionCompilationState RemoveAnalyzerReference(StateChange stateChange, AnalyzerReference analyzerReference) + { + return ForkProject( + stateChange, + static (stateChange, analyzerReference) => new CompilationAndGeneratorDriverTranslationAction.AddOrRemoveAnalyzerReferencesAction( + stateChange.OldProjectState.Language, referencesToRemove: ImmutableArray.Create(analyzerReference)), + forkTracker: true, + arg: analyzerReference); + } + + /// + public SolutionCompilationState WithProjectAnalyzerReferences( + ProjectId projectId, IReadOnlyList analyzerReferences) + { + var stateChange = this.Solution.WithProjectAnalyzerReferences(projectId, analyzerReferences); + if (stateChange.NewSolutionState == this.Solution) + return this; + + // The .Except() methods here aren't going to terribly cheap, but the assumption is adding or removing just the generators + // we changed, rather than creating an entire new generator driver from scratch and rerunning all generators, is cheaper + // in the end. This was written without data backing up that assumption, so if a profile indicates to the contrary, + // this could be changed. + // + // When we're comparing AnalyzerReferences, we'll compare with reference equality; AnalyzerReferences like AnalyzerFileReference + // may implement their own equality, but that can result in things getting out of sync: two references that are value equal can still + // have their own generator instances; it's important that as we're adding and removing references that are value equal that we + // still update with the correct generator instances that are coming from the new reference that is actually held in the project state from above. + // An alternative approach would be to call oldProject.WithAnalyzerReferences keeping all the references in there that are value equal the same, + // but this avoids any surprises where other components calling WithAnalyzerReferences might not expect that. + + return ForkProject( + stateChange, + static stateChange => + { + var addedReferences = stateChange.NewProjectState.AnalyzerReferences.Except(stateChange.OldProjectState.AnalyzerReferences, ReferenceEqualityComparer.Instance).ToImmutableArray(); + var removedReferences = stateChange.OldProjectState.AnalyzerReferences.Except(stateChange.NewProjectState.AnalyzerReferences, ReferenceEqualityComparer.Instance).ToImmutableArray(); + + return new CompilationAndGeneratorDriverTranslationAction.AddOrRemoveAnalyzerReferencesAction( + stateChange.OldProjectState.Language, referencesToAdd: addedReferences, referencesToRemove: removedReferences); + }, + forkTracker: true); + } + + /// + public SolutionCompilationState WithDocumentName( + DocumentId documentId, string name) + { + return UpdateDocumentState( + this.Solution.WithDocumentName(documentId, name), documentId); + } + + /// + public SolutionCompilationState WithDocumentFolders( + DocumentId documentId, IReadOnlyList folders) + { + return UpdateDocumentState( + this.Solution.WithDocumentFolders(documentId, folders), documentId); + } + + /// + public SolutionCompilationState WithDocumentFilePath( + DocumentId documentId, string? filePath) + { + return UpdateDocumentState( + this.Solution.WithDocumentFilePath(documentId, filePath), documentId); + } + + /// + public SolutionCompilationState WithDocumentText( + DocumentId documentId, SourceText text, PreservationMode mode) + { + return UpdateDocumentState( + this.Solution.WithDocumentText(documentId, text, mode), documentId); + } + + /// + public SolutionCompilationState WithAdditionalDocumentText( + DocumentId documentId, SourceText text, PreservationMode mode) + { + return UpdateAdditionalDocumentState( + this.Solution.WithAdditionalDocumentText(documentId, text, mode), documentId); + } + + /// + public SolutionCompilationState WithAnalyzerConfigDocumentText( + DocumentId documentId, SourceText text, PreservationMode mode) + { + return UpdateAnalyzerConfigDocumentState(this.Solution.WithAnalyzerConfigDocumentText(documentId, text, mode)); + } + + /// + public SolutionCompilationState WithDocumentText( + DocumentId documentId, TextAndVersion textAndVersion, PreservationMode mode) + { + return UpdateDocumentState( + this.Solution.WithDocumentText(documentId, textAndVersion, mode), documentId); + } + + /// + public SolutionCompilationState WithAdditionalDocumentText( + DocumentId documentId, TextAndVersion textAndVersion, PreservationMode mode) + { + return UpdateAdditionalDocumentState( + this.Solution.WithAdditionalDocumentText(documentId, textAndVersion, mode), documentId); + } + + /// + public SolutionCompilationState WithAnalyzerConfigDocumentText( + DocumentId documentId, TextAndVersion textAndVersion, PreservationMode mode) + { + return UpdateAnalyzerConfigDocumentState( + this.Solution.WithAnalyzerConfigDocumentText(documentId, textAndVersion, mode)); + } + + /// + public SolutionCompilationState WithDocumentSyntaxRoot( + DocumentId documentId, SyntaxNode root, PreservationMode mode) + { + return UpdateDocumentState( + this.Solution.WithDocumentSyntaxRoot(documentId, root, mode), documentId); + } + + public SolutionCompilationState WithDocumentContentsFrom( + DocumentId documentId, DocumentState documentState) + { + return UpdateDocumentState( + this.Solution.WithDocumentContentsFrom(documentId, documentState), documentId); + } + + /// + public SolutionCompilationState WithDocumentSourceCodeKind( + DocumentId documentId, SourceCodeKind sourceCodeKind) + { + return UpdateDocumentState( + this.Solution.WithDocumentSourceCodeKind(documentId, sourceCodeKind), documentId); + } + + /// + public SolutionCompilationState UpdateDocumentTextLoader( + DocumentId documentId, TextLoader loader, PreservationMode mode) + { + var stateChange = this.Solution.UpdateDocumentTextLoader(documentId, loader, mode); + + // Note: state is currently not reused. + // If UpdateDocumentTextLoader is changed to reuse the state replace this assert with Solution instance reusal. + Debug.Assert(stateChange.NewSolutionState != this.Solution); + + // Assumes that content has changed. User could have closed a doc without saving and we are loading text + // from closed file with old content. + return UpdateDocumentState(stateChange, documentId); + } + + /// + public SolutionCompilationState UpdateAdditionalDocumentTextLoader( + DocumentId documentId, TextLoader loader, PreservationMode mode) + { + var stateChange = this.Solution.UpdateAdditionalDocumentTextLoader(documentId, loader, mode); + + // Note: state is currently not reused. + // If UpdateAdditionalDocumentTextLoader is changed to reuse the state replace this assert with Solution instance reusal. + Debug.Assert(stateChange.NewSolutionState != this.Solution); + + // Assumes that content has changed. User could have closed a doc without saving and we are loading text + // from closed file with old content. + return UpdateAdditionalDocumentState(stateChange, documentId); + } + + /// + public SolutionCompilationState UpdateAnalyzerConfigDocumentTextLoader( + DocumentId documentId, TextLoader loader, PreservationMode mode) + { + var stateChange = this.Solution.UpdateAnalyzerConfigDocumentTextLoader(documentId, loader, mode); + + // Note: state is currently not reused. + // If UpdateAnalyzerConfigDocumentTextLoader is changed to reuse the state replace this assert with Solution instance reusal. + Debug.Assert(stateChange.NewSolutionState != this.Solution); + + // Assumes that text has changed. User could have closed a doc without saving and we are loading text from closed file with + // old content. Also this should make sure we don't re-use latest doc version with data associated with opened document. + return UpdateAnalyzerConfigDocumentState(stateChange); + } + + private SolutionCompilationState UpdateDocumentState(StateChange stateChange, DocumentId documentId) + { + if (stateChange.NewSolutionState == this.Solution) + return this; + + // This method shouldn't have been called if the document has not changed. + Debug.Assert(stateChange.OldProjectState != stateChange.NewProjectState); + + return ForkProject( + stateChange, + static (stateChange, documentId) => + { + var oldDocument = stateChange.OldProjectState.DocumentStates.GetRequiredState(documentId); + var newDocument = stateChange.NewProjectState.DocumentStates.GetRequiredState(documentId); + + return new CompilationAndGeneratorDriverTranslationAction.TouchDocumentAction(oldDocument, newDocument); + }, + forkTracker: true, + arg: documentId); + } + + private SolutionCompilationState UpdateAdditionalDocumentState(StateChange stateChange, DocumentId documentId) + { + if (stateChange.NewSolutionState == this.Solution) + return this; + + // This method shouldn't have been called if the document has not changed.cument has not changed. + Debug.Assert(stateChange.OldProjectState != stateChange.NewProjectState); + + return ForkProject( + stateChange, + static (stateChange, documentId) => + { + var oldDocument = stateChange.OldProjectState.AdditionalDocumentStates.GetRequiredState(documentId); + var newDocument = stateChange.NewProjectState.AdditionalDocumentStates.GetRequiredState(documentId); + + return new CompilationAndGeneratorDriverTranslationAction.TouchAdditionalDocumentAction(oldDocument, newDocument); + }, + forkTracker: true, + arg: documentId); + } + + private SolutionCompilationState UpdateAnalyzerConfigDocumentState(StateChange stateChange) + { + return ForkProject( + stateChange, + static stateChange => stateChange.NewProjectState.CompilationOptions != null + ? new CompilationAndGeneratorDriverTranslationAction.ProjectCompilationOptionsAction(stateChange.NewProjectState, isAnalyzerConfigChange: true) + : null, + forkTracker: true); + } + + /// + /// Gets the associated with an assembly symbol. + /// + public static ProjectId? GetProjectId(IAssemblySymbol? assemblySymbol) + { + if (assemblySymbol == null) + return null; + + s_assemblyOrModuleSymbolToProjectMap.TryGetValue(assemblySymbol, out var id); + return id; + } + + private bool TryGetCompilationTracker(ProjectId projectId, [NotNullWhen(returnValue: true)] out ICompilationTracker? tracker) + => _projectIdToTrackerMap.TryGetValue(projectId, out tracker); + + private static readonly Func s_createCompilationTrackerFunction = CreateCompilationTracker; + + private static CompilationTracker CreateCompilationTracker(ProjectId projectId, SolutionState solution) + { + var projectState = solution.GetProjectState(projectId); + Contract.ThrowIfNull(projectState); + return new CompilationTracker(projectState); + } + + private ICompilationTracker GetCompilationTracker(ProjectId projectId) + { + if (!_projectIdToTrackerMap.TryGetValue(projectId, out var tracker)) + { + tracker = ImmutableInterlocked.GetOrAdd(ref _projectIdToTrackerMap, projectId, s_createCompilationTrackerFunction, this.Solution); + } + + return tracker; + } + + public Task GetDependentVersionAsync(ProjectId projectId, CancellationToken cancellationToken) + => this.GetCompilationTracker(projectId).GetDependentVersionAsync(this, cancellationToken); + + public Task GetDependentSemanticVersionAsync(ProjectId projectId, CancellationToken cancellationToken) + => this.GetCompilationTracker(projectId).GetDependentSemanticVersionAsync(this, cancellationToken); + + public Task GetDependentChecksumAsync(ProjectId projectId, CancellationToken cancellationToken) + => this.GetCompilationTracker(projectId).GetDependentChecksumAsync(this, cancellationToken); + + public bool TryGetCompilation(ProjectId projectId, [NotNullWhen(returnValue: true)] out Compilation? compilation) + { + this.Solution.CheckContainsProject(projectId); + compilation = null; + + return this.TryGetCompilationTracker(projectId, out var tracker) + && tracker.TryGetCompilation(out compilation); + } + + /// + /// Returns the compilation for the specified . Can return when the project + /// does not support compilations. + /// + /// + /// The compilation is guaranteed to have a syntax tree for each document of the project. + /// + private Task GetCompilationAsync(ProjectId projectId, CancellationToken cancellationToken) + { + // TODO: figure out where this is called and why the nullable suppression is required + return GetCompilationAsync(this.Solution.GetProjectState(projectId)!, cancellationToken); + } + + /// + /// Returns the compilation for the specified . Can return when the project + /// does not support compilations. + /// + /// + /// The compilation is guaranteed to have a syntax tree for each document of the project. + /// + public Task GetCompilationAsync(ProjectState project, CancellationToken cancellationToken) + { + return project.SupportsCompilation + ? GetCompilationTracker(project.Id).GetCompilationAsync(this, cancellationToken).AsNullable() + : SpecializedTasks.Null(); + } + + /// + /// Return reference completeness for the given project and all projects this references. + /// + public Task HasSuccessfullyLoadedAsync(ProjectState project, CancellationToken cancellationToken) + { + // return HasAllInformation when compilation is not supported. + // regardless whether project support compilation or not, if projectInfo is not complete, we can't guarantee its reference completeness + return project.SupportsCompilation + ? this.GetCompilationTracker(project.Id).HasSuccessfullyLoadedAsync(this, cancellationToken) + : project.HasAllInformation ? SpecializedTasks.True : SpecializedTasks.False; + } + + /// + /// Returns the generated document states for source generated documents. + /// + public ValueTask> GetSourceGeneratedDocumentStatesAsync( + ProjectState project, CancellationToken cancellationToken) + { + return project.SupportsCompilation + ? GetCompilationTracker(project.Id).GetSourceGeneratedDocumentStatesAsync(this, cancellationToken) + : new(TextDocumentStates.Empty); + } + + public ValueTask> GetSourceGeneratorDiagnosticsAsync( + ProjectState project, CancellationToken cancellationToken) + { + return project.SupportsCompilation + ? GetCompilationTracker(project.Id).GetSourceGeneratorDiagnosticsAsync(this, cancellationToken) + : new(ImmutableArray.Empty); + } + + /// + /// Returns the for a source generated document that has already been generated and observed. + /// + /// + /// This is only safe to call if you already have seen the SyntaxTree or equivalent that indicates the document state has already been + /// generated. This method exists to implement and is best avoided unless you're doing something + /// similarly tricky like that. + /// + public SourceGeneratedDocumentState? TryGetSourceGeneratedDocumentStateForAlreadyGeneratedId( + DocumentId documentId) + { + return GetCompilationTracker(documentId.ProjectId).TryGetSourceGeneratedDocumentStateForAlreadyGeneratedId(documentId); + } + + /// + /// Attempt to get the best readily available compilation for the project. It may be a + /// partially built compilation. + /// + private MetadataReference? GetPartialMetadataReference( + ProjectReference projectReference, + ProjectState fromProject) + { + // Try to get the compilation state for this project. If it doesn't exist, don't do any + // more work. + if (!_projectIdToTrackerMap.TryGetValue(projectReference.ProjectId, out var state)) + { + return null; + } + + return state.GetPartialMetadataReference(fromProject, projectReference); + } + + /// + /// Get a metadata reference to this compilation info's compilation with respect to + /// another project. For cross language references produce a skeletal assembly. If the + /// compilation is not available, it is built. If a skeletal assembly reference is + /// needed and does not exist, it is also built. + /// + private async Task GetMetadataReferenceAsync( + ICompilationTracker tracker, ProjectState fromProject, ProjectReference projectReference, CancellationToken cancellationToken) + { + try + { + // If same language then we can wrap the other project's compilation into a compilation reference + if (tracker.ProjectState.LanguageServices == fromProject.LanguageServices) + { + // otherwise, base it off the compilation by building it first. + var compilation = await tracker.GetCompilationAsync(this, cancellationToken).ConfigureAwait(false); + return compilation.ToMetadataReference(projectReference.Aliases, projectReference.EmbedInteropTypes); + } + + // otherwise get a metadata only image reference that is built by emitting the metadata from the + // referenced project's compilation and re-importing it. + using (Logger.LogBlock(FunctionId.Workspace_SkeletonAssembly_GetMetadataOnlyImage, cancellationToken)) + { + var properties = new MetadataReferenceProperties(aliases: projectReference.Aliases, embedInteropTypes: projectReference.EmbedInteropTypes); + return await tracker.SkeletonReferenceCache.GetOrBuildReferenceAsync( + tracker, this, properties, cancellationToken).ConfigureAwait(false); + } + } + catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken, ErrorSeverity.Critical)) + { + throw ExceptionUtilities.Unreachable(); + } + } + + /// + /// Get a metadata reference for the project's compilation. Returns upon failure, which + /// can happen when trying to build a skeleton reference that fails to build. + /// + public Task GetMetadataReferenceAsync( + ProjectReference projectReference, ProjectState fromProject, CancellationToken cancellationToken) + { + try + { + // Get the compilation state for this project. If it's not already created, then this + // will create it. Then force that state to completion and get a metadata reference to it. + var tracker = this.GetCompilationTracker(projectReference.ProjectId); + return GetMetadataReferenceAsync(tracker, fromProject, projectReference, cancellationToken); + } + catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken, ErrorSeverity.Critical)) + { + throw ExceptionUtilities.Unreachable(); + } + } + + /// + /// Undoes the operation of ; any frozen source generated document is allowed + /// to have it's real output again. + /// + public SolutionCompilationState WithoutFrozenSourceGeneratedDocuments() + { + // If there's nothing frozen, there's nothing to do. + if (_frozenSourceGeneratedDocumentState == null) + return this; + + var projectId = _frozenSourceGeneratedDocumentState.Id.ProjectId; + + // Since we previously froze this document, we should have a CompilationTracker entry for it, and it should be a + // GeneratedFileReplacingCompilationTracker. To undo the operation, we'll just restore the original CompilationTracker. + var newTrackerMap = CreateCompilationTrackerMap(projectId, this.Solution.GetProjectDependencyGraph()); + Contract.ThrowIfFalse(newTrackerMap.TryGetValue(projectId, out var existingTracker)); + var replacingItemTracker = existingTracker as GeneratedFileReplacingCompilationTracker; + Contract.ThrowIfNull(replacingItemTracker); + newTrackerMap = newTrackerMap.SetItem(projectId, replacingItemTracker.UnderlyingTracker); + + return this.Branch( + // TODO(cyrusn): Is it ok to preserve the same solution here? + this.Solution, + projectIdToTrackerMap: newTrackerMap, + frozenSourceGeneratedDocument: null); + } + + /// + /// Returns a new SolutionState that will always produce a specific output for a generated file. This is used only in the + /// implementation of where if a user has a source + /// generated file open, we need to make sure everything lines up. + /// + public SolutionCompilationState WithFrozenSourceGeneratedDocument( + SourceGeneratedDocumentIdentity documentIdentity, SourceText sourceText) + { + // We won't support freezing multiple source generated documents at once. Although nothing in the implementation + // of this method would have problems, this simplifies the handling of serializing this solution to out-of-proc. + // Since we only produce these snapshots from an open document, there should be no way to observe this, so this assertion + // also serves as a good check on the system. If down the road we need to support this, we can remove this check and + // update the out-of-process serialization logic accordingly. + Contract.ThrowIfTrue(_frozenSourceGeneratedDocumentState != null, "We shouldn't be calling WithFrozenSourceGeneratedDocument on a solution with a frozen source generated document."); + + var existingGeneratedState = TryGetSourceGeneratedDocumentStateForAlreadyGeneratedId(documentIdentity.DocumentId); + SourceGeneratedDocumentState newGeneratedState; + + if (existingGeneratedState != null) + { + newGeneratedState = existingGeneratedState + .WithText(sourceText) + .WithParseOptions(existingGeneratedState.ParseOptions); + + // If the content already matched, we can just reuse the existing state + if (newGeneratedState == existingGeneratedState) + { + return this; + } + } + else + { + var projectState = this.Solution.GetRequiredProjectState(documentIdentity.DocumentId.ProjectId); + newGeneratedState = SourceGeneratedDocumentState.Create( + documentIdentity, + sourceText, + projectState.ParseOptions!, + projectState.LanguageServices, + // Just compute the checksum from the source text passed in. + originalSourceTextChecksum: null); + } + + var projectId = documentIdentity.DocumentId.ProjectId; + var newTrackerMap = CreateCompilationTrackerMap(projectId, this.Solution.GetProjectDependencyGraph()); + + // We want to create a new snapshot with a new compilation tracker that will do this replacement. + // If we already have an existing tracker we'll just wrap that (so we also are reusing any underlying + // computations). If we don't have one, we'll create one and then wrap it. + if (!newTrackerMap.TryGetValue(projectId, out var existingTracker)) + { + existingTracker = CreateCompilationTracker(projectId, this.Solution); + } + + newTrackerMap = newTrackerMap.SetItem( + projectId, + new GeneratedFileReplacingCompilationTracker(existingTracker, newGeneratedState)); + + return this.Branch( + // TODO(cyrusn): Is it ok to just pass this.Solution along here? + this.Solution, + projectIdToTrackerMap: newTrackerMap, + frozenSourceGeneratedDocument: newGeneratedState); + } + + public SolutionCompilationState WithNewWorkspace(string? workspaceKind, int workspaceVersion, SolutionServices services) + { + return this.Branch( + this.Solution.WithNewWorkspace(workspaceKind, workspaceVersion, services)); + } + + public SolutionCompilationState WithOptions(SolutionOptionSet options) + { + return this.Branch( + this.Solution.WithOptions(options)); + } + + /// + /// Creates a branch of the solution that has its compilations frozen in whatever state they are in at the time, assuming a background compiler is + /// busy building this compilations. + /// + /// A compilation for the project containing the specified document id will be guaranteed to exist with at least the syntax tree for the document. + /// + /// This not intended to be the public API, use Document.WithFrozenPartialSemantics() instead. + /// + public SolutionCompilationState WithFrozenPartialCompilationIncludingSpecificDocument( + DocumentId documentId, CancellationToken cancellationToken) + { + try + { + var allDocumentIds = this.Solution.GetRelatedDocumentIds(documentId); + using var _ = ArrayBuilder<(DocumentState, SyntaxTree)>.GetInstance(allDocumentIds.Length, out var builder); + + foreach (var currentDocumentId in allDocumentIds) + { + var document = this.Solution.GetRequiredDocumentState(currentDocumentId); + builder.Add((document, document.GetSyntaxTree(cancellationToken))); + } + + using (this.StateLock.DisposableWait(cancellationToken)) + { + // in progress solutions are disabled for some testing + if (Services.GetService()?.IsPartialSolutionDisabled == true) + { + return this; + } + + SolutionCompilationState? currentPartialSolution = null; + _latestSolutionWithPartialCompilation?.TryGetTarget(out currentPartialSolution); + + var reuseExistingPartialSolution = + (DateTime.UtcNow - _timeOfLatestSolutionWithPartialCompilation).TotalSeconds < 0.1 && + _documentIdOfLatestSolutionWithPartialCompilation == documentId; + + if (reuseExistingPartialSolution && currentPartialSolution != null) + { + SolutionLogger.UseExistingPartialSolution(); + return currentPartialSolution; + } + + var newIdToProjectStateMap = this.Solution.ProjectStates; + var newIdToTrackerMap = _projectIdToTrackerMap; + + foreach (var (doc, tree) in builder) + { + // if we don't have one or it is stale, create a new partial solution + var tracker = this.GetCompilationTracker(doc.Id.ProjectId); + var newTracker = tracker.FreezePartialStateWithTree(this, doc, tree, cancellationToken); + + Contract.ThrowIfFalse(newIdToProjectStateMap.ContainsKey(doc.Id.ProjectId)); + newIdToProjectStateMap = newIdToProjectStateMap.SetItem(doc.Id.ProjectId, newTracker.ProjectState); + newIdToTrackerMap = newIdToTrackerMap.SetItem(doc.Id.ProjectId, newTracker); + } + + var newState = this.Solution.Branch( + idToProjectStateMap: newIdToProjectStateMap, + dependencyGraph: SolutionState.CreateDependencyGraph(this.Solution.ProjectIds, newIdToProjectStateMap)); + var newCompilationState = this.Branch( + newState, + newIdToTrackerMap); + + _latestSolutionWithPartialCompilation = new WeakReference(newCompilationState); + _timeOfLatestSolutionWithPartialCompilation = DateTime.UtcNow; + _documentIdOfLatestSolutionWithPartialCompilation = documentId; + + SolutionLogger.CreatePartialSolution(); + return newCompilationState; + } + } + catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken, ErrorSeverity.Critical)) + { + throw ExceptionUtilities.Unreachable(); + } + } + + public SolutionCompilationState AddDocuments(ImmutableArray documentInfos) + { + return AddDocumentsToMultipleProjects(documentInfos, + (documentInfo, project) => project.CreateDocument(documentInfo, project.ParseOptions, new LoadTextOptions(project.ChecksumAlgorithm)), + (oldProject, documents) => (oldProject.AddDocuments(documents), new CompilationAndGeneratorDriverTranslationAction.AddDocumentsAction(documents))); + } + + public SolutionCompilationState AddAdditionalDocuments(ImmutableArray documentInfos) + { + return AddDocumentsToMultipleProjects(documentInfos, + (documentInfo, project) => new AdditionalDocumentState(Services, documentInfo, new LoadTextOptions(project.ChecksumAlgorithm)), + (projectState, documents) => (projectState.AddAdditionalDocuments(documents), new CompilationAndGeneratorDriverTranslationAction.AddAdditionalDocumentsAction(documents))); + } + + public SolutionCompilationState AddAnalyzerConfigDocuments(ImmutableArray documentInfos) + { + return AddDocumentsToMultipleProjects(documentInfos, + (documentInfo, project) => new AnalyzerConfigDocumentState(Services, documentInfo, new LoadTextOptions(project.ChecksumAlgorithm)), + (oldProject, documents) => + { + var newProject = oldProject.AddAnalyzerConfigDocuments(documents); + return (newProject, new CompilationAndGeneratorDriverTranslationAction.ProjectCompilationOptionsAction(newProject, isAnalyzerConfigChange: true)); + }); + } + + public SolutionCompilationState RemoveDocuments(ImmutableArray documentIds) + { + return RemoveDocumentsFromMultipleProjects(documentIds, + (projectState, documentId) => projectState.DocumentStates.GetRequiredState(documentId), + (projectState, documentIds, documentStates) => (projectState.RemoveDocuments(documentIds), new CompilationAndGeneratorDriverTranslationAction.RemoveDocumentsAction(documentStates))); + } + + public SolutionCompilationState RemoveAdditionalDocuments(ImmutableArray documentIds) + { + return RemoveDocumentsFromMultipleProjects(documentIds, + (projectState, documentId) => projectState.AdditionalDocumentStates.GetRequiredState(documentId), + (projectState, documentIds, documentStates) => (projectState.RemoveAdditionalDocuments(documentIds), new CompilationAndGeneratorDriverTranslationAction.RemoveAdditionalDocumentsAction(documentStates))); + } + + public SolutionCompilationState RemoveAnalyzerConfigDocuments(ImmutableArray documentIds) + { + return RemoveDocumentsFromMultipleProjects(documentIds, + (projectState, documentId) => projectState.AnalyzerConfigDocumentStates.GetRequiredState(documentId), + (oldProject, documentIds, _) => + { + var newProject = oldProject.RemoveAnalyzerConfigDocuments(documentIds); + return (newProject, new CompilationAndGeneratorDriverTranslationAction.ProjectCompilationOptionsAction(newProject, isAnalyzerConfigChange: true)); + }); + } + + /// + /// Core helper that takes a set of s and does the application of the appropriate documents to each project. + /// + /// The set of documents to add. + /// Returns the new with the documents added, and the needed as well. + /// + private SolutionCompilationState AddDocumentsToMultipleProjects( + ImmutableArray documentInfos, + Func createDocumentState, + Func, (ProjectState newState, CompilationAndGeneratorDriverTranslationAction translationAction)> addDocumentsToProjectState) + where T : TextDocumentState + { + if (documentInfos.IsDefault) + { + throw new ArgumentNullException(nameof(documentInfos)); + } + + if (documentInfos.IsEmpty) + { + return this; + } + + // The documents might be contributing to multiple different projects; split them by project and then we'll process + // project-at-a-time. + var documentInfosByProjectId = documentInfos.ToLookup(d => d.Id.ProjectId); + + var newCompilationState = this; + + foreach (var documentInfosInProject in documentInfosByProjectId) + { + this.Solution.CheckContainsProject(documentInfosInProject.Key); + var oldProjectState = this.Solution.GetProjectState(documentInfosInProject.Key)!; + + var newDocumentStatesForProjectBuilder = ArrayBuilder.GetInstance(); + + foreach (var documentInfo in documentInfosInProject) + { + newDocumentStatesForProjectBuilder.Add(createDocumentState(documentInfo, oldProjectState)); + } + + var newDocumentStatesForProject = newDocumentStatesForProjectBuilder.ToImmutableAndFree(); + + var (newProjectState, compilationTranslationAction) = addDocumentsToProjectState(oldProjectState, newDocumentStatesForProject); + + var stateChange = newCompilationState.Solution.ForkProject( + oldProjectState, + newProjectState, + // intentionally accessing this.Solution here not newSolutionState + newFilePathToDocumentIdsMap: this.Solution.CreateFilePathToDocumentIdsMapWithAddedDocuments(newDocumentStatesForProject)); + + newCompilationState = newCompilationState.ForkProject( + stateChange, + static (_, compilationTranslationAction) => compilationTranslationAction, + forkTracker: true, + arg: compilationTranslationAction); + } + + return newCompilationState; + } + + private SolutionCompilationState RemoveDocumentsFromMultipleProjects( + ImmutableArray documentIds, + Func getExistingTextDocumentState, + Func, ImmutableArray, (ProjectState newState, SolutionCompilationState.CompilationAndGeneratorDriverTranslationAction translationAction)> removeDocumentsFromProjectState) + where T : TextDocumentState + { + if (documentIds.IsEmpty) + { + return this; + } + + // The documents might be contributing to multiple different projects; split them by project and then we'll process + // project-at-a-time. + var documentIdsByProjectId = documentIds.ToLookup(id => id.ProjectId); + + var newCompilationState = this; + + foreach (var documentIdsInProject in documentIdsByProjectId) + { + var oldProjectState = this.Solution.GetProjectState(documentIdsInProject.Key); + + if (oldProjectState == null) + { + throw new InvalidOperationException(string.Format(WorkspacesResources._0_is_not_part_of_the_workspace, documentIdsInProject.Key)); + } + + var removedDocumentStatesBuilder = ArrayBuilder.GetInstance(); + + foreach (var documentId in documentIdsInProject) + { + removedDocumentStatesBuilder.Add(getExistingTextDocumentState(oldProjectState, documentId)); + } + + var removedDocumentStatesForProject = removedDocumentStatesBuilder.ToImmutableAndFree(); + + var (newProjectState, compilationTranslationAction) = removeDocumentsFromProjectState(oldProjectState, documentIdsInProject.ToImmutableArray(), removedDocumentStatesForProject); + + var stateChange = newCompilationState.Solution.ForkProject( + oldProjectState, + newProjectState, + // Intentionally using this.Solution here and not newSolutionState + newFilePathToDocumentIdsMap: this.Solution.CreateFilePathToDocumentIdsMapWithRemovedDocuments(removedDocumentStatesForProject)); + + newCompilationState = newCompilationState.ForkProject( + stateChange, + static (_, compilationTranslationAction) => compilationTranslationAction, + forkTracker: true, + arg: compilationTranslationAction); + } + + return newCompilationState; + } + + /// + public SolutionCompilationState WithCachedSourceGeneratorState(ProjectId projectToUpdate, Project projectWithCachedGeneratorState) + { + this.Solution.CheckContainsProject(projectToUpdate); + + // First see if we have a generator driver that we can get from the other project. + + if (!projectWithCachedGeneratorState.Solution.CompilationState.TryGetCompilationTracker(projectWithCachedGeneratorState.Id, out var tracker) || + tracker.GeneratorDriver is null) + { + // We don't actually have any state at all, so no change. + return this; + } + + var projectToUpdateState = this.Solution.GetRequiredProjectState(projectToUpdate); + + // Note: we have to force this fork to happen as the actual solution-state object is not changing. We're just + // changing the tracker for a particular project. + var newCompilationState = this.ForceForkProject( + new(this.Solution, projectToUpdateState, projectToUpdateState), + translate: new CompilationAndGeneratorDriverTranslationAction.ReplaceGeneratorDriverAction( + tracker.GeneratorDriver, + newProjectState: projectToUpdateState), + forkTracker: true); + + return newCompilationState; + } + + /// + /// Creates a new solution instance with all the documents specified updated to have the same specified text. + /// + public SolutionCompilationState WithDocumentText(IEnumerable documentIds, SourceText text, PreservationMode mode) + { + var result = this; + + foreach (var documentId in documentIds) + { + // This API has always allowed null document IDs and documents IDs not contained within the solution. So + // skip those if we run into that (otherwise the call to WithDocumentText will throw, as it is more + // restrictive). + if (documentId is null) + continue; + + var documentState = this.Solution.GetProjectState(documentId.ProjectId)?.DocumentStates.GetState(documentId); + if (documentState != null) + result = result.WithDocumentText(documentId, text, mode); + } + + return result; + } + + internal TestAccessor GetTestAccessor() + => new(this); + + internal readonly struct TestAccessor(SolutionCompilationState solutionState) + { + public GeneratorDriver? GetGeneratorDriver(Project project) + => project.SupportsCompilation ? solutionState.GetCompilationTracker(project.Id).GeneratorDriver : null; + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationAndGeneratorDriverTranslationAction.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationAndGeneratorDriverTranslationAction.cs index 92e824fcc101b..ceb53cbb3e325 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationAndGeneratorDriverTranslationAction.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationAndGeneratorDriverTranslationAction.cs @@ -7,7 +7,7 @@ namespace Microsoft.CodeAnalysis { - internal partial class SolutionState + internal partial class SolutionCompilationState { /// /// Represents a change that needs to be made to a , , or both in response to diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationAndGeneratorDriverTranslationAction_Actions.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationAndGeneratorDriverTranslationAction_Actions.cs index e65148e36a524..0ca09911f0e44 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationAndGeneratorDriverTranslationAction_Actions.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationAndGeneratorDriverTranslationAction_Actions.cs @@ -2,19 +2,16 @@ // 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.Collections.Generic; using System.Collections.Immutable; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.ErrorReporting; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis { - internal partial class SolutionState + internal partial class SolutionCompilationState { private abstract partial class CompilationAndGeneratorDriverTranslationAction { @@ -23,9 +20,11 @@ internal sealed class TouchDocumentAction(DocumentState oldState, DocumentState private readonly DocumentState _oldState = oldState; private readonly DocumentState _newState = newState; - public override Task TransformCompilationAsync(Compilation oldCompilation, CancellationToken cancellationToken) + public override async Task TransformCompilationAsync(Compilation oldCompilation, CancellationToken cancellationToken) { - return UpdateDocumentInCompilationAsync(oldCompilation, _oldState, _newState, cancellationToken); + return oldCompilation.ReplaceSyntaxTree( + await _oldState.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false), + await _newState.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false)); } public DocumentId DocumentId => _newState.Attributes.Id; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationPair.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationPair.cs index 08d010013e74d..3e6725dc53caf 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationPair.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationPair.cs @@ -7,7 +7,7 @@ namespace Microsoft.CodeAnalysis { - internal partial class SolutionState + internal partial class SolutionCompilationState { private partial class CompilationTracker { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.CompilationTrackerState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.CompilationTrackerState.cs index a868673465012..c6e33f59a0f25 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.CompilationTrackerState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.CompilationTrackerState.cs @@ -12,7 +12,7 @@ namespace Microsoft.CodeAnalysis { - internal partial class SolutionState + internal partial class SolutionCompilationState { private partial class CompilationTracker { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs index 4c18f116aaa1f..bd6e295cf1895 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker.cs @@ -23,7 +23,7 @@ namespace Microsoft.CodeAnalysis { - internal partial class SolutionState + internal partial class SolutionCompilationState { /// /// Tracks the changes made to a project and provides the facility to get a lazily built @@ -176,10 +176,14 @@ public ICompilationTracker Fork( } } - public ICompilationTracker FreezePartialStateWithTree(SolutionState solution, DocumentState docState, SyntaxTree tree, CancellationToken cancellationToken) + public ICompilationTracker FreezePartialStateWithTree( + SolutionCompilationState compilationState, + DocumentState docState, + SyntaxTree tree, + CancellationToken cancellationToken) { GetPartialCompilationState( - solution, docState.Id, + compilationState, docState.Id, out var inProgressProject, out var compilationPair, out var generatorInfo, @@ -262,16 +266,14 @@ public ICompilationTracker FreezePartialStateWithTree(SolutionState solution, Do } /// - /// Tries to get the latest snapshot of the compilation without waiting for it to be - /// fully built. This method takes advantage of the progress side-effect produced during - /// . - /// It will either return the already built compilation, any - /// in-progress compilation or any known old compilation in that order of preference. - /// The compilation state that is returned will have a compilation that is retained so - /// that it cannot disappear. + /// Tries to get the latest snapshot of the compilation without waiting for it to be fully built. This + /// method takes advantage of the progress side-effect produced during . It will either return the already built compilation, any in-progress + /// compilation or any known old compilation in that order of preference. The compilation state that is + /// returned will have a compilation that is retained so that it cannot disappear. /// private void GetPartialCompilationState( - SolutionState solution, + SolutionCompilationState compilationState, DocumentId id, out ProjectState inProgressProject, out CompilationPair compilations, @@ -349,12 +351,13 @@ private void GetPartialCompilationState( foreach (var projectReference in this.ProjectState.ProjectReferences) { - var referencedProject = solution.GetProjectState(projectReference.ProjectId); + var referencedProject = compilationState.Solution.GetProjectState(projectReference.ProjectId); if (referencedProject != null) { if (referencedProject.IsSubmission) { - var previousScriptCompilation = solution.GetCompilationAsync(projectReference.ProjectId, cancellationToken).WaitAndGetResult(cancellationToken); + var previousScriptCompilation = compilationState.GetCompilationAsync( + projectReference.ProjectId, cancellationToken).WaitAndGetResult(cancellationToken); // previous submission project must support compilation: RoslynDebug.Assert(previousScriptCompilation != null); @@ -364,14 +367,14 @@ private void GetPartialCompilationState( else { // get the latest metadata for the partial compilation of the referenced project. - var metadata = solution.GetPartialMetadataReference(projectReference, this.ProjectState); + var metadata = compilationState.GetPartialMetadataReference(projectReference, this.ProjectState); if (metadata == null) { // if we failed to get the metadata, check to see if we previously had existing metadata and reuse it instead. var inProgressCompilationNotRef = compilations.CompilationWithGeneratedDocuments; metadata = inProgressCompilationNotRef.ExternalReferences.FirstOrDefault( - r => solution.GetProjectState(inProgressCompilationNotRef.GetAssemblyOrModuleSymbol(r) as IAssemblySymbol)?.Id == projectReference.ProjectId); + r => SolutionCompilationState.GetProjectId(inProgressCompilationNotRef.GetAssemblyOrModuleSymbol(r) as IAssemblySymbol) == projectReference.ProjectId); } if (metadata != null) @@ -408,7 +411,7 @@ public bool TryGetCompilation([NotNullWhen(true)] out Compilation? compilation) return compilation != null; } - public Task GetCompilationAsync(SolutionState solution, CancellationToken cancellationToken) + public Task GetCompilationAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken) { if (this.TryGetCompilation(out var compilation)) { @@ -423,18 +426,19 @@ public Task GetCompilationAsync(SolutionState solution, Cancellatio } else { - return GetCompilationSlowAsync(solution, cancellationToken); + return GetCompilationSlowAsync(compilationState, cancellationToken); } } - private async Task GetCompilationSlowAsync(SolutionState solution, CancellationToken cancellationToken) + private async Task GetCompilationSlowAsync( + SolutionCompilationState compilationState, CancellationToken cancellationToken) { - var compilationInfo = await GetOrBuildCompilationInfoAsync(solution, lockGate: true, cancellationToken: cancellationToken).ConfigureAwait(false); + var compilationInfo = await GetOrBuildCompilationInfoAsync(compilationState, lockGate: true, cancellationToken: cancellationToken).ConfigureAwait(false); return compilationInfo.Compilation; } private async Task GetOrBuildCompilationInfoAsync( - SolutionState solution, + SolutionCompilationState compilationState, bool lockGate, CancellationToken cancellationToken) { @@ -461,12 +465,12 @@ private async Task GetOrBuildCompilationInfoAsync( { using (await _buildLock.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { - return await BuildCompilationInfoAsync(solution, cancellationToken).ConfigureAwait(false); + return await BuildCompilationInfoAsync(compilationState, cancellationToken).ConfigureAwait(false); } } else { - return await BuildCompilationInfoAsync(solution, cancellationToken).ConfigureAwait(false); + return await BuildCompilationInfoAsync(compilationState, cancellationToken).ConfigureAwait(false); } } } @@ -481,7 +485,7 @@ private async Task GetOrBuildCompilationInfoAsync( /// produce in progress snapshots that can be accessed from other threads. /// private async Task BuildCompilationInfoAsync( - SolutionState solution, + SolutionCompilationState compilationState, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -503,7 +507,7 @@ private async Task BuildCompilationInfoAsync( { // We've got nothing. Build it from scratch :( return await BuildCompilationInfoFromScratchAsync( - solution, + compilationState, state.GeneratorInfo, cancellationToken).ConfigureAwait(false); } @@ -512,7 +516,7 @@ private async Task BuildCompilationInfoAsync( { // We have a declaration compilation, use it to reconstruct the final compilation return await FinalizeCompilationAsync( - solution, + compilationState, compilation, state.GeneratorInfo, compilationWithStaleGeneratedTrees: null, @@ -522,12 +526,12 @@ private async Task BuildCompilationInfoAsync( { // We must have an in progress compilation. Build off of that. return await BuildFinalStateFromInProgressStateAsync( - solution, (InProgressState)state, compilation, cancellationToken).ConfigureAwait(false); + compilationState, (InProgressState)state, compilation, cancellationToken).ConfigureAwait(false); } } private async Task BuildCompilationInfoFromScratchAsync( - SolutionState solution, + SolutionCompilationState compilationState, CompilationTrackerGeneratorInfo generatorInfo, CancellationToken cancellationToken) { @@ -537,7 +541,7 @@ private async Task BuildCompilationInfoFromScratchAsync( generatorInfo, cancellationToken).ConfigureAwait(false); return await FinalizeCompilationAsync( - solution, + compilationState, compilation, generatorInfo, compilationWithStaleGeneratedTrees: null, @@ -598,7 +602,7 @@ private Compilation CreateEmptyCompilation() } private async Task BuildFinalStateFromInProgressStateAsync( - SolutionState solution, InProgressState state, Compilation inProgressCompilation, CancellationToken cancellationToken) + SolutionCompilationState compilationState, InProgressState state, Compilation inProgressCompilation, CancellationToken cancellationToken) { try { @@ -606,7 +610,7 @@ private async Task BuildFinalStateFromInProgressStateAsync( state, inProgressCompilation, cancellationToken).ConfigureAwait(false); return await FinalizeCompilationAsync( - solution, + compilationState, compilationWithoutGenerators, state.GeneratorInfo.WithDriver(generatorDriver), compilationWithGenerators, @@ -702,7 +706,7 @@ private readonly struct CompilationInfo(Compilation compilation, bool hasSuccess /// changes to references, we can then use this compilation instead of re-adding source generated files again to the /// . private async Task FinalizeCompilationAsync( - SolutionState solution, + SolutionCompilationState compilationState, Compilation compilationWithoutGeneratedFiles, CompilationTrackerGeneratorInfo generatorInfo, Compilation? compilationWithStaleGeneratedTrees, @@ -724,7 +728,7 @@ private async Task FinalizeCompilationAsync( foreach (var projectReference in this.ProjectState.ProjectReferences) { - var referencedProject = solution.GetProjectState(projectReference.ProjectId); + var referencedProject = compilationState.Solution.GetProjectState(projectReference.ProjectId); // Even though we're creating a final compilation (vs. an in progress compilation), // it's possible that the target project has been removed. @@ -740,7 +744,8 @@ private async Task FinalizeCompilationAsync( // We now need to (potentially) update the prior submission compilation. That Compilation is held in the // ScriptCompilationInfo that we need to replace as a unit. var previousSubmissionCompilation = - await solution.GetCompilationAsync(projectReference.ProjectId, cancellationToken).ConfigureAwait(false); + await compilationState.GetCompilationAsync( + projectReference.ProjectId, cancellationToken).ConfigureAwait(false); if (compilationWithoutGeneratedFiles.ScriptCompilationInfo!.PreviousScriptCompilation != previousSubmissionCompilation) { @@ -753,7 +758,7 @@ private async Task FinalizeCompilationAsync( } else { - var metadataReference = await solution.GetMetadataReferenceAsync( + var metadataReference = await compilationState.GetMetadataReferenceAsync( projectReference, this.ProjectState, cancellationToken).ConfigureAwait(false); // A reference can fail to be created if a skeleton assembly could not be constructed. @@ -783,7 +788,7 @@ private async Task FinalizeCompilationAsync( // We will finalize the compilation by adding full contents here. var (compilationWithGeneratedFiles, nextGeneratorInfo) = await AddExistingOrComputeNewGeneratorInfoAsync( - solution, + compilationState, compilationWithoutGeneratedFiles, generatorInfo, compilationWithStaleGeneratedTrees, @@ -848,7 +853,8 @@ private async Task FinalizeCompilationAsync( return null; } - public Task HasSuccessfullyLoadedAsync(SolutionState solution, CancellationToken cancellationToken) + public Task HasSuccessfullyLoadedAsync( + SolutionCompilationState compilationState, CancellationToken cancellationToken) { var state = this.ReadState(); @@ -858,17 +864,20 @@ public Task HasSuccessfullyLoadedAsync(SolutionState solution, Cancellatio } else { - return HasSuccessfullyLoadedSlowAsync(solution, cancellationToken); + return HasSuccessfullyLoadedSlowAsync(compilationState, cancellationToken); } } - private async Task HasSuccessfullyLoadedSlowAsync(SolutionState solution, CancellationToken cancellationToken) + private async Task HasSuccessfullyLoadedSlowAsync( + SolutionCompilationState compilationState, CancellationToken cancellationToken) { - var compilationInfo = await GetOrBuildCompilationInfoAsync(solution, lockGate: true, cancellationToken: cancellationToken).ConfigureAwait(false); + var compilationInfo = await GetOrBuildCompilationInfoAsync( + compilationState, lockGate: true, cancellationToken: cancellationToken).ConfigureAwait(false); return compilationInfo.HasSuccessfullyLoaded; } - public async ValueTask> GetSourceGeneratedDocumentStatesAsync(SolutionState solution, CancellationToken cancellationToken) + public async ValueTask> GetSourceGeneratedDocumentStatesAsync( + SolutionCompilationState compilationState, CancellationToken cancellationToken) { // If we don't have any generators, then we know we have no generated files, so we can skip the computation entirely. if (!this.ProjectState.SourceGenerators.Any()) @@ -876,18 +885,21 @@ public async ValueTask> GetSour return TextDocumentStates.Empty; } - var compilationInfo = await GetOrBuildCompilationInfoAsync(solution, lockGate: true, cancellationToken: cancellationToken).ConfigureAwait(false); + var compilationInfo = await GetOrBuildCompilationInfoAsync( + compilationState, lockGate: true, cancellationToken: cancellationToken).ConfigureAwait(false); return compilationInfo.GeneratorInfo.Documents; } - public async ValueTask> GetSourceGeneratorDiagnosticsAsync(SolutionState solution, CancellationToken cancellationToken) + public async ValueTask> GetSourceGeneratorDiagnosticsAsync( + SolutionCompilationState compilationState, CancellationToken cancellationToken) { if (!this.ProjectState.SourceGenerators.Any()) { return ImmutableArray.Empty; } - var compilationInfo = await GetOrBuildCompilationInfoAsync(solution, lockGate: true, cancellationToken: cancellationToken).ConfigureAwait(false); + var compilationInfo = await GetOrBuildCompilationInfoAsync( + compilationState, lockGate: true, cancellationToken: cancellationToken).ConfigureAwait(false); var driverRunResult = compilationInfo.GeneratorInfo.Driver?.GetRunResult(); if (driverRunResult is null) @@ -1036,19 +1048,23 @@ public CompilationTrackerValidationException(string message, Exception inner) : private AsyncLazy? _lazyDependentSemanticVersion; private AsyncLazy? _lazyDependentChecksum; - public Task GetDependentVersionAsync(SolutionState solution, CancellationToken cancellationToken) + public Task GetDependentVersionAsync( + SolutionCompilationState compilationState, CancellationToken cancellationToken) { if (_lazyDependentVersion == null) { - var tmp = solution; // temp. local to avoid a closure allocation for the fast path + // temp. local to avoid a closure allocation for the fast path // note: solution is captured here, but it will go away once GetValueAsync executes. - Interlocked.CompareExchange(ref _lazyDependentVersion, AsyncLazy.Create(c => ComputeDependentVersionAsync(tmp, c)), null); + var compilationStateCapture = compilationState; + Interlocked.CompareExchange(ref _lazyDependentVersion, AsyncLazy.Create( + c => ComputeDependentVersionAsync(compilationStateCapture, c)), null); } return _lazyDependentVersion.GetValueAsync(cancellationToken); } - private async Task ComputeDependentVersionAsync(SolutionState solution, CancellationToken cancellationToken) + private async Task ComputeDependentVersionAsync( + SolutionCompilationState compilationState, CancellationToken cancellationToken) { var projectState = this.ProjectState; var projVersion = projectState.Version; @@ -1059,9 +1075,9 @@ private async Task ComputeDependentVersionAsync(SolutionState solu { cancellationToken.ThrowIfCancellationRequested(); - if (solution.ContainsProject(dependentProjectReference.ProjectId)) + if (compilationState.Solution.ContainsProject(dependentProjectReference.ProjectId)) { - var dependentProjectVersion = await solution.GetDependentVersionAsync(dependentProjectReference.ProjectId, cancellationToken).ConfigureAwait(false); + var dependentProjectVersion = await compilationState.GetDependentVersionAsync(dependentProjectReference.ProjectId, cancellationToken).ConfigureAwait(false); version = dependentProjectVersion.GetNewerVersion(version); } } @@ -1069,19 +1085,23 @@ private async Task ComputeDependentVersionAsync(SolutionState solu return version; } - public Task GetDependentSemanticVersionAsync(SolutionState solution, CancellationToken cancellationToken) + public Task GetDependentSemanticVersionAsync( + SolutionCompilationState compilationState, CancellationToken cancellationToken) { if (_lazyDependentSemanticVersion == null) { - var tmp = solution; // temp. local to avoid a closure allocation for the fast path + // temp. local to avoid a closure allocation for the fast path // note: solution is captured here, but it will go away once GetValueAsync executes. - Interlocked.CompareExchange(ref _lazyDependentSemanticVersion, AsyncLazy.Create(c => ComputeDependentSemanticVersionAsync(tmp, c)), null); + var compilationStateCapture = compilationState; + Interlocked.CompareExchange(ref _lazyDependentSemanticVersion, AsyncLazy.Create( + c => ComputeDependentSemanticVersionAsync(compilationStateCapture, c)), null); } return _lazyDependentSemanticVersion.GetValueAsync(cancellationToken); } - private async Task ComputeDependentSemanticVersionAsync(SolutionState solution, CancellationToken cancellationToken) + private async Task ComputeDependentSemanticVersionAsync( + SolutionCompilationState compilationState, CancellationToken cancellationToken) { var projectState = this.ProjectState; var version = await projectState.GetSemanticVersionAsync(cancellationToken).ConfigureAwait(false); @@ -1090,9 +1110,10 @@ private async Task ComputeDependentSemanticVersionAsync(SolutionSt { cancellationToken.ThrowIfCancellationRequested(); - if (solution.ContainsProject(dependentProjectReference.ProjectId)) + if (compilationState.Solution.ContainsProject(dependentProjectReference.ProjectId)) { - var dependentProjectVersion = await solution.GetDependentSemanticVersionAsync(dependentProjectReference.ProjectId, cancellationToken).ConfigureAwait(false); + var dependentProjectVersion = await compilationState.GetDependentSemanticVersionAsync( + dependentProjectReference.ProjectId, cancellationToken).ConfigureAwait(false); version = dependentProjectVersion.GetNewerVersion(version); } } @@ -1100,11 +1121,12 @@ private async Task ComputeDependentSemanticVersionAsync(SolutionSt return version; } - public Task GetDependentChecksumAsync(SolutionState solution, CancellationToken cancellationToken) + public Task GetDependentChecksumAsync( + SolutionCompilationState compilationState, CancellationToken cancellationToken) { if (_lazyDependentChecksum == null) { - var tmp = solution; // temp. local to avoid a closure allocation for the fast path + var tmp = compilationState.Solution; // temp. local to avoid a closure allocation for the fast path // note: solution is captured here, but it will go away once GetValueAsync executes. Interlocked.CompareExchange(ref _lazyDependentChecksum, AsyncLazy.Create(c => ComputeDependentChecksumAsync(tmp, c)), null); } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker_Generators.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker_Generators.cs index f64ca274d1837..4e2ce972286c3 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker_Generators.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTracker_Generators.cs @@ -22,12 +22,12 @@ namespace Microsoft.CodeAnalysis; -internal partial class SolutionState +internal partial class SolutionCompilationState { private partial class CompilationTracker : ICompilationTracker { private async Task<(Compilation compilationWithGeneratedFiles, CompilationTrackerGeneratorInfo generatorInfo)> AddExistingOrComputeNewGeneratorInfoAsync( - SolutionState solution, + SolutionCompilationState solution, Compilation compilationWithoutGeneratedFiles, CompilationTrackerGeneratorInfo generatorInfo, Compilation? compilationWithStaleGeneratedTrees, @@ -65,7 +65,7 @@ await generatorInfo.Documents.States.Values.SelectAsArrayAsync( } private async Task<(Compilation compilationWithGeneratedFiles, CompilationTrackerGeneratorInfo generatorInfo)> ComputeNewGeneratorInfoAsync( - SolutionState solution, + SolutionCompilationState solution, Compilation compilationWithoutGeneratedFiles, CompilationTrackerGeneratorInfo generatorInfo, Compilation? compilationWithStaleGeneratedTrees, @@ -80,17 +80,19 @@ await generatorInfo.Documents.States.Values.SelectAsArrayAsync( return result.Value; // If that failed (OOP crash, or we are the OOP process ourselves), then generate the SG docs locally. + var telemetryCollector = solution.Solution.Services.GetService(); return await ComputeNewGeneratorInfoInCurrentProcessAsync( - solution, compilationWithoutGeneratedFiles, generatorInfo, compilationWithStaleGeneratedTrees, cancellationToken).ConfigureAwait(false); + telemetryCollector, compilationWithoutGeneratedFiles, generatorInfo, compilationWithStaleGeneratedTrees, cancellationToken).ConfigureAwait(false); } private async Task<(Compilation compilationWithGeneratedFiles, CompilationTrackerGeneratorInfo generatorInfo)?> TryComputeNewGeneratorInfoInRemoteProcessAsync( - SolutionState solution, + SolutionCompilationState compilationState, Compilation compilationWithoutGeneratedFiles, CompilationTrackerGeneratorInfo generatorInfo, Compilation? compilationWithStaleGeneratedTrees, CancellationToken cancellationToken) { + var solution = compilationState.Solution; var options = solution.Services.GetRequiredService().Options; if (options.RunSourceGeneratorsInSameProcessOnly) return null; @@ -104,12 +106,12 @@ await generatorInfo.Documents.States.Values.SelectAsArrayAsync( // throughout the calls. var listenerProvider = solution.Services.ExportProvider.GetExports().First().Value; using var connection = client.CreateConnection(callbackTarget: null); - using var _ = RemoteKeepAliveSession.Create(solution, listenerProvider.GetListener(FeatureAttribute.Workspace)); + using var _ = RemoteKeepAliveSession.Create(compilationState, listenerProvider.GetListener(FeatureAttribute.Workspace)); // First, grab the info from our external host about the generated documents it has for this project. var projectId = this.ProjectState.Id; var infosOpt = await connection.TryInvokeAsync( - solution, + compilationState, (service, solutionChecksum, cancellationToken) => service.GetSourceGenerationInfoAsync(solutionChecksum, projectId, cancellationToken), cancellationToken).ConfigureAwait(false); @@ -154,7 +156,7 @@ await generatorInfo.Documents.States.Values.SelectAsArrayAsync( // Either we generated a different number of files, and/or we had contents of files that changed. Ensure // we know the contents of any new/changed files. var generatedSourcesOpt = await connection.TryInvokeAsync( - solution, + compilationState, (service, solutionChecksum, cancellationToken) => service.GetContentsAsync( solutionChecksum, projectId, documentsToAddOrUpdate.ToImmutable(), cancellationToken), cancellationToken).ConfigureAwait(false); @@ -213,7 +215,7 @@ await generatedDocuments.States.Values.SelectAsArrayAsync( } private async Task<(Compilation compilationWithGeneratedFiles, CompilationTrackerGeneratorInfo generatorInfo)> ComputeNewGeneratorInfoInCurrentProcessAsync( - SolutionState solution, + ISourceGeneratorTelemetryCollectorWorkspaceService? telemetryCollector, Compilation compilationWithoutGeneratedFiles, CompilationTrackerGeneratorInfo generatorInfo, Compilation? compilationWithStaleGeneratedTrees, @@ -275,7 +277,6 @@ await generatedDocuments.States.Values.SelectAsArrayAsync( var runResult = generatorInfo.Driver.GetRunResult(); - var telemetryCollector = solution.Services.GetService(); telemetryCollector?.CollectRunResult(runResult, generatorInfo.Driver.GetTimingInfo(), ProjectState); // We may be able to reuse compilationWithStaleGeneratedTrees if the generated trees are identical. We will assign null diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.GeneratedFileReplacingCompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.GeneratedFileReplacingCompilationTracker.cs index f61dddd18e496..3d4e8982f9960 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.GeneratedFileReplacingCompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.GeneratedFileReplacingCompilationTracker.cs @@ -12,7 +12,7 @@ namespace Microsoft.CodeAnalysis { - internal partial class SolutionState + internal partial class SolutionCompilationState { /// /// An implementation of that takes a compilation from another compilation tracker and updates it @@ -57,13 +57,13 @@ public ICompilationTracker Fork(ProjectState newProject, CompilationAndGenerator throw new NotImplementedException(); } - public ICompilationTracker FreezePartialStateWithTree(SolutionState solution, DocumentState docState, SyntaxTree tree, CancellationToken cancellationToken) + public ICompilationTracker FreezePartialStateWithTree(SolutionCompilationState compilationState, DocumentState docState, SyntaxTree tree, CancellationToken cancellationToken) { // Because we override SourceGeneratedDocument.WithFrozenPartialSemantics directly, we shouldn't be able to get here. throw ExceptionUtilities.Unreachable(); } - public async Task GetCompilationAsync(SolutionState solution, CancellationToken cancellationToken) + public async Task GetCompilationAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken) { // Fast path if we've definitely already done this before if (_compilationWithReplacement != null) @@ -71,8 +71,8 @@ public async Task GetCompilationAsync(SolutionState solution, Cance return _compilationWithReplacement; } - var underlyingCompilation = await UnderlyingTracker.GetCompilationAsync(solution, cancellationToken).ConfigureAwait(false); - var underlyingSourceGeneratedDocuments = await UnderlyingTracker.GetSourceGeneratedDocumentStatesAsync(solution, cancellationToken).ConfigureAwait(false); + var underlyingCompilation = await UnderlyingTracker.GetCompilationAsync(compilationState, cancellationToken).ConfigureAwait(false); + var underlyingSourceGeneratedDocuments = await UnderlyingTracker.GetSourceGeneratedDocumentStatesAsync(compilationState, cancellationToken).ConfigureAwait(false); underlyingSourceGeneratedDocuments.TryGetState(replacementDocumentState.Id, out var existingState); @@ -101,17 +101,17 @@ public async Task GetCompilationAsync(SolutionState solution, Cance return _compilationWithReplacement; } - public Task GetDependentVersionAsync(SolutionState solution, CancellationToken cancellationToken) - => UnderlyingTracker.GetDependentVersionAsync(solution, cancellationToken); + public Task GetDependentVersionAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken) + => UnderlyingTracker.GetDependentVersionAsync(compilationState, cancellationToken); - public Task GetDependentSemanticVersionAsync(SolutionState solution, CancellationToken cancellationToken) - => UnderlyingTracker.GetDependentSemanticVersionAsync(solution, cancellationToken); + public Task GetDependentSemanticVersionAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken) + => UnderlyingTracker.GetDependentSemanticVersionAsync(compilationState, cancellationToken); - public Task GetDependentChecksumAsync(SolutionState solution, CancellationToken cancellationToken) + public Task GetDependentChecksumAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken) { if (_lazyDependentChecksum == null) { - var tmp = solution; // temp. local to avoid a closure allocation for the fast path + var tmp = compilationState; // temp. local to avoid a closure allocation for the fast path // note: solution is captured here, but it will go away once GetValueAsync executes. Interlocked.CompareExchange(ref _lazyDependentChecksum, AsyncLazy.Create(c => ComputeDependentChecksumAsync(tmp, c)), null); } @@ -119,9 +119,9 @@ public Task GetDependentChecksumAsync(SolutionState solution, Cancella return _lazyDependentChecksum.GetValueAsync(cancellationToken); } - private async Task ComputeDependentChecksumAsync(SolutionState solution, CancellationToken cancellationToken) + private async Task ComputeDependentChecksumAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken) => Checksum.Create( - await UnderlyingTracker.GetDependentChecksumAsync(solution, cancellationToken).ConfigureAwait(false), + await UnderlyingTracker.GetDependentChecksumAsync(compilationState, cancellationToken).ConfigureAwait(false), await replacementDocumentState.GetChecksumAsync(cancellationToken).ConfigureAwait(false)); public MetadataReference? GetPartialMetadataReference(ProjectState fromProject, ProjectReference projectReference) @@ -136,9 +136,11 @@ await UnderlyingTracker.GetDependentChecksumAsync(solution, cancellationToken).C throw new NotImplementedException(); } - public async ValueTask> GetSourceGeneratedDocumentStatesAsync(SolutionState solution, CancellationToken cancellationToken) + public async ValueTask> GetSourceGeneratedDocumentStatesAsync( + SolutionCompilationState compilationState, CancellationToken cancellationToken) { - var underlyingGeneratedDocumentStates = await UnderlyingTracker.GetSourceGeneratedDocumentStatesAsync(solution, cancellationToken).ConfigureAwait(false); + var underlyingGeneratedDocumentStates = await UnderlyingTracker.GetSourceGeneratedDocumentStatesAsync( + compilationState, cancellationToken).ConfigureAwait(false); if (underlyingGeneratedDocumentStates.Contains(replacementDocumentState.Id)) { @@ -156,9 +158,10 @@ public async ValueTask> GetSour } } - public Task HasSuccessfullyLoadedAsync(SolutionState solution, CancellationToken cancellationToken) + public Task HasSuccessfullyLoadedAsync( + SolutionCompilationState compilationState, CancellationToken cancellationToken) { - return UnderlyingTracker.HasSuccessfullyLoadedAsync(solution, cancellationToken); + return UnderlyingTracker.HasSuccessfullyLoadedAsync(compilationState, cancellationToken); } public bool TryGetCompilation([NotNullWhen(true)] out Compilation? compilation) @@ -179,13 +182,14 @@ public bool TryGetCompilation([NotNullWhen(true)] out Compilation? compilation) } } - public ValueTask> GetSourceGeneratorDiagnosticsAsync(SolutionState solution, CancellationToken cancellationToken) + public ValueTask> GetSourceGeneratorDiagnosticsAsync( + SolutionCompilationState compilationState, CancellationToken cancellationToken) { // We can directly return the diagnostics from the underlying tracker; this is because // a generated document cannot have any diagnostics that are produced by a generator: // a generator cannot add diagnostics to it's own file outputs, and generators don't see the // outputs of each other. - return UnderlyingTracker.GetSourceGeneratorDiagnosticsAsync(solution, cancellationToken); + return UnderlyingTracker.GetSourceGeneratorDiagnosticsAsync(compilationState, cancellationToken); } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.ICompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.ICompilationTracker.cs index a79fe15137b40..947441af76664 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.ICompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.ICompilationTracker.cs @@ -10,7 +10,7 @@ namespace Microsoft.CodeAnalysis { - internal partial class SolutionState + internal partial class SolutionCompilationState { private interface ICompilationTracker { @@ -35,18 +35,18 @@ private interface ICompilationTracker /// bool ContainsAssemblyOrModuleOrDynamic(ISymbol symbol, bool primary); ICompilationTracker Fork(ProjectState newProject, CompilationAndGeneratorDriverTranslationAction? translate); - ICompilationTracker FreezePartialStateWithTree(SolutionState solution, DocumentState docState, SyntaxTree tree, CancellationToken cancellationToken); - Task GetCompilationAsync(SolutionState solution, CancellationToken cancellationToken); + ICompilationTracker FreezePartialStateWithTree(SolutionCompilationState compilationState, DocumentState docState, SyntaxTree tree, CancellationToken cancellationToken); + Task GetCompilationAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken); - Task GetDependentVersionAsync(SolutionState solution, CancellationToken cancellationToken); - Task GetDependentSemanticVersionAsync(SolutionState solution, CancellationToken cancellationToken); - Task GetDependentChecksumAsync(SolutionState solution, CancellationToken cancellationToken); + Task GetDependentVersionAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken); + Task GetDependentSemanticVersionAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken); + Task GetDependentChecksumAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken); MetadataReference? GetPartialMetadataReference(ProjectState fromProject, ProjectReference projectReference); - ValueTask> GetSourceGeneratedDocumentStatesAsync(SolutionState solution, CancellationToken cancellationToken); - ValueTask> GetSourceGeneratorDiagnosticsAsync(SolutionState solution, CancellationToken cancellationToken); + ValueTask> GetSourceGeneratedDocumentStatesAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken); + ValueTask> GetSourceGeneratorDiagnosticsAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken); - Task HasSuccessfullyLoadedAsync(SolutionState solution, CancellationToken cancellationToken); + Task HasSuccessfullyLoadedAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken); bool TryGetCompilation([NotNullWhen(true)] out Compilation? compilation); SourceGeneratedDocumentState? TryGetSourceGeneratedDocumentStateForAlreadyGeneratedId(DocumentId documentId); } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SkeletonReferenceCache.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SkeletonReferenceCache.cs index 03115f551d7b1..072170a76b2a8 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SkeletonReferenceCache.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SkeletonReferenceCache.cs @@ -16,7 +16,7 @@ namespace Microsoft.CodeAnalysis; -internal partial class SolutionState +internal partial class SolutionCompilationState { /// /// Caches the skeleton references produced for a given project/compilation under the varying GetOrBuildReferenceAsync( ICompilationTracker compilationTracker, - SolutionState solution, + SolutionCompilationState compilationState, MetadataReferenceProperties properties, CancellationToken cancellationToken) { - var version = await compilationTracker.GetDependentSemanticVersionAsync(solution, cancellationToken).ConfigureAwait(false); + var version = await compilationTracker.GetDependentSemanticVersionAsync( + compilationState, cancellationToken).ConfigureAwait(false); var referenceSet = await TryGetOrCreateReferenceSetAsync( - compilationTracker, solution, version, cancellationToken).ConfigureAwait(false); + compilationTracker, compilationState, version, cancellationToken).ConfigureAwait(false); if (referenceSet == null) return null; @@ -145,7 +146,7 @@ public SkeletonReferenceCache Clone() private async Task TryGetOrCreateReferenceSetAsync( ICompilationTracker compilationTracker, - SolutionState solution, + SolutionCompilationState compilationState, VersionStamp version, CancellationToken cancellationToken) { @@ -156,7 +157,7 @@ public SkeletonReferenceCache Clone() // okay, we don't have anything cached with this version. so create one now. - var currentSkeletonReferenceSet = await CreateSkeletonReferenceSetAsync(compilationTracker, solution, cancellationToken).ConfigureAwait(false); + var currentSkeletonReferenceSet = await CreateSkeletonReferenceSetAsync(compilationTracker, compilationState, cancellationToken).ConfigureAwait(false); lock (_stateGate) { @@ -175,14 +176,14 @@ public SkeletonReferenceCache Clone() private static async Task CreateSkeletonReferenceSetAsync( ICompilationTracker compilationTracker, - SolutionState solution, + SolutionCompilationState compilationState, CancellationToken cancellationToken) { // It's acceptable for this computation to be something that multiple calling threads may hit at once. The // implementation inside the compilation tracker does an async-wait on a an internal semaphore to ensure // only one thread actually does the computation and the rest wait. - var compilation = await compilationTracker.GetCompilationAsync(solution, cancellationToken).ConfigureAwait(false); - var services = solution.Services; + var compilation = await compilationTracker.GetCompilationAsync(compilationState, cancellationToken).ConfigureAwait(false); + var services = compilationState.Solution.Services; // note: computing the assembly metadata is actually synchronous. However, this ensures we don't have N // threads blocking on a lazy to compute the work. Instead, we'll only occupy one thread, while any diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SkeletonReferenceSet.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SkeletonReferenceSet.cs index 87b1baf85b295..9a6e26abb172f 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SkeletonReferenceSet.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SkeletonReferenceSet.cs @@ -10,7 +10,7 @@ namespace Microsoft.CodeAnalysis; -internal partial class SolutionState +internal partial class SolutionCompilationState { /// /// The actual assembly metadata produced from another compilation. diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SymbolToProjectId.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SymbolToProjectId.cs index 66c2b40cb80cd..631923b2ed7ab 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SymbolToProjectId.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.SymbolToProjectId.cs @@ -10,8 +10,49 @@ namespace Microsoft.CodeAnalysis { - internal partial class SolutionState + internal partial class SolutionCompilationState { + internal DocumentState? GetDocumentState(SyntaxTree? syntaxTree, ProjectId? projectId) + { + if (syntaxTree != null) + { + // is this tree known to be associated with a document? + var documentId = DocumentState.GetDocumentIdForTree(syntaxTree); + if (documentId != null && (projectId == null || documentId.ProjectId == projectId)) + { + // does this solution even have the document? + var projectState = this.Solution.GetProjectState(documentId.ProjectId); + if (projectState != null) + { + var document = projectState.DocumentStates.GetState(documentId); + if (document != null) + { + // does this document really have the syntax tree? + if (document.TryGetSyntaxTree(out var documentTree) && documentTree == syntaxTree) + { + return document; + } + } + else + { + var generatedDocument = this.TryGetSourceGeneratedDocumentStateForAlreadyGeneratedId(documentId); + + if (generatedDocument != null) + { + // does this document really have the syntax tree? + if (generatedDocument.TryGetSyntaxTree(out var documentTree) && documentTree == syntaxTree) + { + return generatedDocument; + } + } + } + } + } + } + + return null; + } + /// public ProjectId? GetOriginatingProjectId(ISymbol? symbol) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.UnrootedSymbolSet.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.UnrootedSymbolSet.cs index cb67b5dd1a21d..2b952734b5a43 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.UnrootedSymbolSet.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.UnrootedSymbolSet.cs @@ -11,7 +11,7 @@ namespace Microsoft.CodeAnalysis { - internal partial class SolutionState + internal partial class SolutionCompilationState { /// /// A helper type for mapping back to an originating . diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs index d3d15f6ad922c..7ca814e108d8c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs @@ -8,39 +8,34 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.Logging; using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Remote; -using Microsoft.CodeAnalysis.Serialization; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; -using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer; namespace Microsoft.CodeAnalysis { + internal readonly record struct StateChange( + SolutionState NewSolutionState, + ProjectState OldProjectState, + ProjectState NewProjectState); + /// /// Represents a set of projects and their source code documents. /// /// this is a green node of Solution like ProjectState/DocumentState are for /// Project and Document. /// - internal partial class SolutionState + internal sealed partial class SolutionState { // the version of the workspace this solution is from public int WorkspaceVersion { get; } public string? WorkspaceKind { get; } public SolutionServices Services { get; } public SolutionOptionSet Options { get; } - public bool PartialSemanticsEnabled { get; } public IReadOnlyList AnalyzerReferences { get; } private readonly SolutionInfo.SolutionAttributes _solutionAttributes; @@ -48,64 +43,33 @@ internal partial class SolutionState private readonly ImmutableDictionary> _filePathToDocumentIdsMap; private readonly ProjectDependencyGraph _dependencyGraph; - // Values for all these are created on demand. - private ImmutableDictionary _projectIdToTrackerMap; - - // Checksums for this solution state - private readonly AsyncLazy _lazyChecksums; - - /// - /// Mapping from project-id to the checksums needed to synchronize it (and the projects it depends on) over - /// to an OOP host. Lock this specific field before reading/writing to it. - /// - private readonly Dictionary> _lazyProjectChecksums = new(); - // holds on data calculated based on the AnalyzerReferences list private readonly Lazy _lazyAnalyzers; - /// - /// Cache we use to map between unrooted symbols (i.e. assembly, module and dynamic symbols) and the project - /// they came from. That way if we are asked about many symbols from the same assembly/module we can answer the - /// question quickly after computing for the first one. Created on demand. - /// - private ConditionalWeakTable? _unrootedSymbolToProjectId; - private static readonly Func> s_createTable = () => new ConditionalWeakTable(); - - private readonly SourceGeneratedDocumentState? _frozenSourceGeneratedDocumentState; - private SolutionState( string? workspaceKind, int workspaceVersion, - bool partialSemanticsEnabled, SolutionServices services, SolutionInfo.SolutionAttributes solutionAttributes, IReadOnlyList projectIds, SolutionOptionSet options, IReadOnlyList analyzerReferences, ImmutableDictionary idToProjectStateMap, - ImmutableDictionary projectIdToTrackerMap, ImmutableDictionary> filePathToDocumentIdsMap, ProjectDependencyGraph dependencyGraph, - Lazy? lazyAnalyzers, - SourceGeneratedDocumentState? frozenSourceGeneratedDocument) + Lazy? lazyAnalyzers) { WorkspaceKind = workspaceKind; WorkspaceVersion = workspaceVersion; - PartialSemanticsEnabled = partialSemanticsEnabled; _solutionAttributes = solutionAttributes; Services = services; ProjectIds = projectIds; Options = options; AnalyzerReferences = analyzerReferences; _projectIdToProjectStateMap = idToProjectStateMap; - _projectIdToTrackerMap = projectIdToTrackerMap; _filePathToDocumentIdsMap = filePathToDocumentIdsMap; _dependencyGraph = dependencyGraph; _lazyAnalyzers = lazyAnalyzers ?? CreateLazyHostDiagnosticAnalyzers(analyzerReferences); - _frozenSourceGeneratedDocumentState = frozenSourceGeneratedDocument; - - // when solution state is changed, we recalculate its checksum - _lazyChecksums = AsyncLazy.Create(c => ComputeChecksumsAsync(projectsToInclude: null, c)); CheckInvariants(); @@ -116,7 +80,6 @@ static Lazy CreateLazyHostDiagnosticAnalyzers(IReadOnly public SolutionState( string? workspaceKind, - bool partialSemanticsEnabled, SolutionServices services, SolutionInfo.SolutionAttributes solutionAttributes, SolutionOptionSet options, @@ -124,18 +87,15 @@ public SolutionState( : this( workspaceKind, workspaceVersion: 0, - partialSemanticsEnabled, services, solutionAttributes, projectIds: SpecializedCollections.EmptyBoxedImmutableArray(), options, analyzerReferences, idToProjectStateMap: ImmutableDictionary.Empty, - projectIdToTrackerMap: ImmutableDictionary.Empty, filePathToDocumentIdsMap: ImmutableDictionary.Create>(StringComparer.OrdinalIgnoreCase), dependencyGraph: ProjectDependencyGraph.Empty, - lazyAnalyzers: null, - frozenSourceGeneratedDocument: null) + lazyAnalyzers: null) { } @@ -143,8 +103,6 @@ public SolutionState( public SolutionInfo.SolutionAttributes SolutionAttributes => _solutionAttributes; - public SourceGeneratedDocumentState? FrozenSourceGeneratedDocumentState => _frozenSourceGeneratedDocumentState; - public ImmutableDictionary ProjectStates => _projectIdToProjectStateMap; /// @@ -173,37 +131,30 @@ private void CheckInvariants() Contract.ThrowIfFalse(_projectIdToProjectStateMap.Count == ProjectIds.Count); Contract.ThrowIfFalse(_projectIdToProjectStateMap.Count == _dependencyGraph.ProjectIds.Count); - // Only run this in debug builds; even the .Any() call across all projects can be expensive when there's a lot of them. + // Only run this in debug builds; even the .SetEquals() call across all projects can be expensive when there's a lot of them. #if DEBUG - // An id shouldn't point at a tracker for a different project. - Contract.ThrowIfTrue(_projectIdToTrackerMap.Any(kvp => kvp.Key != kvp.Value.ProjectState.Id)); - // project ids must be the same: Debug.Assert(_projectIdToProjectStateMap.Keys.SetEquals(ProjectIds)); Debug.Assert(_projectIdToProjectStateMap.Keys.SetEquals(_dependencyGraph.ProjectIds)); #endif } - private SolutionState Branch( + internal SolutionState Branch( SolutionInfo.SolutionAttributes? solutionAttributes = null, IReadOnlyList? projectIds = null, SolutionOptionSet? options = null, IReadOnlyList? analyzerReferences = null, ImmutableDictionary? idToProjectStateMap = null, - ImmutableDictionary? projectIdToTrackerMap = null, ImmutableDictionary>? filePathToDocumentIdsMap = null, - ProjectDependencyGraph? dependencyGraph = null, - Optional frozenSourceGeneratedDocument = default) + ProjectDependencyGraph? dependencyGraph = null) { solutionAttributes ??= _solutionAttributes; projectIds ??= ProjectIds; idToProjectStateMap ??= _projectIdToProjectStateMap; options ??= Options; analyzerReferences ??= AnalyzerReferences; - projectIdToTrackerMap ??= _projectIdToTrackerMap; filePathToDocumentIdsMap ??= _filePathToDocumentIdsMap; dependencyGraph ??= _dependencyGraph; - var newFrozenSourceGeneratedDocumentState = frozenSourceGeneratedDocument.HasValue ? frozenSourceGeneratedDocument.Value : _frozenSourceGeneratedDocumentState; var analyzerReferencesEqual = AnalyzerReferences.SequenceEqual(analyzerReferences); @@ -212,10 +163,8 @@ private SolutionState Branch( options == Options && analyzerReferencesEqual && idToProjectStateMap == _projectIdToProjectStateMap && - projectIdToTrackerMap == _projectIdToTrackerMap && filePathToDocumentIdsMap == _filePathToDocumentIdsMap && - dependencyGraph == _dependencyGraph && - newFrozenSourceGeneratedDocumentState == _frozenSourceGeneratedDocumentState) + dependencyGraph == _dependencyGraph) { return this; } @@ -223,18 +172,15 @@ private SolutionState Branch( return new SolutionState( WorkspaceKind, WorkspaceVersion, - PartialSemanticsEnabled, Services, solutionAttributes, projectIds, options, analyzerReferences, idToProjectStateMap, - projectIdToTrackerMap, filePathToDocumentIdsMap, dependencyGraph, - analyzerReferencesEqual ? _lazyAnalyzers : null, - newFrozenSourceGeneratedDocumentState); + analyzerReferencesEqual ? _lazyAnalyzers : null); } /// @@ -259,18 +205,15 @@ public SolutionState WithNewWorkspace( return new SolutionState( workspaceKind, workspaceVersion, - PartialSemanticsEnabled, services, _solutionAttributes, ProjectIds, Options, AnalyzerReferences, _projectIdToProjectStateMap, - _projectIdToTrackerMap, _filePathToDocumentIdsMap, _dependencyGraph, - _lazyAnalyzers, - _frozenSourceGeneratedDocumentState); + _lazyAnalyzers); } /// @@ -327,7 +270,7 @@ public bool ContainsAnalyzerConfigDocument([NotNullWhen(returnValue: true)] Docu this.GetProjectState(documentId.ProjectId)!.AnalyzerConfigDocumentStates.Contains(documentId); } - private DocumentState GetRequiredDocumentState(DocumentId documentId) + internal DocumentState GetRequiredDocumentState(DocumentId documentId) => GetRequiredProjectState(documentId.ProjectId).DocumentStates.GetRequiredState(documentId); private AdditionalDocumentState GetRequiredAdditionalDocumentState(DocumentId documentId) @@ -336,61 +279,8 @@ private AdditionalDocumentState GetRequiredAdditionalDocumentState(DocumentId do private AnalyzerConfigDocumentState GetRequiredAnalyzerConfigDocumentState(DocumentId documentId) => GetRequiredProjectState(documentId.ProjectId).AnalyzerConfigDocumentStates.GetRequiredState(documentId); - internal DocumentState? GetDocumentState(SyntaxTree? syntaxTree, ProjectId? projectId) - { - if (syntaxTree != null) - { - // is this tree known to be associated with a document? - var documentId = DocumentState.GetDocumentIdForTree(syntaxTree); - if (documentId != null && (projectId == null || documentId.ProjectId == projectId)) - { - // does this solution even have the document? - var projectState = GetProjectState(documentId.ProjectId); - if (projectState != null) - { - var document = projectState.DocumentStates.GetState(documentId); - if (document != null) - { - // does this document really have the syntax tree? - if (document.TryGetSyntaxTree(out var documentTree) && documentTree == syntaxTree) - { - return document; - } - } - else - { - var generatedDocument = TryGetSourceGeneratedDocumentStateForAlreadyGeneratedId(documentId); - - if (generatedDocument != null) - { - // does this document really have the syntax tree? - if (generatedDocument.TryGetSyntaxTree(out var documentTree) && documentTree == syntaxTree) - { - return generatedDocument; - } - } - } - } - } - } - - return null; - } - - public Task GetDependentVersionAsync(ProjectId projectId, CancellationToken cancellationToken) - => this.GetCompilationTracker(projectId).GetDependentVersionAsync(this, cancellationToken); - - public Task GetDependentSemanticVersionAsync(ProjectId projectId, CancellationToken cancellationToken) - => this.GetCompilationTracker(projectId).GetDependentSemanticVersionAsync(this, cancellationToken); - - public Task GetDependentChecksumAsync(ProjectId projectId, CancellationToken cancellationToken) - => this.GetCompilationTracker(projectId).GetDependentChecksumAsync(this, cancellationToken); - - public ProjectState? GetProjectState(ProjectId projectId) - { - _projectIdToProjectStateMap.TryGetValue(projectId, out var state); - return state; - } + public ProjectState? GetProjectState(ProjectId? projectId) + => projectId != null && _projectIdToProjectStateMap.TryGetValue(projectId, out var state) ? state : null; public ProjectState GetRequiredProjectState(ProjectId projectId) { @@ -399,42 +289,10 @@ public ProjectState GetRequiredProjectState(ProjectId projectId) return result; } - /// - /// Gets the associated with an assembly symbol. - /// - public ProjectState? GetProjectState(IAssemblySymbol? assemblySymbol) + private SolutionState AddProject(ProjectState projectState) { - if (assemblySymbol == null) - return null; - - s_assemblyOrModuleSymbolToProjectMap.TryGetValue(assemblySymbol, out var id); - return id == null ? null : this.GetProjectState(id); - } + var projectId = projectState.Id; - private bool TryGetCompilationTracker(ProjectId projectId, [NotNullWhen(returnValue: true)] out ICompilationTracker? tracker) - => _projectIdToTrackerMap.TryGetValue(projectId, out tracker); - - private static readonly Func s_createCompilationTrackerFunction = CreateCompilationTracker; - - private static CompilationTracker CreateCompilationTracker(ProjectId projectId, SolutionState solution) - { - var projectState = solution.GetProjectState(projectId); - Contract.ThrowIfNull(projectState); - return new CompilationTracker(projectState); - } - - private ICompilationTracker GetCompilationTracker(ProjectId projectId) - { - if (!_projectIdToTrackerMap.TryGetValue(projectId, out var tracker)) - { - tracker = ImmutableInterlocked.GetOrAdd(ref _projectIdToTrackerMap, projectId, s_createCompilationTrackerFunction, this); - } - - return tracker; - } - - private SolutionState AddProject(ProjectId projectId, ProjectState projectState) - { // changed project list so, increment version. var newSolutionAttributes = _solutionAttributes.With(version: Version.GetNewerVersion()); @@ -462,14 +320,12 @@ private SolutionState AddProject(ProjectId projectId, ProjectState projectState) } } - var newTrackerMap = CreateCompilationTrackerMap(projectId, newDependencyGraph); var newFilePathToDocumentIdsMap = CreateFilePathToDocumentIdsMapWithAddedDocuments(GetDocumentStates(newStateMap[projectId])); return Branch( solutionAttributes: newSolutionAttributes, projectIds: newProjectIds, idToProjectStateMap: newStateMap, - projectIdToTrackerMap: newTrackerMap, filePathToDocumentIdsMap: newFilePathToDocumentIdsMap, dependencyGraph: newDependencyGraph); } @@ -508,10 +364,10 @@ public SolutionState AddProject(ProjectInfo projectInfo) var newProject = new ProjectState(languageServices, projectInfo); - return this.AddProject(newProject.Id, newProject); + return this.AddProject(newProject); } - private ImmutableDictionary> CreateFilePathToDocumentIdsMapWithAddedDocuments(IEnumerable documentStates) + public ImmutableDictionary> CreateFilePathToDocumentIdsMapWithAddedDocuments(IEnumerable documentStates) { var builder = _filePathToDocumentIdsMap.ToBuilder(); @@ -553,19 +409,17 @@ public SolutionState RemoveProject(ProjectId projectId) var newProjectIds = ProjectIds.ToImmutableArray().Remove(projectId); var newStateMap = _projectIdToProjectStateMap.Remove(projectId); var newDependencyGraph = _dependencyGraph.WithProjectRemoved(projectId); - var newTrackerMap = CreateCompilationTrackerMap(projectId, newDependencyGraph); var newFilePathToDocumentIdsMap = CreateFilePathToDocumentIdsMapWithRemovedDocuments(GetDocumentStates(_projectIdToProjectStateMap[projectId])); return this.Branch( solutionAttributes: newSolutionAttributes, projectIds: newProjectIds, idToProjectStateMap: newStateMap, - projectIdToTrackerMap: newTrackerMap.Remove(projectId), filePathToDocumentIdsMap: newFilePathToDocumentIdsMap, dependencyGraph: newDependencyGraph); } - private ImmutableDictionary> CreateFilePathToDocumentIdsMapWithRemovedDocuments(IEnumerable documentStates) + public ImmutableDictionary> CreateFilePathToDocumentIdsMapWithRemovedDocuments(IEnumerable documentStates) { var builder = _filePathToDocumentIdsMap.ToBuilder(); @@ -615,236 +469,227 @@ private ImmutableDictionary> CreateFilePathTo /// Creates a new solution instance with the project specified updated to have the new /// assembly name. /// - public SolutionState WithProjectAssemblyName(ProjectId projectId, string assemblyName) + public StateChange WithProjectAssemblyName(ProjectId projectId, string assemblyName) { var oldProject = GetRequiredProjectState(projectId); var newProject = oldProject.WithAssemblyName(assemblyName); if (oldProject == newProject) { - return this; + return new(this, oldProject, newProject); } - return ForkProject(newProject, new CompilationAndGeneratorDriverTranslationAction.ProjectAssemblyNameAction(assemblyName)); + return ForkProject(oldProject, newProject); } /// /// Creates a new solution instance with the project specified updated to have the output file path. /// - public SolutionState WithProjectOutputFilePath(ProjectId projectId, string? outputFilePath) + public StateChange WithProjectOutputFilePath(ProjectId projectId, string? outputFilePath) { var oldProject = GetRequiredProjectState(projectId); var newProject = oldProject.WithOutputFilePath(outputFilePath); if (oldProject == newProject) { - return this; + return new(this, oldProject, newProject); } - return ForkProject(newProject); + return ForkProject(oldProject, newProject); } /// /// Creates a new solution instance with the project specified updated to have the output file path. /// - public SolutionState WithProjectOutputRefFilePath(ProjectId projectId, string? outputRefFilePath) + public StateChange WithProjectOutputRefFilePath(ProjectId projectId, string? outputRefFilePath) { var oldProject = GetRequiredProjectState(projectId); var newProject = oldProject.WithOutputRefFilePath(outputRefFilePath); if (oldProject == newProject) { - return this; + return new(this, oldProject, newProject); } - return ForkProject(newProject); + return ForkProject(oldProject, newProject); } /// /// Creates a new solution instance with the project specified updated to have the compiler output file path. /// - public SolutionState WithProjectCompilationOutputInfo(ProjectId projectId, in CompilationOutputInfo info) + public StateChange WithProjectCompilationOutputInfo(ProjectId projectId, in CompilationOutputInfo info) { var oldProject = GetRequiredProjectState(projectId); var newProject = oldProject.WithCompilationOutputInfo(info); if (oldProject == newProject) { - return this; + return new(this, oldProject, newProject); } - return ForkProject(newProject); + return ForkProject(oldProject, newProject); } /// /// Creates a new solution instance with the project specified updated to have the default namespace. /// - public SolutionState WithProjectDefaultNamespace(ProjectId projectId, string? defaultNamespace) + public StateChange WithProjectDefaultNamespace(ProjectId projectId, string? defaultNamespace) { var oldProject = GetRequiredProjectState(projectId); var newProject = oldProject.WithDefaultNamespace(defaultNamespace); if (oldProject == newProject) { - return this; + return new(this, oldProject, newProject); } - return ForkProject(newProject); + return ForkProject(oldProject, newProject); } /// /// Creates a new solution instance with the project specified updated to have the name. /// - public SolutionState WithProjectChecksumAlgorithm(ProjectId projectId, SourceHashAlgorithm checksumAlgorithm) + public StateChange WithProjectChecksumAlgorithm(ProjectId projectId, SourceHashAlgorithm checksumAlgorithm) { var oldProject = GetRequiredProjectState(projectId); var newProject = oldProject.WithChecksumAlgorithm(checksumAlgorithm); if (oldProject == newProject) { - return this; + return new(this, oldProject, newProject); } - return ForkProject(newProject, new CompilationAndGeneratorDriverTranslationAction.ReplaceAllSyntaxTreesAction(newProject, isParseOptionChange: false)); + return ForkProject(oldProject, newProject); } /// /// Creates a new solution instance with the project specified updated to have the name. /// - public SolutionState WithProjectName(ProjectId projectId, string name) + public StateChange WithProjectName(ProjectId projectId, string name) { var oldProject = GetRequiredProjectState(projectId); var newProject = oldProject.WithName(name); if (oldProject == newProject) { - return this; + return new(this, oldProject, newProject); } - return ForkProject(newProject); + return ForkProject(oldProject, newProject); } /// /// Creates a new solution instance with the project specified updated to have the project file path. /// - public SolutionState WithProjectFilePath(ProjectId projectId, string? filePath) + public StateChange WithProjectFilePath(ProjectId projectId, string? filePath) { var oldProject = GetRequiredProjectState(projectId); var newProject = oldProject.WithFilePath(filePath); if (oldProject == newProject) { - return this; + return new(this, oldProject, newProject); } - return ForkProject(newProject); + return ForkProject(oldProject, newProject); } /// /// Create a new solution instance with the project specified updated to have /// the specified compilation options. /// - public SolutionState WithProjectCompilationOptions(ProjectId projectId, CompilationOptions options) + public StateChange WithProjectCompilationOptions(ProjectId projectId, CompilationOptions options) { var oldProject = GetRequiredProjectState(projectId); var newProject = oldProject.WithCompilationOptions(options); if (oldProject == newProject) { - return this; + return new(this, oldProject, newProject); } - return ForkProject(newProject, new CompilationAndGeneratorDriverTranslationAction.ProjectCompilationOptionsAction(newProject, isAnalyzerConfigChange: false)); + return ForkProject(oldProject, newProject); } /// /// Create a new solution instance with the project specified updated to have /// the specified parse options. /// - public SolutionState WithProjectParseOptions(ProjectId projectId, ParseOptions options) + public StateChange WithProjectParseOptions(ProjectId projectId, ParseOptions options) { var oldProject = GetRequiredProjectState(projectId); var newProject = oldProject.WithParseOptions(options); if (oldProject == newProject) { - return this; + return new(this, oldProject, newProject); } - if (this.PartialSemanticsEnabled) - { - // don't fork tracker with queued action since access via partial semantics can become inconsistent (throw). - // Since changing options is rare event, it is okay to start compilation building from scratch. - return ForkProject(newProject, forkTracker: false); - } - else - { - return ForkProject(newProject, new CompilationAndGeneratorDriverTranslationAction.ReplaceAllSyntaxTreesAction(newProject, isParseOptionChange: true)); - } + return ForkProject(oldProject, newProject); } /// /// Create a new solution instance with the project specified updated to have /// the specified hasAllInformation. /// - public SolutionState WithHasAllInformation(ProjectId projectId, bool hasAllInformation) + public StateChange WithHasAllInformation(ProjectId projectId, bool hasAllInformation) { var oldProject = GetRequiredProjectState(projectId); var newProject = oldProject.WithHasAllInformation(hasAllInformation); if (oldProject == newProject) { - return this; + return new(this, oldProject, newProject); } // fork without any change on compilation. - return ForkProject(newProject); + return ForkProject(oldProject, newProject); } /// /// Create a new solution instance with the project specified updated to have /// the specified runAnalyzers. /// - public SolutionState WithRunAnalyzers(ProjectId projectId, bool runAnalyzers) + public StateChange WithRunAnalyzers(ProjectId projectId, bool runAnalyzers) { var oldProject = GetRequiredProjectState(projectId); var newProject = oldProject.WithRunAnalyzers(runAnalyzers); if (oldProject == newProject) { - return this; + return new(this, oldProject, newProject); } // fork without any change on compilation. - return ForkProject(newProject); + return ForkProject(oldProject, newProject); } /// /// Create a new solution instance with the project specified updated to include /// the specified project references. /// - public SolutionState AddProjectReferences(ProjectId projectId, IReadOnlyCollection projectReferences) + public StateChange AddProjectReferences(ProjectId projectId, IReadOnlyCollection projectReferences) { + var oldProject = GetRequiredProjectState(projectId); if (projectReferences.Count == 0) { - return this; + return new(this, oldProject, oldProject); } - var oldProject = GetRequiredProjectState(projectId); var oldReferences = oldProject.ProjectReferences.ToImmutableArray(); var newReferences = oldReferences.AddRange(projectReferences); var newProject = oldProject.WithProjectReferences(newReferences); var newDependencyGraph = _dependencyGraph.WithAdditionalProjectReferences(projectId, projectReferences); - return ForkProject(newProject, newDependencyGraph: newDependencyGraph); + return ForkProject(oldProject, newProject, newDependencyGraph: newDependencyGraph); } /// /// Create a new solution instance with the project specified updated to no longer /// include the specified project reference. /// - public SolutionState RemoveProjectReference(ProjectId projectId, ProjectReference projectReference) + public StateChange RemoveProjectReference(ProjectId projectId, ProjectReference projectReference) { var oldProject = GetRequiredProjectState(projectId); var oldReferences = oldProject.ProjectReferences.ToImmutableArray(); @@ -854,7 +699,7 @@ public SolutionState RemoveProjectReference(ProjectId projectId, ProjectReferenc if (oldReferences == newReferences) { - return this; + return new(this, oldProject, oldProject); } var newProject = oldProject.WithProjectReferences(newReferences); @@ -877,31 +722,31 @@ public SolutionState RemoveProjectReference(ProjectId projectId, ProjectReferenc newDependencyGraph = _dependencyGraph.WithProjectReferenceRemoved(projectId, projectReference.ProjectId); } - return ForkProject(newProject, newDependencyGraph: newDependencyGraph); + return ForkProject(oldProject, newProject, newDependencyGraph: newDependencyGraph); } /// /// Create a new solution instance with the project specified updated to contain /// the specified list of project references. /// - public SolutionState WithProjectReferences(ProjectId projectId, IReadOnlyList projectReferences) + public StateChange WithProjectReferences(ProjectId projectId, IReadOnlyList projectReferences) { var oldProject = GetRequiredProjectState(projectId); var newProject = oldProject.WithProjectReferences(projectReferences); if (oldProject == newProject) { - return this; + return new(this, oldProject, newProject); } var newDependencyGraph = _dependencyGraph.WithProjectReferences(projectId, projectReferences); - return ForkProject(newProject, newDependencyGraph: newDependencyGraph); + return ForkProject(oldProject, newProject, newDependencyGraph: newDependencyGraph); } /// /// Creates a new solution instance with the project documents in the order by the specified document ids. /// The specified document ids must be the same as what is already in the project; no adding or removing is allowed. /// - public SolutionState WithProjectDocumentsOrder(ProjectId projectId, ImmutableList documentIds) + public StateChange WithProjectDocumentsOrder(ProjectId projectId, ImmutableList documentIds) { var oldProject = GetRequiredProjectState(projectId); @@ -922,301 +767,124 @@ public SolutionState WithProjectDocumentsOrder(ProjectId projectId, ImmutableLis if (oldProject == newProject) { - return this; + return new(this, oldProject, newProject); } - return ForkProject(newProject, new CompilationAndGeneratorDriverTranslationAction.ReplaceAllSyntaxTreesAction(newProject, isParseOptionChange: false)); + return ForkProject(oldProject, newProject); } /// /// Create a new solution instance with the project specified updated to include the /// specified metadata references. /// - public SolutionState AddMetadataReferences(ProjectId projectId, IReadOnlyCollection metadataReferences) + public StateChange AddMetadataReferences(ProjectId projectId, IReadOnlyCollection metadataReferences) { + var oldProject = GetRequiredProjectState(projectId); if (metadataReferences.Count == 0) { - return this; + return new(this, oldProject, oldProject); } - var oldProject = GetRequiredProjectState(projectId); var oldReferences = oldProject.MetadataReferences.ToImmutableArray(); var newReferences = oldReferences.AddRange(metadataReferences); - return ForkProject(oldProject.WithMetadataReferences(newReferences)); + return ForkProject(oldProject, oldProject.WithMetadataReferences(newReferences)); } /// /// Create a new solution instance with the project specified updated to no longer include /// the specified metadata reference. /// - public SolutionState RemoveMetadataReference(ProjectId projectId, MetadataReference metadataReference) + public StateChange RemoveMetadataReference(ProjectId projectId, MetadataReference metadataReference) { var oldProject = GetRequiredProjectState(projectId); var oldReferences = oldProject.MetadataReferences.ToImmutableArray(); var newReferences = oldReferences.Remove(metadataReference); if (oldReferences == newReferences) { - return this; + return new(this, oldProject, oldProject); } - return ForkProject(oldProject.WithMetadataReferences(newReferences)); + return ForkProject(oldProject, oldProject.WithMetadataReferences(newReferences)); } /// /// Create a new solution instance with the project specified updated to include only the /// specified metadata references. /// - public SolutionState WithProjectMetadataReferences(ProjectId projectId, IReadOnlyList metadataReferences) + public StateChange WithProjectMetadataReferences(ProjectId projectId, IReadOnlyList metadataReferences) { var oldProject = GetRequiredProjectState(projectId); var newProject = oldProject.WithMetadataReferences(metadataReferences); if (oldProject == newProject) { - return this; + return new(this, oldProject, newProject); } - return ForkProject(newProject); + return ForkProject(oldProject, newProject); } /// /// Create a new solution instance with the project specified updated to include the /// specified analyzer references. /// - public SolutionState AddAnalyzerReferences(ProjectId projectId, ImmutableArray analyzerReferences) + public StateChange AddAnalyzerReferences(ProjectId projectId, ImmutableArray analyzerReferences) { + var oldProject = GetRequiredProjectState(projectId); if (analyzerReferences.Length == 0) { - return this; + return new(this, oldProject, oldProject); } - var oldProject = GetRequiredProjectState(projectId); var oldReferences = oldProject.AnalyzerReferences.ToImmutableArray(); var newReferences = oldReferences.AddRange(analyzerReferences); - return ForkProject( - oldProject.WithAnalyzerReferences(newReferences), - new CompilationAndGeneratorDriverTranslationAction.AddOrRemoveAnalyzerReferencesAction(oldProject.Language, referencesToAdd: analyzerReferences)); + return ForkProject(oldProject, oldProject.WithAnalyzerReferences(newReferences)); } /// /// Create a new solution instance with the project specified updated to no longer include /// the specified analyzer reference. /// - public SolutionState RemoveAnalyzerReference(ProjectId projectId, AnalyzerReference analyzerReference) + public StateChange RemoveAnalyzerReference(ProjectId projectId, AnalyzerReference analyzerReference) { var oldProject = GetRequiredProjectState(projectId); var oldReferences = oldProject.AnalyzerReferences.ToImmutableArray(); var newReferences = oldReferences.Remove(analyzerReference); if (oldReferences == newReferences) { - return this; + return new(this, oldProject, oldProject); } - return ForkProject( - oldProject.WithAnalyzerReferences(newReferences), - new CompilationAndGeneratorDriverTranslationAction.AddOrRemoveAnalyzerReferencesAction(oldProject.Language, referencesToRemove: ImmutableArray.Create(analyzerReference))); + return ForkProject(oldProject, oldProject.WithAnalyzerReferences(newReferences)); } /// /// Create a new solution instance with the project specified updated to include only the /// specified analyzer references. /// - public SolutionState WithProjectAnalyzerReferences(ProjectId projectId, IEnumerable analyzerReferences) + public StateChange WithProjectAnalyzerReferences(ProjectId projectId, IReadOnlyList analyzerReferences) { var oldProject = GetRequiredProjectState(projectId); var newProject = oldProject.WithAnalyzerReferences(analyzerReferences); if (oldProject == newProject) { - return this; - } - - // The .Except() methods here aren't going to terribly cheap, but the assumption is adding or removing just the generators - // we changed, rather than creating an entire new generator driver from scratch and rerunning all generators, is cheaper - // in the end. This was written without data backing up that assumption, so if a profile indicates to the contrary, - // this could be changed. - // - // When we're comparing AnalyzerReferences, we'll compare with reference equality; AnalyzerReferences like AnalyzerFileReference - // may implement their own equality, but that can result in things getting out of sync: two references that are value equal can still - // have their own generator instances; it's important that as we're adding and removing references that are value equal that we - // still update with the correct generator instances that are coming from the new reference that is actually held in the project state from above. - // An alternative approach would be to call oldProject.WithAnalyzerReferences keeping all the references in there that are value equal the same, - // but this avoids any surprises where other components calling WithAnalyzerReferences might not expect that. - var addedReferences = newProject.AnalyzerReferences.Except(oldProject.AnalyzerReferences, ReferenceEqualityComparer.Instance).ToImmutableArray(); - var removedReferences = oldProject.AnalyzerReferences.Except(newProject.AnalyzerReferences, ReferenceEqualityComparer.Instance).ToImmutableArray(); - - return ForkProject( - newProject, - new CompilationAndGeneratorDriverTranslationAction.AddOrRemoveAnalyzerReferencesAction(oldProject.Language, referencesToAdd: addedReferences, referencesToRemove: removedReferences)); - } - - /// - /// Create a new solution instance with the corresponding projects updated to include new - /// documents defined by the document info. - /// - public SolutionState AddDocuments(ImmutableArray documentInfos) - { - return AddDocumentsToMultipleProjects(documentInfos, - (documentInfo, project) => project.CreateDocument(documentInfo, project.ParseOptions, new LoadTextOptions(project.ChecksumAlgorithm)), - (oldProject, documents) => (oldProject.AddDocuments(documents), new CompilationAndGeneratorDriverTranslationAction.AddDocumentsAction(documents))); - } - - /// - /// Core helper that takes a set of s and does the application of the appropriate documents to each project. - /// - /// The set of documents to add. - /// Returns the new with the documents added, and the needed as well. - /// - private SolutionState AddDocumentsToMultipleProjects( - ImmutableArray documentInfos, - Func createDocumentState, - Func, (ProjectState newState, CompilationAndGeneratorDriverTranslationAction translationAction)> addDocumentsToProjectState) - where T : TextDocumentState - { - if (documentInfos.IsDefault) - { - throw new ArgumentNullException(nameof(documentInfos)); - } - - if (documentInfos.IsEmpty) - { - return this; + return new(this, oldProject, newProject); } - // The documents might be contributing to multiple different projects; split them by project and then we'll process - // project-at-a-time. - var documentInfosByProjectId = documentInfos.ToLookup(d => d.Id.ProjectId); - - var newSolutionState = this; - - foreach (var documentInfosInProject in documentInfosByProjectId) - { - CheckContainsProject(documentInfosInProject.Key); - var oldProjectState = this.GetProjectState(documentInfosInProject.Key)!; - - var newDocumentStatesForProjectBuilder = ArrayBuilder.GetInstance(); - - foreach (var documentInfo in documentInfosInProject) - { - newDocumentStatesForProjectBuilder.Add(createDocumentState(documentInfo, oldProjectState)); - } - - var newDocumentStatesForProject = newDocumentStatesForProjectBuilder.ToImmutableAndFree(); - - var (newProjectState, compilationTranslationAction) = addDocumentsToProjectState(oldProjectState, newDocumentStatesForProject); - - newSolutionState = newSolutionState.ForkProject(newProjectState, - compilationTranslationAction, - newFilePathToDocumentIdsMap: CreateFilePathToDocumentIdsMapWithAddedDocuments(newDocumentStatesForProject)); - } - - return newSolutionState; - } - - public SolutionState AddAdditionalDocuments(ImmutableArray documentInfos) - { - return AddDocumentsToMultipleProjects(documentInfos, - (documentInfo, project) => new AdditionalDocumentState(Services, documentInfo, new LoadTextOptions(project.ChecksumAlgorithm)), - (projectState, documents) => (projectState.AddAdditionalDocuments(documents), new CompilationAndGeneratorDriverTranslationAction.AddAdditionalDocumentsAction(documents))); - } - - public SolutionState AddAnalyzerConfigDocuments(ImmutableArray documentInfos) - { - // Adding a new analyzer config potentially modifies the compilation options - return AddDocumentsToMultipleProjects(documentInfos, - (documentInfo, project) => new AnalyzerConfigDocumentState(Services, documentInfo, new LoadTextOptions(project.ChecksumAlgorithm)), - (oldProject, documents) => - { - var newProject = oldProject.AddAnalyzerConfigDocuments(documents); - return (newProject, new CompilationAndGeneratorDriverTranslationAction.ProjectCompilationOptionsAction(newProject, isAnalyzerConfigChange: true)); - }); - } - - public SolutionState RemoveAnalyzerConfigDocuments(ImmutableArray documentIds) - { - return RemoveDocumentsFromMultipleProjects(documentIds, - (projectState, documentId) => projectState.AnalyzerConfigDocumentStates.GetRequiredState(documentId), - (oldProject, documentIds, _) => - { - var newProject = oldProject.RemoveAnalyzerConfigDocuments(documentIds); - return (newProject, new CompilationAndGeneratorDriverTranslationAction.ProjectCompilationOptionsAction(newProject, isAnalyzerConfigChange: true)); - }); - } - - /// - /// Creates a new solution instance that no longer includes the specified document. - /// - public SolutionState RemoveDocuments(ImmutableArray documentIds) - { - return RemoveDocumentsFromMultipleProjects(documentIds, - (projectState, documentId) => projectState.DocumentStates.GetRequiredState(documentId), - (projectState, documentIds, documentStates) => (projectState.RemoveDocuments(documentIds), new CompilationAndGeneratorDriverTranslationAction.RemoveDocumentsAction(documentStates))); - } - - private SolutionState RemoveDocumentsFromMultipleProjects( - ImmutableArray documentIds, - Func getExistingTextDocumentState, - Func, ImmutableArray, (ProjectState newState, CompilationAndGeneratorDriverTranslationAction translationAction)> removeDocumentsFromProjectState) - where T : TextDocumentState - { - if (documentIds.IsEmpty) - { - return this; - } - - // The documents might be contributing to multiple different projects; split them by project and then we'll process - // project-at-a-time. - var documentIdsByProjectId = documentIds.ToLookup(id => id.ProjectId); - - var newSolutionState = this; - - foreach (var documentIdsInProject in documentIdsByProjectId) - { - var oldProjectState = this.GetProjectState(documentIdsInProject.Key); - - if (oldProjectState == null) - { - throw new InvalidOperationException(string.Format(WorkspacesResources._0_is_not_part_of_the_workspace, documentIdsInProject.Key)); - } - - var removedDocumentStatesBuilder = ArrayBuilder.GetInstance(); - - foreach (var documentId in documentIdsInProject) - { - removedDocumentStatesBuilder.Add(getExistingTextDocumentState(oldProjectState, documentId)); - } - - var removedDocumentStatesForProject = removedDocumentStatesBuilder.ToImmutableAndFree(); - - var (newProjectState, compilationTranslationAction) = removeDocumentsFromProjectState(oldProjectState, documentIdsInProject.ToImmutableArray(), removedDocumentStatesForProject); - - newSolutionState = newSolutionState.ForkProject(newProjectState, - compilationTranslationAction, - newFilePathToDocumentIdsMap: CreateFilePathToDocumentIdsMapWithRemovedDocuments(removedDocumentStatesForProject)); - } - - return newSolutionState; - } - - /// - /// Creates a new solution instance that no longer includes the specified additional documents. - /// - public SolutionState RemoveAdditionalDocuments(ImmutableArray documentIds) - { - return RemoveDocumentsFromMultipleProjects(documentIds, - (projectState, documentId) => projectState.AdditionalDocumentStates.GetRequiredState(documentId), - (projectState, documentIds, documentStates) => (projectState.RemoveAdditionalDocuments(documentIds), new CompilationAndGeneratorDriverTranslationAction.RemoveAdditionalDocumentsAction(documentStates))); + return ForkProject(oldProject, newProject); } /// /// Creates a new solution instance with the document specified updated to have the specified name. /// - public SolutionState WithDocumentName(DocumentId documentId, string name) + public StateChange WithDocumentName(DocumentId documentId, string name) { var oldDocument = GetRequiredDocumentState(documentId); if (oldDocument.Attributes.Name == name) { - return this; + var oldProject = GetRequiredProjectState(documentId.ProjectId); + return new(this, oldProject, oldProject); } return UpdateDocumentState(oldDocument.UpdateName(name), contentChanged: false); @@ -1226,12 +894,13 @@ public SolutionState WithDocumentName(DocumentId documentId, string name) /// Creates a new solution instance with the document specified updated to be contained in /// the sequence of logical folders. /// - public SolutionState WithDocumentFolders(DocumentId documentId, IReadOnlyList folders) + public StateChange WithDocumentFolders(DocumentId documentId, IReadOnlyList folders) { var oldDocument = GetRequiredDocumentState(documentId); if (oldDocument.Folders.SequenceEqual(folders)) { - return this; + var oldProject = GetRequiredProjectState(documentId.ProjectId); + return new(this, oldProject, oldProject); } return UpdateDocumentState(oldDocument.UpdateFolders(folders), contentChanged: false); @@ -1240,12 +909,13 @@ public SolutionState WithDocumentFolders(DocumentId documentId, IReadOnlyList /// Creates a new solution instance with the document specified updated to have the specified file path. /// - public SolutionState WithDocumentFilePath(DocumentId documentId, string? filePath) + public StateChange WithDocumentFilePath(DocumentId documentId, string? filePath) { var oldDocument = GetRequiredDocumentState(documentId); if (oldDocument.FilePath == filePath) { - return this; + var oldProject = GetRequiredProjectState(documentId.ProjectId); + return new(this, oldProject, oldProject); } return UpdateDocumentState(oldDocument.UpdateFilePath(filePath), contentChanged: false); @@ -1255,12 +925,13 @@ public SolutionState WithDocumentFilePath(DocumentId documentId, string? filePat /// Creates a new solution instance with the document specified updated to have the text /// specified. /// - public SolutionState WithDocumentText(DocumentId documentId, SourceText text, PreservationMode mode = PreservationMode.PreserveValue) + public StateChange WithDocumentText(DocumentId documentId, SourceText text, PreservationMode mode = PreservationMode.PreserveValue) { var oldDocument = GetRequiredDocumentState(documentId); if (oldDocument.TryGetText(out var oldText) && text == oldText) { - return this; + var oldProject = GetRequiredProjectState(documentId.ProjectId); + return new(this, oldProject, oldProject); } return UpdateDocumentState(oldDocument.UpdateText(text, mode), contentChanged: true); @@ -1270,12 +941,13 @@ public SolutionState WithDocumentText(DocumentId documentId, SourceText text, Pr /// Creates a new solution instance with the additional document specified updated to have the text /// specified. /// - public SolutionState WithAdditionalDocumentText(DocumentId documentId, SourceText text, PreservationMode mode = PreservationMode.PreserveValue) + public StateChange WithAdditionalDocumentText(DocumentId documentId, SourceText text, PreservationMode mode = PreservationMode.PreserveValue) { var oldDocument = GetRequiredAdditionalDocumentState(documentId); if (oldDocument.TryGetText(out var oldText) && text == oldText) { - return this; + var oldProject = GetRequiredProjectState(documentId.ProjectId); + return new(this, oldProject, oldProject); } return UpdateAdditionalDocumentState(oldDocument.UpdateText(text, mode), contentChanged: true); @@ -1285,12 +957,13 @@ public SolutionState WithAdditionalDocumentText(DocumentId documentId, SourceTex /// Creates a new solution instance with the document specified updated to have the text /// specified. /// - public SolutionState WithAnalyzerConfigDocumentText(DocumentId documentId, SourceText text, PreservationMode mode = PreservationMode.PreserveValue) + public StateChange WithAnalyzerConfigDocumentText(DocumentId documentId, SourceText text, PreservationMode mode = PreservationMode.PreserveValue) { var oldDocument = GetRequiredAnalyzerConfigDocumentState(documentId); if (oldDocument.TryGetText(out var oldText) && text == oldText) { - return this; + var oldProject = GetRequiredProjectState(documentId.ProjectId); + return new(this, oldProject, oldProject); } return UpdateAnalyzerConfigDocumentState(oldDocument.UpdateText(text, mode)); @@ -1300,12 +973,13 @@ public SolutionState WithAnalyzerConfigDocumentText(DocumentId documentId, Sourc /// Creates a new solution instance with the document specified updated to have the text /// and version specified. /// - public SolutionState WithDocumentText(DocumentId documentId, TextAndVersion textAndVersion, PreservationMode mode = PreservationMode.PreserveValue) + public StateChange WithDocumentText(DocumentId documentId, TextAndVersion textAndVersion, PreservationMode mode = PreservationMode.PreserveValue) { var oldDocument = GetRequiredDocumentState(documentId); if (oldDocument.TryGetTextAndVersion(out var oldTextAndVersion) && textAndVersion == oldTextAndVersion) { - return this; + var oldProject = GetRequiredProjectState(documentId.ProjectId); + return new(this, oldProject, oldProject); } return UpdateDocumentState(oldDocument.UpdateText(textAndVersion, mode), contentChanged: true); @@ -1315,12 +989,13 @@ public SolutionState WithDocumentText(DocumentId documentId, TextAndVersion text /// Creates a new solution instance with the additional document specified updated to have the text /// and version specified. /// - public SolutionState WithAdditionalDocumentText(DocumentId documentId, TextAndVersion textAndVersion, PreservationMode mode = PreservationMode.PreserveValue) + public StateChange WithAdditionalDocumentText(DocumentId documentId, TextAndVersion textAndVersion, PreservationMode mode = PreservationMode.PreserveValue) { var oldDocument = GetRequiredAdditionalDocumentState(documentId); if (oldDocument.TryGetTextAndVersion(out var oldTextAndVersion) && textAndVersion == oldTextAndVersion) { - return this; + var oldProject = GetRequiredProjectState(documentId.ProjectId); + return new(this, oldProject, oldProject); } return UpdateAdditionalDocumentState(oldDocument.UpdateText(textAndVersion, mode), contentChanged: true); @@ -1330,12 +1005,13 @@ public SolutionState WithAdditionalDocumentText(DocumentId documentId, TextAndVe /// Creates a new solution instance with the analyzer config document specified updated to have the text /// and version specified. /// - public SolutionState WithAnalyzerConfigDocumentText(DocumentId documentId, TextAndVersion textAndVersion, PreservationMode mode = PreservationMode.PreserveValue) + public StateChange WithAnalyzerConfigDocumentText(DocumentId documentId, TextAndVersion textAndVersion, PreservationMode mode = PreservationMode.PreserveValue) { var oldDocument = GetRequiredAnalyzerConfigDocumentState(documentId); if (oldDocument.TryGetTextAndVersion(out var oldTextAndVersion) && textAndVersion == oldTextAndVersion) { - return this; + var oldProject = GetRequiredProjectState(documentId.ProjectId); + return new(this, oldProject, oldProject); } return UpdateAnalyzerConfigDocumentState(oldDocument.UpdateText(textAndVersion, mode)); @@ -1345,29 +1021,31 @@ public SolutionState WithAnalyzerConfigDocumentText(DocumentId documentId, TextA /// Creates a new solution instance with the document specified updated to have a syntax tree /// rooted by the specified syntax node. /// - public SolutionState WithDocumentSyntaxRoot(DocumentId documentId, SyntaxNode root, PreservationMode mode = PreservationMode.PreserveValue) + public StateChange WithDocumentSyntaxRoot(DocumentId documentId, SyntaxNode root, PreservationMode mode = PreservationMode.PreserveValue) { var oldDocument = GetRequiredDocumentState(documentId); if (oldDocument.TryGetSyntaxTree(out var oldTree) && oldTree.TryGetRoot(out var oldRoot) && oldRoot == root) { - return this; + var oldProject = GetRequiredProjectState(documentId.ProjectId); + return new(this, oldProject, oldProject); } return UpdateDocumentState(oldDocument.UpdateTree(root, mode), contentChanged: true); } - public SolutionState WithDocumentContentsFrom(DocumentId documentId, DocumentState documentState) + public StateChange WithDocumentContentsFrom(DocumentId documentId, DocumentState documentState) { var oldDocument = GetRequiredDocumentState(documentId); + var oldProject = GetRequiredProjectState(documentId.ProjectId); if (oldDocument == documentState) - return this; + return new(this, oldProject, oldProject); if (oldDocument.TextAndVersionSource == documentState.TextAndVersionSource && oldDocument.TreeSource == documentState.TreeSource) { - return this; + return new(this, oldProject, oldProject); } return UpdateDocumentState( @@ -1375,33 +1053,23 @@ public SolutionState WithDocumentContentsFrom(DocumentId documentId, DocumentSta contentChanged: true); } - private static async Task UpdateDocumentInCompilationAsync( - Compilation compilation, - DocumentState oldDocument, - DocumentState newDocument, - CancellationToken cancellationToken) - { - return compilation.ReplaceSyntaxTree( - await oldDocument.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false), - await newDocument.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false)); - } - /// /// Creates a new solution instance with the document specified updated to have the source /// code kind specified. /// - public SolutionState WithDocumentSourceCodeKind(DocumentId documentId, SourceCodeKind sourceCodeKind) + public StateChange WithDocumentSourceCodeKind(DocumentId documentId, SourceCodeKind sourceCodeKind) { var oldDocument = GetRequiredDocumentState(documentId); if (oldDocument.SourceCodeKind == sourceCodeKind) { - return this; + var oldProject = GetRequiredProjectState(documentId.ProjectId); + return new(this, oldProject, oldProject); } return UpdateDocumentState(oldDocument.UpdateSourceCodeKind(sourceCodeKind), contentChanged: true); } - public SolutionState UpdateDocumentTextLoader(DocumentId documentId, TextLoader loader, PreservationMode mode) + public StateChange UpdateDocumentTextLoader(DocumentId documentId, TextLoader loader, PreservationMode mode) { var oldDocument = GetRequiredDocumentState(documentId); @@ -1414,7 +1082,7 @@ public SolutionState UpdateDocumentTextLoader(DocumentId documentId, TextLoader /// Creates a new solution instance with the additional document specified updated to have the text /// supplied by the text loader. /// - public SolutionState UpdateAdditionalDocumentTextLoader(DocumentId documentId, TextLoader loader, PreservationMode mode) + public StateChange UpdateAdditionalDocumentTextLoader(DocumentId documentId, TextLoader loader, PreservationMode mode) { var oldDocument = GetRequiredAdditionalDocumentState(documentId); @@ -1427,7 +1095,7 @@ public SolutionState UpdateAdditionalDocumentTextLoader(DocumentId documentId, T /// Creates a new solution instance with the analyzer config document specified updated to have the text /// supplied by the text loader. /// - public SolutionState UpdateAnalyzerConfigDocumentTextLoader(DocumentId documentId, TextLoader loader, PreservationMode mode) + public StateChange UpdateAnalyzerConfigDocumentTextLoader(DocumentId documentId, TextLoader loader, PreservationMode mode) { var oldDocument = GetRequiredAnalyzerConfigDocumentState(documentId); @@ -1436,7 +1104,7 @@ public SolutionState UpdateAnalyzerConfigDocumentTextLoader(DocumentId documentI return UpdateAnalyzerConfigDocumentState(oldDocument.UpdateText(loader, mode)); } - private SolutionState UpdateDocumentState(DocumentState newDocument, bool contentChanged) + private StateChange UpdateDocumentState(DocumentState newDocument, bool contentChanged) { var oldProject = GetProjectState(newDocument.Id.ProjectId)!; var newProject = oldProject.UpdateDocument(newDocument, contentChanged); @@ -1448,12 +1116,12 @@ private SolutionState UpdateDocumentState(DocumentState newDocument, bool conten var newFilePathToDocumentIdsMap = CreateFilePathToDocumentIdsMapWithFilePath(newDocument.Id, oldDocument.FilePath, newDocument.FilePath); return ForkProject( + oldProject, newProject, - new CompilationAndGeneratorDriverTranslationAction.TouchDocumentAction(oldDocument, newDocument), newFilePathToDocumentIdsMap: newFilePathToDocumentIdsMap); } - private SolutionState UpdateAdditionalDocumentState(AdditionalDocumentState newDocument, bool contentChanged) + private StateChange UpdateAdditionalDocumentState(AdditionalDocumentState newDocument, bool contentChanged) { var oldProject = GetProjectState(newDocument.Id.ProjectId)!; var newProject = oldProject.UpdateAdditionalDocument(newDocument, contentChanged); @@ -1461,14 +1129,10 @@ private SolutionState UpdateAdditionalDocumentState(AdditionalDocumentState newD // This method shouldn't have been called if the document has not changed. Debug.Assert(oldProject != newProject); - var oldDocument = oldProject.AdditionalDocumentStates.GetRequiredState(newDocument.Id); - - return ForkProject( - newProject, - translate: new CompilationAndGeneratorDriverTranslationAction.TouchAdditionalDocumentAction(oldDocument, newDocument)); + return ForkProject(oldProject, newProject); } - private SolutionState UpdateAnalyzerConfigDocumentState(AnalyzerConfigDocumentState newDocument) + private StateChange UpdateAnalyzerConfigDocumentState(AnalyzerConfigDocumentState newDocument) { var oldProject = GetProjectState(newDocument.Id.ProjectId)!; var newProject = oldProject.UpdateAnalyzerConfigDocument(newDocument); @@ -1476,8 +1140,7 @@ private SolutionState UpdateAnalyzerConfigDocumentState(AnalyzerConfigDocumentSt // This method shouldn't have been called if the document has not changed. Debug.Assert(oldProject != newProject); - return ForkProject(newProject, - newProject.CompilationOptions != null ? new CompilationAndGeneratorDriverTranslationAction.ProjectCompilationOptionsAction(newProject, isAnalyzerConfigChange: true) : null); + return ForkProject(oldProject, newProject); } /// @@ -1486,12 +1149,11 @@ private SolutionState UpdateAnalyzerConfigDocumentState(AnalyzerConfigDocumentSt /// are fixed-up if the change to the new project affects its public metadata, and old /// dependent compilations are forgotten. /// - private SolutionState ForkProject( + public StateChange ForkProject( + ProjectState oldProjectState, ProjectState newProjectState, - CompilationAndGeneratorDriverTranslationAction? translate = null, ProjectDependencyGraph? newDependencyGraph = null, - ImmutableDictionary>? newFilePathToDocumentIdsMap = null, - bool forkTracker = true) + ImmutableDictionary>? newFilePathToDocumentIdsMap = null) { var projectId = newProjectState.Id; @@ -1499,24 +1161,13 @@ private SolutionState ForkProject( var newStateMap = _projectIdToProjectStateMap.SetItem(projectId, newProjectState); newDependencyGraph ??= _dependencyGraph; - var newTrackerMap = CreateCompilationTrackerMap(projectId, newDependencyGraph); - // If we have a tracker for this project, then fork it as well (along with the - // translation action and store it in the tracker map. - if (newTrackerMap.TryGetValue(projectId, out var tracker)) - { - newTrackerMap = newTrackerMap.Remove(projectId); - - if (forkTracker) - { - newTrackerMap = newTrackerMap.Add(projectId, tracker.Fork(newProjectState, translate)); - } - } - return this.Branch( + var newSolutionState = this.Branch( idToProjectStateMap: newStateMap, - projectIdToTrackerMap: newTrackerMap, dependencyGraph: newDependencyGraph, filePathToDocumentIdsMap: newFilePathToDocumentIdsMap ?? _filePathToDocumentIdsMap); + + return new(newSolutionState, oldProjectState, newProjectState); } /// @@ -1535,7 +1186,7 @@ public ImmutableArray GetDocumentIdsWithFilePath(string? filePath) : ImmutableArray.Empty; } - private static ProjectDependencyGraph CreateDependencyGraph( + public static ProjectDependencyGraph CreateDependencyGraph( IReadOnlyList projectIds, ImmutableDictionary projectStates) { @@ -1547,42 +1198,6 @@ private static ProjectDependencyGraph CreateDependencyGraph( return new ProjectDependencyGraph(projectIds.ToImmutableHashSet(), map); } - private ImmutableDictionary CreateCompilationTrackerMap(ProjectId changedProjectId, ProjectDependencyGraph dependencyGraph) - { - if (_projectIdToTrackerMap.Count == 0) - return _projectIdToTrackerMap; - - using var _ = ArrayBuilder>.GetInstance(_projectIdToTrackerMap.Count, out var newTrackerInfo); - var allReused = true; - foreach (var (id, tracker) in _projectIdToTrackerMap) - { - var localTracker = tracker; - if (!CanReuse(id)) - { - localTracker = tracker.Fork(tracker.ProjectState, translate: null); - allReused = false; - } - - newTrackerInfo.Add(new KeyValuePair(id, localTracker)); - } - - if (allReused) - return _projectIdToTrackerMap; - - return ImmutableDictionary.CreateRange(newTrackerInfo); - - // Returns true if 'tracker' can be reused for project 'id' - bool CanReuse(ProjectId id) - { - if (id == changedProjectId) - { - return true; - } - - return !dependencyGraph.DoesProjectTransitivelyDependOnProject(id, changedProjectId); - } - } - public SolutionState WithOptions(SolutionOptionSet options) => Branch(options: options); @@ -1620,97 +1235,6 @@ public SolutionState WithAnalyzerReferences(IReadOnlyList ana return Branch(analyzerReferences: analyzerReferences); } - // this lock guards all the mutable fields (do not share lock with derived classes) - private NonReentrantLock? _stateLockBackingField; - private NonReentrantLock StateLock - { - get - { - // TODO: why did I need to do a nullable suppression here? - return LazyInitializer.EnsureInitialized(ref _stateLockBackingField, NonReentrantLock.Factory)!; - } - } - - private WeakReference? _latestSolutionWithPartialCompilation; - private DateTime _timeOfLatestSolutionWithPartialCompilation; - private DocumentId? _documentIdOfLatestSolutionWithPartialCompilation; - - /// - /// Creates a branch of the solution that has its compilations frozen in whatever state they are in at the time, assuming a background compiler is - /// busy building this compilations. - /// - /// A compilation for the project containing the specified document id will be guaranteed to exist with at least the syntax tree for the document. - /// - /// This not intended to be the public API, use Document.WithFrozenPartialSemantics() instead. - /// - public SolutionState WithFrozenPartialCompilationIncludingSpecificDocument(DocumentId documentId, CancellationToken cancellationToken) - { - try - { - var allDocumentIds = GetRelatedDocumentIds(documentId); - using var _ = ArrayBuilder<(DocumentState, SyntaxTree)>.GetInstance(allDocumentIds.Length, out var builder); - - foreach (var currentDocumentId in allDocumentIds) - { - var document = this.GetRequiredDocumentState(currentDocumentId); - builder.Add((document, document.GetSyntaxTree(cancellationToken))); - } - - using (this.StateLock.DisposableWait(cancellationToken)) - { - // in progress solutions are disabled for some testing - if (Services.GetService()?.IsPartialSolutionDisabled == true) - { - return this; - } - - SolutionState? currentPartialSolution = null; - _latestSolutionWithPartialCompilation?.TryGetTarget(out currentPartialSolution); - - var reuseExistingPartialSolution = - currentPartialSolution != null && - (DateTime.UtcNow - _timeOfLatestSolutionWithPartialCompilation).TotalSeconds < 0.1 && - _documentIdOfLatestSolutionWithPartialCompilation == documentId; - - if (reuseExistingPartialSolution) - { - SolutionLogger.UseExistingPartialSolution(); - return currentPartialSolution!; - } - - var newIdToProjectStateMap = _projectIdToProjectStateMap; - var newIdToTrackerMap = _projectIdToTrackerMap; - - foreach (var (doc, tree) in builder) - { - // if we don't have one or it is stale, create a new partial solution - var tracker = this.GetCompilationTracker(doc.Id.ProjectId); - var newTracker = tracker.FreezePartialStateWithTree(this, doc, tree, cancellationToken); - - Contract.ThrowIfFalse(newIdToProjectStateMap.ContainsKey(doc.Id.ProjectId)); - newIdToProjectStateMap = newIdToProjectStateMap.SetItem(doc.Id.ProjectId, newTracker.ProjectState); - newIdToTrackerMap = newIdToTrackerMap.SetItem(doc.Id.ProjectId, newTracker); - } - - currentPartialSolution = this.Branch( - idToProjectStateMap: newIdToProjectStateMap, - projectIdToTrackerMap: newIdToTrackerMap, - dependencyGraph: CreateDependencyGraph(ProjectIds, newIdToProjectStateMap)); - - _latestSolutionWithPartialCompilation = new WeakReference(currentPartialSolution); - _timeOfLatestSolutionWithPartialCompilation = DateTime.UtcNow; - _documentIdOfLatestSolutionWithPartialCompilation = documentId; - - SolutionLogger.CreatePartialSolution(); - return currentPartialSolution; - } - } - catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken, ErrorSeverity.Critical)) - { - throw ExceptionUtilities.Unreachable(); - } - } - public ImmutableArray GetRelatedDocumentIds(DocumentId documentId) { var projectState = this.GetProjectState(documentId.ProjectId); @@ -1757,295 +1281,6 @@ public ImmutableArray GetRelatedDocumentIds(DocumentId documentId) (solution: this, projectState.Language)); } - /// - /// Creates a new solution instance with all the documents specified updated to have the same specified text. - /// - public SolutionState WithDocumentText(IEnumerable documentIds, SourceText text, PreservationMode mode) - { - var solution = this; - - foreach (var documentId in documentIds) - { - if (documentId == null) - { - continue; - } - - var doc = GetProjectState(documentId.ProjectId)?.DocumentStates.GetState(documentId); - if (doc != null) - { - if (!doc.TryGetText(out var existingText) || existingText != text) - { - solution = solution.WithDocumentText(documentId, text, mode); - } - } - } - - return solution; - } - - public bool TryGetCompilation(ProjectId projectId, [NotNullWhen(returnValue: true)] out Compilation? compilation) - { - CheckContainsProject(projectId); - compilation = null; - - return this.TryGetCompilationTracker(projectId, out var tracker) - && tracker.TryGetCompilation(out compilation); - } - - /// - /// Returns the compilation for the specified . Can return when the project - /// does not support compilations. - /// - /// - /// The compilation is guaranteed to have a syntax tree for each document of the project. - /// - private Task GetCompilationAsync(ProjectId projectId, CancellationToken cancellationToken) - { - // TODO: figure out where this is called and why the nullable suppression is required - return GetCompilationAsync(GetProjectState(projectId)!, cancellationToken); - } - - /// - /// Returns the compilation for the specified . Can return when the project - /// does not support compilations. - /// - /// - /// The compilation is guaranteed to have a syntax tree for each document of the project. - /// - public Task GetCompilationAsync(ProjectState project, CancellationToken cancellationToken) - { - return project.SupportsCompilation - ? GetCompilationTracker(project.Id).GetCompilationAsync(this, cancellationToken).AsNullable() - : SpecializedTasks.Null(); - } - - /// - /// Return reference completeness for the given project and all projects this references. - /// - public Task HasSuccessfullyLoadedAsync(ProjectState project, CancellationToken cancellationToken) - { - // return HasAllInformation when compilation is not supported. - // regardless whether project support compilation or not, if projectInfo is not complete, we can't guarantee its reference completeness - return project.SupportsCompilation - ? this.GetCompilationTracker(project.Id).HasSuccessfullyLoadedAsync(this, cancellationToken) - : project.HasAllInformation ? SpecializedTasks.True : SpecializedTasks.False; - } - - /// - /// Returns the generated document states for source generated documents. - /// - public ValueTask> GetSourceGeneratedDocumentStatesAsync(ProjectState project, CancellationToken cancellationToken) - { - return project.SupportsCompilation - ? GetCompilationTracker(project.Id).GetSourceGeneratedDocumentStatesAsync(this, cancellationToken) - : new(TextDocumentStates.Empty); - } - - public ValueTask> GetSourceGeneratorDiagnosticsAsync(ProjectState project, CancellationToken cancellationToken) - { - return project.SupportsCompilation - ? GetCompilationTracker(project.Id).GetSourceGeneratorDiagnosticsAsync(this, cancellationToken) - : new(ImmutableArray.Empty); - } - - /// - /// Returns the for a source generated document that has already been generated and observed. - /// - /// - /// This is only safe to call if you already have seen the SyntaxTree or equivalent that indicates the document state has already been - /// generated. This method exists to implement and is best avoided unless you're doing something - /// similarly tricky like that. - /// - public SourceGeneratedDocumentState? TryGetSourceGeneratedDocumentStateForAlreadyGeneratedId(DocumentId documentId) - { - return GetCompilationTracker(documentId.ProjectId).TryGetSourceGeneratedDocumentStateForAlreadyGeneratedId(documentId); - } - - /// - /// Returns a new SolutionState that will always produce a specific output for a generated file. This is used only in the - /// implementation of where if a user has a source - /// generated file open, we need to make sure everything lines up. - /// - public SolutionState WithFrozenSourceGeneratedDocument(SourceGeneratedDocumentIdentity documentIdentity, SourceText sourceText) - { - // We won't support freezing multiple source generated documents at once. Although nothing in the implementation - // of this method would have problems, this simplifies the handling of serializing this solution to out-of-proc. - // Since we only produce these snapshots from an open document, there should be no way to observe this, so this assertion - // also serves as a good check on the system. If down the road we need to support this, we can remove this check and - // update the out-of-process serialization logic accordingly. - Contract.ThrowIfTrue(_frozenSourceGeneratedDocumentState != null, "We shouldn't be calling WithFrozenSourceGeneratedDocument on a solution with a frozen source generated document."); - - var existingGeneratedState = TryGetSourceGeneratedDocumentStateForAlreadyGeneratedId(documentIdentity.DocumentId); - SourceGeneratedDocumentState newGeneratedState; - - if (existingGeneratedState != null) - { - newGeneratedState = existingGeneratedState - .WithText(sourceText) - .WithParseOptions(existingGeneratedState.ParseOptions); - - // If the content already matched, we can just reuse the existing state - if (newGeneratedState == existingGeneratedState) - { - return this; - } - } - else - { - var projectState = GetRequiredProjectState(documentIdentity.DocumentId.ProjectId); - newGeneratedState = SourceGeneratedDocumentState.Create( - documentIdentity, - sourceText, - projectState.ParseOptions!, - projectState.LanguageServices, - // Just compute the checksum from the source text passed in. - originalSourceTextChecksum: null); - } - - var projectId = documentIdentity.DocumentId.ProjectId; - var newTrackerMap = CreateCompilationTrackerMap(projectId, _dependencyGraph); - - // We want to create a new snapshot with a new compilation tracker that will do this replacement. - // If we already have an existing tracker we'll just wrap that (so we also are reusing any underlying - // computations). If we don't have one, we'll create one and then wrap it. - if (!newTrackerMap.TryGetValue(projectId, out var existingTracker)) - { - existingTracker = CreateCompilationTracker(projectId, this); - } - - newTrackerMap = newTrackerMap.SetItem( - projectId, - new GeneratedFileReplacingCompilationTracker(existingTracker, newGeneratedState)); - - return this.Branch( - projectIdToTrackerMap: newTrackerMap, - frozenSourceGeneratedDocument: newGeneratedState); - } - - /// - /// Undoes the operation of ; any frozen source generated document is allowed - /// to have it's real output again. - /// - public SolutionState WithoutFrozenSourceGeneratedDocuments() - { - // If there's nothing frozen, there's nothing to do. - if (_frozenSourceGeneratedDocumentState == null) - return this; - - var projectId = _frozenSourceGeneratedDocumentState.Id.ProjectId; - - // Since we previously froze this document, we should have a CompilationTracker entry for it, and it should be a - // GeneratedFileReplacingCompilationTracker. To undo the operation, we'll just restore the original CompilationTracker. - var newTrackerMap = CreateCompilationTrackerMap(projectId, _dependencyGraph); - Contract.ThrowIfFalse(newTrackerMap.TryGetValue(projectId, out var existingTracker)); - var replacingItemTracker = existingTracker as GeneratedFileReplacingCompilationTracker; - Contract.ThrowIfNull(replacingItemTracker); - newTrackerMap = newTrackerMap.SetItem(projectId, replacingItemTracker.UnderlyingTracker); - - return this.Branch( - projectIdToTrackerMap: newTrackerMap, - frozenSourceGeneratedDocument: null); - } - - /// - public SolutionState WithCachedSourceGeneratorState(ProjectId projectToUpdate, Project projectWithCachedGeneratorState) - { - CheckContainsProject(projectToUpdate); - - // First see if we have a generator driver that we can get from the other project. - if (!projectWithCachedGeneratorState.Solution.State.TryGetCompilationTracker(projectWithCachedGeneratorState.Id, out var tracker) || - tracker.GeneratorDriver is null) - { - // We don't actually have any state at all, so no change. - return this; - } - - var projectToUpdateState = GetRequiredProjectState(projectToUpdate); - - return ForkProject( - projectToUpdateState, - translate: new CompilationAndGeneratorDriverTranslationAction.ReplaceGeneratorDriverAction( - tracker.GeneratorDriver, - newProjectState: projectToUpdateState)); - } - - /// - /// Symbols need to be either or . - /// - private static readonly ConditionalWeakTable s_assemblyOrModuleSymbolToProjectMap = new(); - - /// - /// Get a metadata reference for the project's compilation. Returns upon failure, which - /// can happen when trying to build a skeleton reference that fails to build. - /// - public Task GetMetadataReferenceAsync(ProjectReference projectReference, ProjectState fromProject, CancellationToken cancellationToken) - { - try - { - // Get the compilation state for this project. If it's not already created, then this - // will create it. Then force that state to completion and get a metadata reference to it. - var tracker = this.GetCompilationTracker(projectReference.ProjectId); - return GetMetadataReferenceAsync(tracker, fromProject, projectReference, cancellationToken); - } - catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken, ErrorSeverity.Critical)) - { - throw ExceptionUtilities.Unreachable(); - } - } - - /// - /// Get a metadata reference to this compilation info's compilation with respect to - /// another project. For cross language references produce a skeletal assembly. If the - /// compilation is not available, it is built. If a skeletal assembly reference is - /// needed and does not exist, it is also built. - /// - private async Task GetMetadataReferenceAsync( - ICompilationTracker tracker, ProjectState fromProject, ProjectReference projectReference, CancellationToken cancellationToken) - { - try - { - // If same language then we can wrap the other project's compilation into a compilation reference - if (tracker.ProjectState.LanguageServices == fromProject.LanguageServices) - { - // otherwise, base it off the compilation by building it first. - var compilation = await tracker.GetCompilationAsync(this, cancellationToken).ConfigureAwait(false); - return compilation.ToMetadataReference(projectReference.Aliases, projectReference.EmbedInteropTypes); - } - - // otherwise get a metadata only image reference that is built by emitting the metadata from the - // referenced project's compilation and re-importing it. - using (Logger.LogBlock(FunctionId.Workspace_SkeletonAssembly_GetMetadataOnlyImage, cancellationToken)) - { - var properties = new MetadataReferenceProperties(aliases: projectReference.Aliases, embedInteropTypes: projectReference.EmbedInteropTypes); - return await tracker.SkeletonReferenceCache.GetOrBuildReferenceAsync( - tracker, this, properties, cancellationToken).ConfigureAwait(false); - } - } - catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken, ErrorSeverity.Critical)) - { - throw ExceptionUtilities.Unreachable(); - } - } - - /// - /// Attempt to get the best readily available compilation for the project. It may be a - /// partially built compilation. - /// - private MetadataReference? GetPartialMetadataReference( - ProjectReference projectReference, - ProjectState fromProject) - { - // Try to get the compilation state for this project. If it doesn't exist, don't do any - // more work. - if (!_projectIdToTrackerMap.TryGetValue(projectReference.ProjectId, out var state)) - { - return null; - } - - return state.GetPartialMetadataReference(fromProject, projectReference); - } - /// /// Gets a that details the dependencies between projects for this solution. /// @@ -2060,7 +1295,7 @@ private void CheckNotContainsProject(ProjectId projectId) } } - private void CheckContainsProject(ProjectId projectId) + internal void CheckContainsProject(ProjectId projectId) { if (!this.ContainsProject(projectId)) { @@ -2079,15 +1314,5 @@ internal bool ContainsAnalyzerReference(ProjectId projectId, AnalyzerReference a internal bool ContainsTransitiveReference(ProjectId fromProjectId, ProjectId toProjectId) => _dependencyGraph.GetProjectsThatThisProjectTransitivelyDependsOn(fromProjectId).Contains(toProjectId); - - internal TestAccessor GetTestAccessor() => new TestAccessor(this); - - internal readonly struct TestAccessor(SolutionState solutionState) - { - public GeneratorDriver? GetGeneratorDriver(Project project) - { - return project.SupportsCompilation ? solutionState.GetCompilationTracker(project.Id).GeneratorDriver : null; - } - } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs index 376b95c2f5810..359ba018b4fa5 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState_Checksum.cs @@ -19,10 +19,19 @@ namespace Microsoft.CodeAnalysis { - internal partial class SolutionState + internal partial class SolutionCompilationState { private static readonly ConditionalWeakTable, IReadOnlyList> s_projectIdToSortedProjectsMap = new(); + // Checksums for this solution state + private readonly AsyncLazy _lazyChecksums; + + /// + /// Mapping from project-id to the checksums needed to synchronize it (and the projects it depends on) over + /// to an OOP host. Lock this specific field before reading/writing to it. + /// + private readonly Dictionary> _lazyProjectChecksums = new(); + public static IReadOnlyList GetOrCreateSortedProjectIds(IReadOnlyList unorderedList) => s_projectIdToSortedProjectsMap.GetValue(unorderedList, projectIds => projectIds.OrderBy(id => id.Id).ToImmutableArray()); @@ -88,7 +97,7 @@ void AddReferencedProjects(HashSet result, ProjectId projectId) if (!result.Add(projectId)) return; - var projectState = this.GetProjectState(projectId); + var projectState = this.Solution.GetProjectState(projectId); if (projectState == null) return; @@ -100,7 +109,7 @@ void AddReferencedProjects(HashSet result, ProjectId projectId) // host to remove the reference. We do not expose this through the full Solution/Project which // filters out this case already (in Project.ProjectReferences). However, becausde we're at the // ProjectState level it cannot do that filtering unless examined through us (the SolutionState). - if (this.ProjectStates.ContainsKey(refProject.ProjectId)) + if (this.Solution.ProjectStates.ContainsKey(refProject.ProjectId)) AddReferencedProjects(result, refProject.ProjectId); } } @@ -121,13 +130,13 @@ private async Task ComputeChecksumsAsync( { try { - using (Logger.LogBlock(FunctionId.SolutionState_ComputeChecksumsAsync, FilePath, cancellationToken)) + using (Logger.LogBlock(FunctionId.SolutionState_ComputeChecksumsAsync, this.Solution.FilePath, cancellationToken)) { // get states by id order to have deterministic checksum. Limit expensive computation to the // requested set of projects if applicable. - var orderedProjectIds = GetOrCreateSortedProjectIds(ProjectIds); + var orderedProjectIds = GetOrCreateSortedProjectIds(this.Solution.ProjectIds); var projectChecksumTasks = orderedProjectIds - .Select(id => (state: ProjectStates[id], mustCompute: projectsToInclude == null || projectsToInclude.Contains(id))) + .Select(id => (state: this.Solution.ProjectStates[id], mustCompute: projectsToInclude == null || projectsToInclude.Contains(id))) .Where(t => RemoteSupportedLanguages.IsSupported(t.state.Language)) .Select(async t => { @@ -147,8 +156,8 @@ private async Task ComputeChecksumsAsync( }) .ToArray(); - var serializer = Services.GetRequiredService(); - var attributesChecksum = this.SolutionAttributes.Checksum; + var serializer = this.Solution.Services.GetRequiredService(); + var attributesChecksum = this.Solution.SolutionAttributes.Checksum; var frozenSourceGeneratedDocumentIdentityChecksum = Checksum.Null; var frozenSourceGeneratedDocumentTextChecksum = Checksum.Null; @@ -159,7 +168,8 @@ private async Task ComputeChecksumsAsync( frozenSourceGeneratedDocumentTextChecksum = (await FrozenSourceGeneratedDocumentState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false)).Text; } - var analyzerReferenceChecksums = ChecksumCache.GetOrCreateChecksumCollection(AnalyzerReferences, serializer, cancellationToken); + var analyzerReferenceChecksums = ChecksumCache.GetOrCreateChecksumCollection( + this.Solution.AnalyzerReferences, serializer, cancellationToken); var allResults = await Task.WhenAll(projectChecksumTasks).ConfigureAwait(false); using var _1 = ArrayBuilder.GetInstance(allResults.Length, out var projectIds); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs index a725572b5bbf6..e54343d910e7c 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/StateChecksums.cs @@ -70,7 +70,7 @@ public static SolutionStateChecksums Deserialize(ObjectReader reader) } public async Task FindAsync( - SolutionState state, + SolutionCompilationState compilationState, AssetHint assetHint, HashSet searchingChecksumsLeft, Dictionary result, @@ -85,28 +85,28 @@ public async Task FindAsync( result[Checksum] = this; if (searchingChecksumsLeft.Remove(Attributes)) - result[Attributes] = state.SolutionAttributes; + result[Attributes] = compilationState.Solution.SolutionAttributes; if (searchingChecksumsLeft.Remove(FrozenSourceGeneratedDocumentIdentity)) { - Contract.ThrowIfNull(state.FrozenSourceGeneratedDocumentState, "We should not have had a FrozenSourceGeneratedDocumentIdentity checksum if we didn't have a text in the first place."); - result[FrozenSourceGeneratedDocumentIdentity] = state.FrozenSourceGeneratedDocumentState.Identity; + Contract.ThrowIfNull(compilationState.FrozenSourceGeneratedDocumentState, "We should not have had a FrozenSourceGeneratedDocumentIdentity checksum if we didn't have a text in the first place."); + result[FrozenSourceGeneratedDocumentIdentity] = compilationState.FrozenSourceGeneratedDocumentState.Identity; } if (searchingChecksumsLeft.Remove(FrozenSourceGeneratedDocumentText)) { - Contract.ThrowIfNull(state.FrozenSourceGeneratedDocumentState, "We should not have had a FrozenSourceGeneratedDocumentState checksum if we didn't have a text in the first place."); - result[FrozenSourceGeneratedDocumentText] = await SerializableSourceText.FromTextDocumentStateAsync(state.FrozenSourceGeneratedDocumentState, cancellationToken).ConfigureAwait(false); + Contract.ThrowIfNull(compilationState.FrozenSourceGeneratedDocumentState, "We should not have had a FrozenSourceGeneratedDocumentState checksum if we didn't have a text in the first place."); + result[FrozenSourceGeneratedDocumentText] = await SerializableSourceText.FromTextDocumentStateAsync(compilationState.FrozenSourceGeneratedDocumentState, cancellationToken).ConfigureAwait(false); } - ChecksumCollection.Find(state.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, result, cancellationToken); + ChecksumCollection.Find(compilationState.Solution.AnalyzerReferences, AnalyzerReferences, searchingChecksumsLeft, result, cancellationToken); if (searchingChecksumsLeft.Count == 0) return; if (assetHint.ProjectId != null) { - var projectState = state.GetProjectState(assetHint.ProjectId); + var projectState = compilationState.Solution.GetProjectState(assetHint.ProjectId); if (projectState != null && projectState.TryGetStateChecksums(out var projectStateChecksums)) { @@ -121,7 +121,7 @@ public async Task FindAsync( // This ensures that when we are trying to sync the projects referenced by a SolutionStateChecksums' instance // that we don't unnecessarily walk all documents looking just for those. - foreach (var (_, projectState) in state.ProjectStates) + foreach (var (_, projectState) in compilationState.Solution.ProjectStates) { if (searchingChecksumsLeft.Count == 0) break; @@ -135,7 +135,7 @@ public async Task FindAsync( // Now actually do the depth first search into each project. - foreach (var (_, projectState) in state.ProjectStates) + foreach (var (_, projectState) in compilationState.Solution.ProjectStates) { if (searchingChecksumsLeft.Count == 0) break; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs index 4818cdd199b29..49ec3ed50b694 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentStates.cs @@ -69,8 +69,8 @@ public bool Contains(DocumentId id) public bool TryGetState(DocumentId documentId, [NotNullWhen(true)] out TState? state) => _map.TryGetValue(documentId, out state); - public TState? GetState(DocumentId documentId) - => _map.TryGetValue(documentId, out var state) ? state : null; + public TState? GetState(DocumentId? documentId) + => documentId != null && _map.TryGetValue(documentId, out var state) ? state : null; public TState GetRequiredState(DocumentId documentId) => _map.TryGetValue(documentId, out var state) ? state : throw ExceptionUtilities.Unreachable(); diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index ed85f322f3b86..bfdafafa9c5b7 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -2211,7 +2211,7 @@ private static async Task ValidateSolutionAndCompilationsAsync(Solution solution { if (solution.ContainsProject(referenced.ProjectId)) { - var referencedMetadata = await solution.State.GetMetadataReferenceAsync(referenced, solution.GetProjectState(project.Id), CancellationToken.None); + var referencedMetadata = await solution.CompilationState.GetMetadataReferenceAsync(referenced, solution.GetProjectState(project.Id), CancellationToken.None); Assert.NotNull(referencedMetadata); if (referencedMetadata is CompilationReference compilationReference) { @@ -3269,9 +3269,10 @@ public async Task TestDocumentFileAccessFailureMissingFile() var pid = ProjectId.CreateNewId(); var did = DocumentId.CreateNewId(pid); - solution = solution.AddProject(pid, "goo", "goo", LanguageNames.CSharp) - .AddDocument(did, "x", new WorkspaceFileTextLoader(solution.Services, @"C:\doesnotexist.cs", Encoding.UTF8)) - .WithDocumentFilePath(did, "document path"); + solution = solution + .AddProject(pid, "goo", "goo", LanguageNames.CSharp) + .AddDocument(did, "x", new WorkspaceFileTextLoader(solution.Services, @"C:\doesnotexist.cs", Encoding.UTF8)) + .WithDocumentFilePath(did, "document path"); var doc = solution.GetDocument(did); var text = await doc.GetTextAsync().ConfigureAwait(false); @@ -3282,7 +3283,7 @@ public async Task TestDocumentFileAccessFailureMissingFile() Assert.Equal("", text.ToString()); // Verify invariant: The compilation is guaranteed to have a syntax tree for each document of the project (even if the contnet fails to load). - var compilation = await solution.State.GetCompilationAsync(doc.Project.State, CancellationToken.None).ConfigureAwait(false); + var compilation = await doc.Project.GetCompilationAsync(CancellationToken.None).ConfigureAwait(false); var syntaxTree = compilation.SyntaxTrees.Single(); Assert.Equal("", syntaxTree.ToString()); } diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs index 8128f519b5b6a..ac2d9f4ab0d5d 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionWithSourceGeneratorTests.cs @@ -174,7 +174,7 @@ public async Task IncrementalSourceGeneratorInvokedCorrectNumberOfTimes() var compilation = await project.GetRequiredCompilationAsync(CancellationToken.None); - var generatorDriver = project.Solution.State.GetTestAccessor().GetGeneratorDriver(project)!; + var generatorDriver = project.Solution.CompilationState.GetTestAccessor().GetGeneratorDriver(project)!; var runResult = generatorDriver!.GetRunResult().Results[0]; @@ -194,7 +194,7 @@ public async Task IncrementalSourceGeneratorInvokedCorrectNumberOfTimes() project = project.AdditionalDocuments.First().WithAdditionalDocumentText(SourceText.From("Changed text!")).Project; compilation = await project.GetRequiredCompilationAsync(CancellationToken.None); - generatorDriver = project.Solution.State.GetTestAccessor().GetGeneratorDriver(project)!; + generatorDriver = project.Solution.CompilationState.GetTestAccessor().GetGeneratorDriver(project)!; runResult = generatorDriver.GetRunResult().Results[0]; Assert.Equal(2, compilation.SyntaxTrees.Count()); @@ -218,7 +218,7 @@ public async Task IncrementalSourceGeneratorInvokedCorrectNumberOfTimes() project = project.AddDocument("Source.cs", SourceText.From("")).Project; compilation = await project.GetRequiredCompilationAsync(CancellationToken.None); - generatorDriver = project.Solution.State.GetTestAccessor().GetGeneratorDriver(project)!; + generatorDriver = project.Solution.CompilationState.GetTestAccessor().GetGeneratorDriver(project)!; runResult = generatorDriver.GetRunResult().Results[0]; // We have one extra syntax tree now, but it did not require any invocations of the incremental generator. diff --git a/src/Workspaces/Remote/Core/BrokeredServiceConnection.cs b/src/Workspaces/Remote/Core/BrokeredServiceConnection.cs index 9938f30a9f77f..57eb74547f411 100644 --- a/src/Workspaces/Remote/Core/BrokeredServiceConnection.cs +++ b/src/Workspaces/Remote/Core/BrokeredServiceConnection.cs @@ -162,7 +162,7 @@ public override async ValueTask> TryInvokeAsync(Func< // solution, no callback - public override async ValueTask TryInvokeAsync(SolutionState solution, Func invocation, CancellationToken cancellationToken) + public override async ValueTask TryInvokeAsync(SolutionCompilationState solution, Func invocation, CancellationToken cancellationToken) { try { @@ -178,7 +178,7 @@ public override async ValueTask TryInvokeAsync(SolutionState solution, Fun } } - public override async ValueTask> TryInvokeAsync(SolutionState solution, Func> invocation, CancellationToken cancellationToken) + public override async ValueTask> TryInvokeAsync(SolutionCompilationState solution, Func> invocation, CancellationToken cancellationToken) { try { @@ -195,7 +195,7 @@ public override async ValueTask> TryInvokeAsync(Solut // project, no callback - public override async ValueTask TryInvokeAsync(SolutionState solution, ProjectId projectId, Func invocation, CancellationToken cancellationToken) + public override async ValueTask TryInvokeAsync(SolutionCompilationState solution, ProjectId projectId, Func invocation, CancellationToken cancellationToken) { try { @@ -211,7 +211,7 @@ public override async ValueTask TryInvokeAsync(SolutionState solution, Pro } } - public override async ValueTask> TryInvokeAsync(SolutionState solution, ProjectId projectId, Func> invocation, CancellationToken cancellationToken) + public override async ValueTask> TryInvokeAsync(SolutionCompilationState solution, ProjectId projectId, Func> invocation, CancellationToken cancellationToken) { try { @@ -228,7 +228,7 @@ public override async ValueTask> TryInvokeAsync(Solut // solution, callback - public override async ValueTask TryInvokeAsync(SolutionState solution, Func invocation, CancellationToken cancellationToken) + public override async ValueTask TryInvokeAsync(SolutionCompilationState solution, Func invocation, CancellationToken cancellationToken) { Contract.ThrowIfFalse(_callbackDispatcher is not null); @@ -247,7 +247,7 @@ public override async ValueTask TryInvokeAsync(SolutionState solution, Fun } } - public override async ValueTask> TryInvokeAsync(SolutionState solution, Func> invocation, CancellationToken cancellationToken) + public override async ValueTask> TryInvokeAsync(SolutionCompilationState solution, Func> invocation, CancellationToken cancellationToken) { Contract.ThrowIfFalse(_callbackDispatcher is not null); @@ -266,7 +266,7 @@ public override async ValueTask> TryInvokeAsync(Solut // project, callback - public override async ValueTask TryInvokeAsync(SolutionState solution, ProjectId projectId, Func invocation, CancellationToken cancellationToken) + public override async ValueTask TryInvokeAsync(SolutionCompilationState solution, ProjectId projectId, Func invocation, CancellationToken cancellationToken) { Contract.ThrowIfFalse(_callbackDispatcher is not null); @@ -285,7 +285,7 @@ public override async ValueTask TryInvokeAsync(SolutionState solution, Pro } } - public override async ValueTask> TryInvokeAsync(SolutionState solution, ProjectId projectId, Func> invocation, CancellationToken cancellationToken) + public override async ValueTask> TryInvokeAsync(SolutionCompilationState solution, ProjectId projectId, Func> invocation, CancellationToken cancellationToken) { Contract.ThrowIfFalse(_callbackDispatcher is not null); @@ -304,7 +304,7 @@ public override async ValueTask> TryInvokeAsync(Solut // multi-solution, no callback - public override async ValueTask TryInvokeAsync(SolutionState solution1, SolutionState solution2, Func invocation, CancellationToken cancellationToken) + public override async ValueTask TryInvokeAsync(SolutionCompilationState solution1, SolutionCompilationState solution2, Func invocation, CancellationToken cancellationToken) { try { diff --git a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs index 4ea5ed655eaf7..cf5b250744d7d 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetStorage.Scope.cs @@ -18,12 +18,12 @@ internal partial class SolutionAssetStorage internal sealed partial class Scope( SolutionAssetStorage storage, Checksum solutionChecksum, - SolutionState solution) : IDisposable + SolutionCompilationState solution) : IDisposable { private readonly SolutionAssetStorage _storage = storage; public readonly Checksum SolutionChecksum = solutionChecksum; - public readonly SolutionState Solution = solution; + public readonly SolutionCompilationState Solution = solution; /// /// Will be disposed from when the last ref-count to this scope goes @@ -70,7 +70,7 @@ private async Task FindAssetsAsync( if (solutionState.TryGetStateChecksums(out var stateChecksums)) await stateChecksums.FindAsync(solutionState, assetHint, remainingChecksumsToFind, result, cancellationToken).ConfigureAwait(false); - foreach (var projectId in solutionState.ProjectIds) + foreach (var projectId in solutionState.Solution.ProjectIds) { if (remainingChecksumsToFind.Count == 0) break; diff --git a/src/Workspaces/Remote/Core/SolutionAssetStorage.cs b/src/Workspaces/Remote/Core/SolutionAssetStorage.cs index 49050acba04b9..42cdd9bc3293e 100644 --- a/src/Workspaces/Remote/Core/SolutionAssetStorage.cs +++ b/src/Workspaces/Remote/Core/SolutionAssetStorage.cs @@ -50,18 +50,18 @@ public Scope GetScope(Checksum solutionChecksum) /// Adds given snapshot into the storage. This snapshot will be available within the returned . /// public ValueTask StoreAssetsAsync(Solution solution, CancellationToken cancellationToken) - => StoreAssetsAsync(solution.State, cancellationToken); + => StoreAssetsAsync(solution.CompilationState, cancellationToken); /// public ValueTask StoreAssetsAsync(Project project, CancellationToken cancellationToken) - => StoreAssetsAsync(project.Solution.State, project.Id, cancellationToken); + => StoreAssetsAsync(project.Solution.CompilationState, project.Id, cancellationToken); /// - public ValueTask StoreAssetsAsync(SolutionState solution, CancellationToken cancellationToken) + public ValueTask StoreAssetsAsync(SolutionCompilationState solution, CancellationToken cancellationToken) => StoreAssetsAsync(solution, projectId: null, cancellationToken); /// - public async ValueTask StoreAssetsAsync(SolutionState solutionState, ProjectId? projectId, CancellationToken cancellationToken) + public async ValueTask StoreAssetsAsync(SolutionCompilationState solutionState, ProjectId? projectId, CancellationToken cancellationToken) { var checksum = projectId == null ? await solutionState.GetChecksumAsync(cancellationToken).ConfigureAwait(false) diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index 6fa145fa6878b..7322c8d965ad8 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -56,7 +56,7 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum, Ca // if needed again later. solution = solution.WithoutFrozenSourceGeneratedDocuments(); - var oldSolutionChecksums = await solution.State.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); + var oldSolutionChecksums = await solution.CompilationState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); var newSolutionChecksums = await _assetProvider.GetAssetAsync( assetHint: AssetHint.None, newSolutionChecksum, cancellationToken).ConfigureAwait(false); @@ -539,7 +539,7 @@ private async Task UpdateDocumentInfoAsync(TextDocument document, #if DEBUG private async Task ValidateChecksumAsync(Checksum checksumFromRequest, Solution incrementalSolutionBuilt, CancellationToken cancellationToken) { - var currentSolutionChecksum = await incrementalSolutionBuilt.State.GetChecksumAsync(CancellationToken.None).ConfigureAwait(false); + var currentSolutionChecksum = await incrementalSolutionBuilt.CompilationState.GetChecksumAsync(CancellationToken.None).ConfigureAwait(false); if (checksumFromRequest == currentSolutionChecksum) { return; diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs index b480f2ff7553e..b4bda851d777b 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs @@ -64,7 +64,7 @@ public async Task UpdatePrimaryBranchSolutionAsync( { // See if the current snapshot we're pointing at is the same one the host wants us to sync to. If so, we // don't need to do anything. - var currentSolutionChecksum = await this.CurrentSolution.State.GetChecksumAsync(cancellationToken).ConfigureAwait(false); + var currentSolutionChecksum = await this.CurrentSolution.CompilationState.GetChecksumAsync(cancellationToken).ConfigureAwait(false); if (currentSolutionChecksum == solutionChecksum) return; diff --git a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetCache.cs b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetCache.cs index 9cf5b9b5a73b5..f41418215977f 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetCache.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetCache.cs @@ -217,7 +217,7 @@ private async ValueTask AddPinnedChecksumsAsync(HashSet pinnedChecksum if (_remoteWorkspace is null) return; - var checksums = await _remoteWorkspace.CurrentSolution.State.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); + var checksums = await _remoteWorkspace.CurrentSolution.CompilationState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); checksums.AddAllTo(pinnedChecksums); } diff --git a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs index 81d1790e81c94..75e5237eea040 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/TestUtils.cs @@ -192,16 +192,16 @@ public static async Task AppendAssetMapAsync( { if (projectId == null) { - var solutionChecksums = await solution.State.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); - await solutionChecksums.FindAsync(solution.State, assetHint: AssetHint.None, Flatten(solutionChecksums), map, cancellationToken).ConfigureAwait(false); + var solutionChecksums = await solution.CompilationState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); + await solutionChecksums.FindAsync(solution.CompilationState, assetHint: AssetHint.None, Flatten(solutionChecksums), map, cancellationToken).ConfigureAwait(false); foreach (var project in solution.Projects) await project.AppendAssetMapAsync(map, cancellationToken).ConfigureAwait(false); } else { - var solutionChecksums = await solution.State.GetStateChecksumsAsync(projectId, cancellationToken).ConfigureAwait(false); - await solutionChecksums.FindAsync(solution.State, assetHint: projectId, Flatten(solutionChecksums), map, cancellationToken).ConfigureAwait(false); + var solutionChecksums = await solution.CompilationState.GetStateChecksumsAsync(projectId, cancellationToken).ConfigureAwait(false); + await solutionChecksums.FindAsync(solution.CompilationState, assetHint: projectId, Flatten(solutionChecksums), map, cancellationToken).ConfigureAwait(false); var project = solution.GetRequiredProject(projectId); await project.AppendAssetMapAsync(map, cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs b/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs index 5b877ef4e8060..05b74c0a06f1d 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SourceGeneration/RemoteSourceGenerationService.cs @@ -27,7 +27,7 @@ protected override IRemoteSourceGenerationService CreateService(in ServiceConstr return RunServiceAsync(solutionChecksum, async solution => { var project = solution.GetRequiredProject(projectId); - var documentStates = await solution.State.GetSourceGeneratedDocumentStatesAsync(project.State, cancellationToken).ConfigureAwait(false); + var documentStates = await solution.CompilationState.GetSourceGeneratedDocumentStatesAsync(project.State, cancellationToken).ConfigureAwait(false); using var _ = ArrayBuilder<(SourceGeneratedDocumentIdentity documentIdentity, SourceGeneratedDocumentContentIdentity contentIdentity)>.GetInstance(documentStates.Ids.Count, out var result); @@ -47,7 +47,7 @@ public ValueTask> GetContentsAsync( return RunServiceAsync(solutionChecksum, async solution => { var project = solution.GetRequiredProject(projectId); - var documentStates = await solution.State.GetSourceGeneratedDocumentStatesAsync(project.State, cancellationToken).ConfigureAwait(false); + var documentStates = await solution.CompilationState.GetSourceGeneratedDocumentStatesAsync(project.State, cancellationToken).ConfigureAwait(false); using var _ = ArrayBuilder.GetInstance(documentIds.Length, out var result);