diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.CompilationTrackerState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.CompilationTrackerState.cs index bc455b1baf4c2..290f4573ca9ec 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.CompilationTrackerState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.CompilationTrackerState.cs @@ -91,7 +91,7 @@ private InProgressState( Compilation compilationWithoutGeneratedDocuments, CompilationTrackerGeneratorInfo generatorInfo, Compilation? staleCompilationWithGeneratedDocuments, - ImmutableList<(ProjectState state, CompilationAndGeneratorDriverTranslationAction action)> pendingTranslationSteps) + ImmutableList<(ProjectState oldState, CompilationAndGeneratorDriverTranslationAction action)> pendingTranslationSteps) : base(isFrozen, compilationWithoutGeneratedDocuments, generatorInfo) @@ -108,7 +108,7 @@ public static InProgressState Create( Compilation compilationWithoutGeneratedDocuments, CompilationTrackerGeneratorInfo generatorInfo, Compilation? staleCompilationWithGeneratedDocuments, - ImmutableList<(ProjectState state, CompilationAndGeneratorDriverTranslationAction action)> pendingTranslationSteps) + ImmutableList<(ProjectState oldState, CompilationAndGeneratorDriverTranslationAction action)> pendingTranslationSteps) { Contract.ThrowIfTrue(pendingTranslationSteps is null); @@ -166,7 +166,10 @@ private FinalCompilationTrackerState( : base(isFrozen, compilationWithoutGeneratedDocuments, generatorInfo) { Contract.ThrowIfNull(finalCompilationWithGeneratedDocuments); - HasSuccessfullyLoaded = hasSuccessfullyLoaded; + + // As a policy, all partial-state projects are said to have incomplete references, since the + // state has no guarantees. + HasSuccessfullyLoaded = hasSuccessfullyLoaded && !isFrozen; FinalCompilationWithGeneratedDocuments = finalCompilationWithGeneratedDocuments; UnrootedSymbolSet = unrootedSymbolSet; @@ -205,6 +208,14 @@ public static FinalCompilationTrackerState Create( unrootedSymbolSet); } + public FinalCompilationTrackerState WithIsFrozen() + => new(isFrozen: true, + FinalCompilationWithGeneratedDocuments, + CompilationWithoutGeneratedDocuments, + HasSuccessfullyLoaded, + GeneratorInfo, + UnrootedSymbolSet); + private static void RecordAssemblySymbols(ProjectId projectId, Compilation compilation, Dictionary? metadataReferenceToProjectId) { RecordSourceOfAssemblySymbol(compilation.Assembly, projectId); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs index f1ea24896201c..a245adfbe1d7b 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.CompilationTracker.cs @@ -187,123 +187,130 @@ public ICompilationTracker Fork( } } - public ICompilationTracker FreezePartialState( - SolutionCompilationState compilationState, - CancellationToken cancellationToken) + public ICompilationTracker FreezePartialState(CancellationToken cancellationToken) { - return FreezePartialStateWorker( - compilationState, docState: null, tree: null, cancellationToken); - } + GetPartialCompilationState( + documentId: null, + out var state, + out var inProgressProject, + out var compilationPair, + out var generatorInfo, + cancellationToken); - public ICompilationTracker FreezePartialStateWithTree( - SolutionCompilationState compilationState, - DocumentState docState, - SyntaxTree tree, - CancellationToken cancellationToken) - { - return FreezePartialStateWorker( - compilationState, docState, tree, cancellationToken); + // If we're already finalized then just return what we have, and with the frozen bit flipped so that + // any future forks keep things frozen. + if (state is FinalCompilationTrackerState finalState) + { + var frozenFinalState = finalState.WithIsFrozen(); + return new CompilationTracker(this.ProjectState, frozenFinalState, this.SkeletonReferenceCache.Clone()); + } + else + { + // Transition us to a frozen in progress state. With the best compilations up to this point, but no + // more pending actions, and with the frozen bit flipped so that any future forks keep things + // frozen. + var inProgressState = InProgressState.Create( + isFrozen: true, + compilationPair.CompilationWithoutGeneratedDocuments, + generatorInfo, + compilationPair.CompilationWithGeneratedDocuments, + ImmutableList<(ProjectState, CompilationAndGeneratorDriverTranslationAction)>.Empty); + + return new CompilationTracker(inProgressProject, inProgressState, this.SkeletonReferenceCache.Clone()); + } } - private ICompilationTracker FreezePartialStateWorker( - SolutionCompilationState compilationState, - DocumentState? docState, - SyntaxTree? tree, + public ICompilationTracker FreezePartialStateWithDocument( + DocumentState docState, CancellationToken cancellationToken) { GetPartialCompilationState( - compilationState, - docState?.Id, + docState.Id, + out var state, out var inProgressProject, out var compilationPair, out var generatorInfo, - out var metadataReferenceToProjectId, cancellationToken); - if (docState == null) + // If we're already finalized, and no change has been made to this document. Then just return what we + // have (just with the frozen bit flipped so that any future forks keep things frozen). + if (state is FinalCompilationTrackerState finalState && + this.ProjectState.DocumentStates.TryGetState(docState.Id, out var oldState) && + oldState == docState) { - Contract.ThrowIfTrue(tree != null); + var frozenFinalState = finalState.WithIsFrozen(); + return new CompilationTracker(this.ProjectState, frozenFinalState, this.SkeletonReferenceCache.Clone()); } else { - Contract.ThrowIfNull(tree); - - // Ensure we actually have the tree we need in there; note that if the tree is present, then we know the document must also be - // present in inProgressProject, since those are both updated in parallel. - // - // the tree that we have been given was directly returned from the document state that we're also being passed -- - // the only reason we're requesting it earlier is this code is running under a lock in - // SolutionState.WithFrozenPartialCompilationIncludingSpecificDocument. - if (!compilationPair.CompilationWithoutGeneratedDocuments.ContainsSyntaxTree(tree)) + // Otherwise, we're not finalized, or the document has changed. We'll create an in-progress state + // to represent the new state of the project, with all the necessary translation steps to + // incorporate the new document. + var pendingActions = ImmutableList<(ProjectState, CompilationAndGeneratorDriverTranslationAction)>.Empty; + if (!inProgressProject.DocumentStates.TryGetState(docState.Id, out oldState) || oldState != docState) { - // We do not have the exact tree. It either means this document was recently added, or the tree was recently changed. - // We now need to update both the inProgressState and the compilation. There are several possibilities we want to consider: + // We do not have the exact document. It either means this document was recently added, or the + // document was recently changed. We now need to update both the inProgressState and the + // compilation. There are several possibilities we want to consider: // // 1. An earlier version of the document is present in the compilation, and we just need to update it to the current version - // 2. The tree wasn't present in the original snapshot at all, and we just need to add the tree. - // 3. The tree wasn't present in the original snapshot, but an older file had been removed that had the same file path. - // As a heuristic, we remove the old one so we don't end up with duplicate trees. + // 2. The document wasn't present in the original snapshot at all, and we just need to add the + // document. + // 3. The document wasn't present in the original snapshot, but an older file had been removed that + // had the same file path. As a heuristic, we remove the old one so we don't end up with + // duplicate documents. // - // Note it's possible that we simply had never tried to produce a compilation yet for this project at all, in that case - // GetPartialCompilationState would have produced an empty compilation, and it would have updated inProgressProject to - // remove all the documents. Thus, that is no different than the "add" case above. - if (inProgressProject.DocumentStates.TryGetState(docState.Id, out var oldState)) + // Note it's possible that we simply had never tried to produce a compilation yet for this project + // at all, in that case GetPartialCompilationState would have produced an empty compilation, and it + // would have updated inProgressProject to remove all the documents. Thus, that is no different than + // the "add" case above. + if (oldState != null) { - // Scenario 1. The document had been previously parsed and it's there, so we can update it with our current state - // This call should be instant, since the compilation already must exist that contains this tree. Note if no compilation existed - // GetPartialCompilationState would have produced an empty one, and removed any documents, so inProgressProject.DocumentStates would + // Scenario 1. The document had been previously parsed and it's there, so we can update it with + // our current state. Note if no compilation existed GetPartialCompilationState would have + // produced an empty one, and removed any documents, so inProgressProject.DocumentStates would // have been empty originally. - var oldTree = oldState.GetSyntaxTree(cancellationToken); - - compilationPair = compilationPair.ReplaceSyntaxTree(oldTree, tree); + pendingActions = [(inProgressProject, new CompilationAndGeneratorDriverTranslationAction.TouchDocumentAction(oldState, docState))]; inProgressProject = inProgressProject.UpdateDocument(docState, contentChanged: true); } else { - // We're in either scenario 2 or 3. Do we have an existing tree to try replacing? Note: the file path here corresponds to Document.FilePath. - // If a document's file path is null, we then substitute Document.Name, so we usually expect there to be a unique string regardless. - var oldTree = compilationPair.CompilationWithoutGeneratedDocuments.SyntaxTrees.FirstOrDefault(t => t.FilePath == tree.FilePath); - if (oldTree == null) + // We're in either scenario 2 or 3. Do we have an existing document to try replacing? Note: the + // file path here corresponds to Document.FilePath. If a document's file path is null, we then + // substitute Document.Name, so we usually expect there to be a unique string regardless. + oldState = inProgressProject.DocumentStates.States.FirstOrDefault(kvp => kvp.Value.FilePath == docState.FilePath).Value; + if (oldState is null) { // Scenario 2. - compilationPair = compilationPair.AddSyntaxTree(tree); + pendingActions = [(inProgressProject, new CompilationAndGeneratorDriverTranslationAction.AddDocumentsAction([docState]))]; inProgressProject = inProgressProject.AddDocuments([docState]); } else { - // Scenario 3. - compilationPair = compilationPair.ReplaceSyntaxTree(oldTree, tree); - - // The old tree came from some other document with a different ID then we started with -- if the document ID still existed we would have - // been in the Scenario 1 case instead. We'll find the old document ID, remove that state, and then add ours. - var oldDocumentId = DocumentState.GetDocumentIdForTree(oldTree); - Contract.ThrowIfNull(oldDocumentId, $"{nameof(oldTree)} came from the compilation produced by the workspace, so the document ID should have existed."); - inProgressProject = inProgressProject - .RemoveDocuments([oldDocumentId]) + // Scenario 3. The old doc came from some other document with a different ID then we started + // with -- if the document ID still existed we would have been in the Scenario 1 case + // instead. We'll find the old document ID, remove that state, and then add ours. + var inProgressWithDocumentRemoved = inProgressProject.RemoveDocuments([oldState.Id]); + pendingActions = [ + (inProgressProject, new CompilationAndGeneratorDriverTranslationAction.RemoveDocumentsAction([oldState])), + (inProgressWithDocumentRemoved, new CompilationAndGeneratorDriverTranslationAction.AddDocumentsAction([docState]))]; + inProgressProject = inProgressWithDocumentRemoved .AddDocuments([docState]); } } } - // At this point, we now absolutely should have our tree in the compilation - Contract.ThrowIfFalse(compilationPair.CompilationWithoutGeneratedDocuments.ContainsSyntaxTree(tree)); - } + // Transition us to a frozen in progress state. With the best compilations up to this point, and the + // pending actions we'll need to perform on it to get to the final state. + var inProgressState = InProgressState.Create( + isFrozen: true, + compilationPair.CompilationWithoutGeneratedDocuments, + generatorInfo, + compilationPair.CompilationWithGeneratedDocuments, + pendingActions); - // Mark our FinalState as frozen. We'll want to keep whatever we have here through whatever future - // transformations occur. - - var finalState = FinalCompilationTrackerState.Create( - isFrozen: true, - finalCompilationWithGeneratedDocuments: compilationPair.CompilationWithGeneratedDocuments, - compilationWithoutGeneratedDocuments: compilationPair.CompilationWithoutGeneratedDocuments, - // As a policy, all partial-state projects are said to have incomplete references, since the state - // has no guarantees. - hasSuccessfullyLoaded: false, - generatorInfo, - this.ProjectState.Id, - metadataReferenceToProjectId); - - return new CompilationTracker(inProgressProject, finalState, this.SkeletonReferenceCache.Clone()); + return new CompilationTracker(inProgressProject, inProgressState, this.SkeletonReferenceCache.Clone()); + } } /// @@ -314,15 +321,14 @@ private ICompilationTracker FreezePartialStateWorker( /// returned will have a compilation that is retained so that it cannot disappear. /// private void GetPartialCompilationState( - SolutionCompilationState compilationState, DocumentId? documentId, + out CompilationTrackerState? state, out ProjectState inProgressProject, out CompilationPair compilations, out CompilationTrackerGeneratorInfo generatorInfo, - out Dictionary? metadataReferenceToProjectId, CancellationToken cancellationToken) { - var state = ReadState(); + state = ReadState(); var compilationWithoutGeneratedDocuments = state?.CompilationWithoutGeneratedDocuments; generatorInfo = state?.GeneratorInfo ?? CompilationTrackerGeneratorInfo.Empty; @@ -348,9 +354,6 @@ private void GetPartialCompilationState( compilationWithoutGeneratedDocuments, compilationWithoutGeneratedDocuments.AddSyntaxTrees(generatorInfo.Documents.States.Values.Select(state => state.GetSyntaxTree(cancellationToken)))); - // This is likely a bug. It seems possible to pass out a partial compilation state that we don't - // properly record assembly symbols for. - metadataReferenceToProjectId = null; return; } @@ -361,12 +364,6 @@ private void GetPartialCompilationState( Contract.ThrowIfNull(finalCompilation, "We have a FinalState, so we must have a non-null final compilation"); compilations = new CompilationPair(compilationWithoutGeneratedDocuments, finalCompilation); - - // This should hopefully be safe to return as null. Because we already reached the 'FinalState' - // before, we should have already recorded the assembly symbols for it. So not recording them - // again is likely ok (as long as compilations continue to return the same IAssemblySymbols for - // the same references across source edits). - metadataReferenceToProjectId = null; return; } @@ -382,61 +379,6 @@ private void GetPartialCompilationState( compilations = new CompilationPair( compilationWithoutGeneratedDocuments, compilationWithoutGeneratedDocuments.AddSyntaxTrees(generatorInfo.Documents.States.Values.Select(state => state.GetSyntaxTree(cancellationToken)))); - - // Now add in back a consistent set of project references. For project references - // try to get either a CompilationReference or a SkeletonReference. This ensures - // that the in-progress project only reports a reference to another project if it - // could actually get a reference to that project's metadata. - var metadataReferences = new List(); - var newProjectReferences = new List(); - metadataReferences.AddRange(this.ProjectState.MetadataReferences); - - metadataReferenceToProjectId = []; - - foreach (var projectReference in this.ProjectState.ProjectReferences) - { - var referencedProject = compilationState.SolutionState.GetProjectState(projectReference.ProjectId); - if (referencedProject != null) - { - if (referencedProject.IsSubmission) - { - var previousScriptCompilation = compilationState.GetCompilationAsync( - projectReference.ProjectId, cancellationToken).WaitAndGetResult(cancellationToken); - - // previous submission project must support compilation: - RoslynDebug.Assert(previousScriptCompilation != null); - - compilations = compilations.WithPreviousScriptCompilation(previousScriptCompilation); - } - else - { - // get the latest metadata for the partial compilation of the referenced project. - 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 => SolutionCompilationState.GetProjectId(inProgressCompilationNotRef.GetAssemblyOrModuleSymbol(r) as IAssemblySymbol) == projectReference.ProjectId); - } - - if (metadata != null) - { - newProjectReferences.Add(projectReference); - metadataReferences.Add(metadata); - metadataReferenceToProjectId.Add(metadata, projectReference.ProjectId); - } - } - } - } - - inProgressProject = inProgressProject.WithProjectReferences(newProjectReferences); - - if (!Enumerable.SequenceEqual(compilations.CompilationWithoutGeneratedDocuments.ExternalReferences, metadataReferences)) - { - compilations = compilations.WithReferences(metadataReferences); - } } private static bool IsTouchDocumentActionForDocument(CompilationAndGeneratorDriverTranslationAction action, DocumentId id) @@ -730,18 +672,28 @@ await compilationState.GetCompilationAsync( } else { - var metadataReference = await compilationState.GetMetadataReferenceAsync( - projectReference, this.ProjectState, cancellationToken).ConfigureAwait(false); + var metadataReference = isFrozen + ? compilationState.GetPartialMetadataReference(projectReference, this.ProjectState) + : await compilationState.GetMetadataReferenceAsync(projectReference, this.ProjectState, cancellationToken).ConfigureAwait(false); + + if (metadataReference is null && isFrozen) + { + // if we failed to get the metadata and we were frozen, check to see if we + // previously had existing metadata and reuse it instead. + var inProgressCompilationNotRef = staleCompilationWithGeneratedDocuments ?? compilationWithoutGeneratedDocuments; + metadataReference = inProgressCompilationNotRef.ExternalReferences.FirstOrDefault( + r => GetProjectId(inProgressCompilationNotRef.GetAssemblyOrModuleSymbol(r) as IAssemblySymbol) == projectReference.ProjectId); + } // A reference can fail to be created if a skeleton assembly could not be constructed. - if (metadataReference != null) + if (metadataReference == null) { - newReferences.Add(metadataReference); - metadataReferenceToProjectId.Add(metadataReference, projectReference.ProjectId); + hasSuccessfullyLoaded = false; } else { - hasSuccessfullyLoaded = false; + newReferences.Add(metadataReference); + metadataReferenceToProjectId.Add(metadataReference, projectReference.ProjectId); } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs index 22368b6dddbf6..bbb9c98184d4f 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.GeneratedFileReplacingCompilationTracker.cs @@ -57,13 +57,13 @@ public ICompilationTracker Fork(ProjectState newProject, CompilationAndGenerator throw new NotImplementedException(); } - public ICompilationTracker FreezePartialState(SolutionCompilationState compilationState, CancellationToken cancellationToken) + public ICompilationTracker FreezePartialState(CancellationToken cancellationToken) { // Ensure the underlying tracker is totally frozen, and then ensure our replaced generated doc is present. - return new GeneratedFileReplacingCompilationTracker(UnderlyingTracker.FreezePartialState(compilationState, cancellationToken), replacementDocumentState); + return new GeneratedFileReplacingCompilationTracker(UnderlyingTracker.FreezePartialState(cancellationToken), replacementDocumentState); } - public ICompilationTracker FreezePartialStateWithTree(SolutionCompilationState compilationState, DocumentState docState, SyntaxTree tree, CancellationToken cancellationToken) + public ICompilationTracker FreezePartialStateWithDocument(DocumentState docState, CancellationToken cancellationToken) { // Because we override SourceGeneratedDocument.WithFrozenPartialSemantics directly, we shouldn't be able to get here. throw ExceptionUtilities.Unreachable(); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.ICompilationTracker.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.ICompilationTracker.cs index 3a2b99991e7c4..4467f075f719d 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.ICompilationTracker.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.ICompilationTracker.cs @@ -35,10 +35,11 @@ private interface ICompilationTracker /// bool ContainsAssemblyOrModuleOrDynamic(ISymbol symbol, bool primary); ICompilationTracker Fork(ProjectState newProject, CompilationAndGeneratorDriverTranslationAction? translate); + Task GetCompilationAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken); - ICompilationTracker FreezePartialState(SolutionCompilationState compilationState, CancellationToken cancellationToken); - ICompilationTracker FreezePartialStateWithTree(SolutionCompilationState compilationState, DocumentState docState, SyntaxTree tree, CancellationToken cancellationToken); + ICompilationTracker FreezePartialState(CancellationToken cancellationToken); + ICompilationTracker FreezePartialStateWithDocument(DocumentState docState, CancellationToken cancellationToken); Task GetDependentVersionAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken); Task GetDependentSemanticVersionAsync(SolutionCompilationState compilationState, CancellationToken cancellationToken); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs index a3e5fe1e9c273..86710f5a8bb41 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs @@ -1025,7 +1025,7 @@ private SolutionCompilationState ComputeFrozenSnapshot(CancellationToken cancell { // if we don't have one or it is stale, create a new partial solution var tracker = GetCompilationTracker(projectId); - var newTracker = tracker.FreezePartialState(this, cancellationToken); + var newTracker = tracker.FreezePartialState(cancellationToken); Contract.ThrowIfFalse(newIdToProjectStateMapBuilder.ContainsKey(projectId)); newIdToProjectStateMapBuilder[projectId] = newTracker.ProjectState; @@ -1110,7 +1110,7 @@ static SolutionCompilationState WithFrozenPartialCompilationIncludingSpecificDoc try { var allDocumentIds = @this.SolutionState.GetRelatedDocumentIds(documentId); - using var _ = ArrayBuilder<(DocumentState, SyntaxTree)>.GetInstance(allDocumentIds.Length, out var statesAndTrees); + using var _ = ArrayBuilder.GetInstance(allDocumentIds.Length, out var documentStates); // We grab all the contents of linked files as well to ensure that our snapshot is correct wrt to the // set of linked document ids our state says are in it. Note: all of these trees should share the same @@ -1119,14 +1119,14 @@ static SolutionCompilationState WithFrozenPartialCompilationIncludingSpecificDoc foreach (var currentDocumentId in allDocumentIds) { var documentState = @this.SolutionState.GetRequiredDocumentState(currentDocumentId); - statesAndTrees.Add((documentState, documentState.GetSyntaxTree(cancellationToken))); + documentStates.Add(documentState); } using (@this.StateLock.DisposableWait(cancellationToken)) { if (!@this._cachedFrozenDocumentState.TryGetValue(documentId, out var compilationState)) { - compilationState = ComputeFrozenPartialState(@this, statesAndTrees, cancellationToken); + compilationState = ComputeFrozenPartialState(@this, documentStates, cancellationToken); @this._cachedFrozenDocumentState.Add(documentId, compilationState); } @@ -1141,17 +1141,17 @@ static SolutionCompilationState WithFrozenPartialCompilationIncludingSpecificDoc static SolutionCompilationState ComputeFrozenPartialState( SolutionCompilationState @this, - ArrayBuilder<(DocumentState, SyntaxTree)> statesAndTrees, + ArrayBuilder documentStates, CancellationToken cancellationToken) { var newIdToProjectStateMap = @this.SolutionState.ProjectStates; var newIdToTrackerMap = @this._projectIdToTrackerMap; - foreach (var (docState, tree) in statesAndTrees) + foreach (var docState in documentStates) { // if we don't have one or it is stale, create a new partial solution var tracker = @this.GetCompilationTracker(docState.Id.ProjectId); - var newTracker = tracker.FreezePartialStateWithTree(@this, docState, tree, cancellationToken); + var newTracker = tracker.FreezePartialStateWithDocument(docState, cancellationToken); Contract.ThrowIfFalse(newIdToProjectStateMap.ContainsKey(docState.Id.ProjectId)); newIdToProjectStateMap = newIdToProjectStateMap.SetItem(docState.Id.ProjectId, newTracker.ProjectState); diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index 12ec0cbadebad..ac137da3b3083 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -3649,13 +3649,13 @@ public async Task TestFrozenPartialSemanticsHandlesRemoveAndAddWithNullPathAndDi project = project.RemoveDocument(project.DocumentIds.Single()) .AddDocument("RegularDocument2.cs", "// Source File", filePath: null).Project; - // Freeze semantics -- with the new document; this should still give us a project with two documents: the new - // one will be added, and the old one will stay around since the name differed. + // Freeze semantics -- with the new document; this should still give us a project with one document: just + // the newly added one. var frozenDocument = project.Documents.Single().WithFrozenPartialSemantics(CancellationToken.None); - Assert.Equal(2, frozenDocument.Project.Documents.Count()); + Assert.Equal(1, frozenDocument.Project.Documents.Count()); var treesInCompilation = (await frozenDocument.Project.GetCompilationAsync()).SyntaxTrees; - Assert.Equal(2, treesInCompilation.Count()); + Assert.Equal(1, treesInCompilation.Count()); foreach (var document in frozenDocument.Project.Documents) Assert.Contains(await document.GetRequiredSyntaxTreeAsync(CancellationToken.None), treesInCompilation);