diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs index 190e3494827f5..614c5cf70d152 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentState.cs @@ -387,21 +387,15 @@ public DocumentState UpdateSourceCodeKind(SourceCodeKind kind) return UpdateParseOptionsAndSourceCodeKind(ParseOptions.WithKind(kind), onlyPreprocessorDirectiveChange: false); } - return UpdateAttributes(Attributes.With(sourceCodeKind: kind)); + return WithAttributes(Attributes.With(sourceCodeKind: kind)); } - public DocumentState UpdateName(string name) - => UpdateAttributes(Attributes.With(name: name)); - - public DocumentState UpdateFilePath(string? path) - => UpdateAttributes(Attributes.With(filePath: path)); - - public DocumentState UpdateFolders(IReadOnlyList folders) - => UpdateAttributes(Attributes.With(folders: folders)); - - private DocumentState UpdateAttributes(DocumentInfo.DocumentAttributes newAttributes) + public DocumentState WithAttributes(DocumentInfo.DocumentAttributes newAttributes) { - Debug.Assert(newAttributes != Attributes); + if (ReferenceEquals(newAttributes, Attributes)) + { + return this; + } ITreeAndVersionSource? newTreeSource; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs index 438640169d706..281ab04903100 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs @@ -1150,7 +1150,10 @@ public Solution WithDocumentName(DocumentId documentId, string name) throw new ArgumentNullException(nameof(name)); } - return WithCompilationState(_compilationState.WithDocumentName(documentId, name)); + return WithCompilationState(_compilationState.WithDocumentAttributes( + documentId, + name, + static (attributes, value) => attributes.With(name: value))); } /// @@ -1163,7 +1166,10 @@ public Solution WithDocumentFolders(DocumentId documentId, IEnumerable? var collection = PublicContract.ToBoxedImmutableArrayWithNonNullItems(folders, nameof(folders)); - return WithCompilationState(_compilationState.WithDocumentFolders(documentId, collection)); + return WithCompilationState(_compilationState.WithDocumentAttributes( + documentId, + collection, + static (attributes, value) => attributes.With(folders: value))); } /// @@ -1172,7 +1178,11 @@ public Solution WithDocumentFolders(DocumentId documentId, IEnumerable? public Solution WithDocumentFilePath(DocumentId documentId, string? filePath) { CheckContainsDocument(documentId); - return WithCompilationState(_compilationState.WithDocumentFilePath(documentId, filePath)); + + return WithCompilationState(_compilationState.WithDocumentAttributes( + documentId, + filePath, + static (attributes, value) => attributes.With(filePath: value))); } /// diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs index 40a180c4c8e48..6e8de06b8085d 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs @@ -688,72 +688,60 @@ public SolutionCompilationState WithProjectAnalyzerReferences( forkTracker: true); } - /// - public SolutionCompilationState WithDocumentName( - DocumentId documentId, string name) - { - return UpdateDocumentState( - this.SolutionState.WithDocumentName(documentId, name), documentId); - } - - /// - public SolutionCompilationState WithDocumentFolders( - DocumentId documentId, IReadOnlyList folders) - { - return UpdateDocumentState( - this.SolutionState.WithDocumentFolders(documentId, folders), documentId); - } - - /// - public SolutionCompilationState WithDocumentFilePath( - DocumentId documentId, string? filePath) + /// + public SolutionCompilationState WithDocumentAttributes( + DocumentId documentId, + TArg arg, + Func updateAttributes) { return UpdateDocumentState( - this.SolutionState.WithDocumentFilePath(documentId, filePath), documentId); + SolutionState.WithDocumentAttributes(documentId, arg, updateAttributes), documentId); } internal SolutionCompilationState WithDocumentTexts(ImmutableArray<(DocumentId documentId, SourceText text)> texts, PreservationMode mode) - => WithDocumentContents( - texts, SourceTextIsUnchanged, - static (documentState, text, mode) => documentState.UpdateText(text, mode), - data: mode); + => UpdateDocumentsInMultipleProjects( + texts, + arg: mode, + updateDocument: static (oldDocumentState, text, mode) => + SourceTextIsUnchanged(oldDocumentState, text) ? oldDocumentState : oldDocumentState.UpdateText(text, mode)); - private static bool SourceTextIsUnchanged(DocumentState oldDocument, SourceText text, PreservationMode mode) + private static bool SourceTextIsUnchanged(DocumentState oldDocument, SourceText text) => oldDocument.TryGetText(out var oldText) && text == oldText; - private SolutionCompilationState WithDocumentContents( - ImmutableArray<(DocumentId documentId, TContent content)> texts, - Func isUnchanged, - Func updateContent, - TData data) + private SolutionCompilationState UpdateDocumentsInMultipleProjects( + ImmutableArray<(DocumentId documentId, TDocumentData documentData)> updates, + TArg arg, + Func updateDocument) { return UpdateDocumentsInMultipleProjects( - texts.GroupBy(d => d.documentId.ProjectId).Select(g => - { - var projectId = g.Key; - var projectState = this.SolutionState.GetRequiredProjectState(projectId); - - using var _ = ArrayBuilder.GetInstance(out var newDocumentStates); - foreach (var (documentId, content) in g) - { - var documentState = projectState.DocumentStates.GetRequiredState(documentId); - if (isUnchanged(documentState, content, data)) - continue; - - newDocumentStates.Add(updateContent(documentState, content, data)); - } - - return (projectId, newDocumentStates.ToImmutableAndClear()); - }), - static (projectState, newDocumentStates) => - { - return new TranslationAction.TouchDocumentsAction( - projectState, - projectState.UpdateDocuments(newDocumentStates, contentChanged: true), - newDocumentStates); - }); + updates.GroupBy(static d => d.documentId.ProjectId).Select(g => + { + var projectId = g.Key; + var projectState = SolutionState.GetRequiredProjectState(projectId); + + using var _ = ArrayBuilder.GetInstance(out var newDocumentStates); + foreach (var (documentId, documentData) in g) + { + var documentState = projectState.DocumentStates.GetRequiredState(documentId); + var newDocumentState = updateDocument(documentState, documentData, arg); + + if (ReferenceEquals(documentState, newDocumentState)) + continue; + + newDocumentStates.Add(newDocumentState); + } + + return (projectId, newDocumentStates.ToImmutableAndClear()); + }), + GetUpdateDocumentsTranslationAction); } + private static TranslationAction GetUpdateDocumentsTranslationAction(ProjectState projectState, ImmutableArray newDocumentStates) + => new TranslationAction.TouchDocumentsAction( + projectState, + projectState.UpdateDocuments(newDocumentStates, contentChanged: true), + newDocumentStates); + public SolutionCompilationState WithDocumentState( DocumentState documentState) { @@ -807,32 +795,25 @@ public SolutionCompilationState WithAnalyzerConfigDocumentText( /// public SolutionCompilationState WithDocumentSyntaxRoots(ImmutableArray<(DocumentId documentId, SyntaxNode root)> syntaxRoots, PreservationMode mode) { - return WithDocumentContents( - syntaxRoots, IsUnchanged, - static (documentState, root, mode) => documentState.UpdateTree(root, mode), - data: mode); - - static bool IsUnchanged(DocumentState oldDocument, SyntaxNode root, PreservationMode _) - { - return oldDocument.TryGetSyntaxTree(out var oldTree) && - oldTree.TryGetRoot(out var oldRoot) && - oldRoot == root; - } + return UpdateDocumentsInMultipleProjects( + syntaxRoots, + arg: mode, + static (oldDocumentState, root, mode) => + oldDocumentState.TryGetSyntaxTree(out var oldTree) && oldTree.TryGetRoot(out var oldRoot) && oldRoot == root + ? oldDocumentState + : oldDocumentState.UpdateTree(root, mode)); } public SolutionCompilationState WithDocumentContentsFrom( ImmutableArray<(DocumentId documentId, DocumentState documentState)> documentIdsAndStates, bool forceEvenIfTreesWouldDiffer) { - return WithDocumentContents( + return UpdateDocumentsInMultipleProjects( documentIdsAndStates, - isUnchanged: static (oldDocumentState, documentState, forceEvenIfTreesWouldDiffer) => - { - return oldDocumentState.TextAndVersionSource == documentState.TextAndVersionSource - && oldDocumentState.TreeSource == documentState.TreeSource; - }, + arg: forceEvenIfTreesWouldDiffer, static (oldDocumentState, documentState, forceEvenIfTreesWouldDiffer) => - oldDocumentState.UpdateTextAndTreeContents(documentState.TextAndVersionSource, documentState.TreeSource, forceEvenIfTreesWouldDiffer), - data: forceEvenIfTreesWouldDiffer); + oldDocumentState.TextAndVersionSource == documentState.TextAndVersionSource && oldDocumentState.TreeSource == documentState.TreeSource + ? oldDocumentState + : oldDocumentState.UpdateTextAndTreeContents(documentState.TextAndVersionSource, documentState.TreeSource, forceEvenIfTreesWouldDiffer)); } /// @@ -1696,7 +1677,7 @@ public SolutionCompilationState WithDocumentText(IEnumerable docume // the same text (for example, when GetOpenDocumentInCurrentContextWithChanges) is called. // // The use of GetRequiredState mirrors what happens in WithDocumentTexts - if (!SourceTextIsUnchanged(documentState, text, mode)) + if (!SourceTextIsUnchanged(documentState, text)) changedDocuments.Add((documentId, text)); } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs index cc386042c1c14..e87df89f8e4f1 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs @@ -944,49 +944,23 @@ public SolutionState WithFallbackAnalyzerOptions(ImmutableDictionary - /// Creates a new solution instance with the document specified updated to have the specified name. + /// Creates a new solution instance with an attribute of the document updated, if its value has changed. /// - public StateChange WithDocumentName(DocumentId documentId, string name) + public StateChange WithDocumentAttributes( + DocumentId documentId, + TArg arg, + Func updateAttributes) { var oldDocument = GetRequiredDocumentState(documentId); - if (oldDocument.Attributes.Name == name) - { - var oldProject = GetRequiredProjectState(documentId.ProjectId); - return new(this, oldProject, oldProject); - } - - return UpdateDocumentState(oldDocument.UpdateName(name), contentChanged: false); - } - /// - /// Creates a new solution instance with the document specified updated to be contained in - /// the sequence of logical folders. - /// - public StateChange WithDocumentFolders(DocumentId documentId, IReadOnlyList folders) - { - var oldDocument = GetRequiredDocumentState(documentId); - if (oldDocument.Folders.SequenceEqual(folders)) - { - var oldProject = GetRequiredProjectState(documentId.ProjectId); - return new(this, oldProject, oldProject); - } - - return UpdateDocumentState(oldDocument.UpdateFolders(folders), contentChanged: false); - } - - /// - /// Creates a new solution instance with the document specified updated to have the specified file path. - /// - public StateChange WithDocumentFilePath(DocumentId documentId, string? filePath) - { - var oldDocument = GetRequiredDocumentState(documentId); - if (oldDocument.FilePath == filePath) + var newDocument = oldDocument.WithAttributes(updateAttributes(oldDocument.Attributes, arg)); + if (ReferenceEquals(oldDocument, newDocument)) { var oldProject = GetRequiredProjectState(documentId.ProjectId); return new(this, oldProject, oldProject); } - return UpdateDocumentState(oldDocument.UpdateFilePath(filePath), contentChanged: false); + return UpdateDocumentState(newDocument, contentChanged: false); } ///