From 2161fd2c2450c071fde573be4828c77a1dc7a0fe Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 8 May 2024 16:16:16 -0700 Subject: [PATCH 01/18] remove method and rename --- .../Solution/SolutionCompilationState.cs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs index 779dda124c3f..5d9f6b3dd496 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs @@ -1468,7 +1468,7 @@ static SolutionCompilationState ComputeFrozenPartialState( } // Now, add all missing documents per project. - currentState = currentState.AddDocumentsToMultipleProjects( + currentState = currentState.UpdateDocumentsInMultipleProjects( // Do a SelectAsArray here to ensure that we realize the array once, and as such only call things like // ToImmutableAndFree once per ArrayBuilder. missingDocumentStates.SelectAsArray(kvp => (kvp.Key, kvp.Value.ToImmutableAndFree())), @@ -1542,7 +1542,7 @@ private SolutionCompilationState AddDocumentsToMultipleProjects( // The documents might be contributing to multiple different projects; split them by project and then we'll // process one project at a time. - return AddDocumentsToMultipleProjects( + return UpdateDocumentsInMultipleProjects( documentInfos.GroupBy(d => d.Id.ProjectId).Select(g => { var projectId = g.Key; @@ -1553,17 +1553,9 @@ private SolutionCompilationState AddDocumentsToMultipleProjects( addDocumentsToProjectState); } - private SolutionCompilationState AddDocumentsToMultipleProjects( - IEnumerable<(ProjectId projectId, ImmutableArray newDocumentStates)> projectIdAndNewDocuments, - Func, TranslationAction> addDocumentsToProjectState) - where TDocumentState : TextDocumentState - { - return UpdateDocumentsInMultipleProjects(projectIdAndNewDocuments, addDocumentsToProjectState); - } - private SolutionCompilationState UpdateDocumentsInMultipleProjects( IEnumerable<(ProjectId projectId, ImmutableArray updatedDocumentState)> projectIdAndUpdatedDocuments, - Func, TranslationAction> updatedDocumentsToProjectState) + Func, TranslationAction> getTranslationAction) where TDocumentState : TextDocumentState { var newCompilationState = this; @@ -1571,7 +1563,7 @@ private SolutionCompilationState UpdateDocumentsInMultipleProjects Date: Wed, 8 May 2024 16:22:17 -0700 Subject: [PATCH 02/18] Bulk apply --- .../EnableNullableCodeRefactoringProvider.cs | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/Features/CSharp/Portable/CodeRefactorings/EnableNullable/EnableNullableCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/CodeRefactorings/EnableNullable/EnableNullableCodeRefactoringProvider.cs index ae26da1577dd..e19a2b6c70db 100644 --- a/src/Features/CSharp/Portable/CodeRefactorings/EnableNullable/EnableNullableCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/CodeRefactorings/EnableNullable/EnableNullableCodeRefactoringProvider.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; namespace Microsoft.CodeAnalysis.CSharp.CodeRefactorings.EnableNullable; @@ -67,14 +68,21 @@ private static async Task EnableNullableReferenceTypesAsync( Project project, CodeActionPurpose purpose, CodeActionOptionsProvider fallbackOptions, IProgress _, CancellationToken cancellationToken) { var solution = project.Solution; - foreach (var document in project.Documents) - { - if (await document.IsGeneratedCodeAsync(cancellationToken).ConfigureAwait(false)) - continue; + var updatedDocumentRoots = await ProducerConsumer<(DocumentId documentId, SyntaxNode newRoot, PreservationMode mode)>.RunParallelAsync( + source: project.Documents, + produceItems: static async (document, callback, fallbackOptions, cancellationToken) => + { + if (await document.IsGeneratedCodeAsync(cancellationToken).ConfigureAwait(false)) + return; - var updatedDocumentRoot = await EnableNullableReferenceTypesAsync(document, fallbackOptions, cancellationToken).ConfigureAwait(false); - solution = solution.WithDocumentSyntaxRoot(document.Id, updatedDocumentRoot); - } + var updatedDocumentRoot = await EnableNullableReferenceTypesAsync( + document, fallbackOptions, cancellationToken).ConfigureAwait(false); + callback((document.Id, updatedDocumentRoot, PreservationMode.PreserveValue)); + }, + args: fallbackOptions, + cancellationToken).ConfigureAwait(false); + + solution = solution.WithDocumentSyntaxRoots(updatedDocumentRoots); if (purpose is CodeActionPurpose.Apply) { From 385732f2a90933a3aba4b73275cb213a84ad7ffe Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 8 May 2024 16:41:47 -0700 Subject: [PATCH 03/18] use helper --- .../AbstractChangeNamespaceService.cs | 43 ++++++++++--------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs index b9dd43e57681..7825177bc042 100644 --- a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs +++ b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs @@ -487,30 +487,27 @@ private static SyntaxNode CreateImport(SyntaxGenerator syntaxGenerator, string n var refLocationGroups = refLocationsInSolution.GroupBy(loc => loc.Document.Id); - var fixedDocuments = await Task.WhenAll( - refLocationGroups.Select(refInOneDocument => - FixReferencingDocumentAsync( - solutionWithChangedNamespace.GetRequiredDocument(refInOneDocument.Key), - refInOneDocument, - newNamespace, - fallbackOptions, - cancellationToken))).ConfigureAwait(false); + var fixedDocuments = await Task.WhenAll(refLocationGroups.Select(async refInOneDocument => + { + var result = await FixReferencingDocumentAsync( + solutionWithChangedNamespace.GetRequiredDocument(refInOneDocument.Key), + refInOneDocument, + newNamespace, + fallbackOptions, + cancellationToken).ConfigureAwait(false); + return (result.Id, await result.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false)); + })).ConfigureAwait(false); - var solutionWithFixedReferences = await MergeDocumentChangesAsync(solutionWithChangedNamespace, fixedDocuments, cancellationToken).ConfigureAwait(false); + var solutionWithFixedReferences = MergeDocumentChanges(solutionWithChangedNamespace, fixedDocuments); return (solutionWithFixedReferences, refLocationGroups.SelectAsArray(g => g.Key)); } - private static async Task MergeDocumentChangesAsync(Solution originalSolution, Document[] changedDocuments, CancellationToken cancellationToken) + private static Solution MergeDocumentChanges( + Solution originalSolution, (DocumentId documentId, SyntaxNode newRoot)[] changedDocuments) { - foreach (var document in changedDocuments) - { - originalSolution = originalSolution.WithDocumentSyntaxRoot( - document.Id, - await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false)); - } - - return originalSolution; + return originalSolution.WithDocumentSyntaxRoots( + changedDocuments.SelectAsArray(t => (t.documentId, t.newRoot, PreservationMode.PreserveValue))); } private readonly struct LocationForAffectedSymbol(ReferenceLocation location, bool isReferenceToExtensionMethod) @@ -797,12 +794,16 @@ private static async Task RemoveUnnecessaryImportsAsync( var documentsToProcess = documentsToProcessBuilder.ToImmutableAndFree(); var changeDocuments = await Task.WhenAll(documentsToProcess.Select( - doc => RemoveUnnecessaryImportsWorkerAsync( + async doc => + { + var result = await RemoveUnnecessaryImportsWorkerAsync( doc, CreateImports(doc, names, withFormatterAnnotation: false), - cancellationToken))).ConfigureAwait(false); + cancellationToken).ConfigureAwait(false); + return (result.Id, await result.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false)); + })).ConfigureAwait(false); - return await MergeDocumentChangesAsync(solution, changeDocuments, cancellationToken).ConfigureAwait(false); + return MergeDocumentChanges(solution, changeDocuments); async Task RemoveUnnecessaryImportsWorkerAsync( Document doc, From 877cd52f0622d5e9bed4174220fbc1ab86415f7c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 8 May 2024 16:46:58 -0700 Subject: [PATCH 04/18] Share code --- .../Solution/SolutionCompilationState.cs | 52 +++++++------------ 1 file changed, 19 insertions(+), 33 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs index 5d9f6b3dd496..064de2969735 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs @@ -714,6 +714,19 @@ public SolutionCompilationState WithDocumentText(DocumentId documentId, SourceTe internal SolutionCompilationState WithDocumentTexts( ImmutableArray<(DocumentId documentId, SourceText text, PreservationMode mode)> texts) + { + return WithDocumentContents( + texts, IsUnchanged, + static (documentState, text, mode) => documentState.UpdateText(text, mode)); + + static bool IsUnchanged(DocumentState oldDocument, SourceText text) + => oldDocument.TryGetText(out var oldText) && text == oldText; + } + + private SolutionCompilationState WithDocumentContents( + ImmutableArray<(DocumentId documentId, TContent content, PreservationMode mode)> texts, + Func isUnchanged, + Func updateContent) { return UpdateDocumentsInMultipleProjects( texts.GroupBy(d => d.documentId.ProjectId).Select(g => @@ -722,13 +735,13 @@ internal SolutionCompilationState WithDocumentTexts( var projectState = this.SolutionState.GetRequiredProjectState(projectId); using var _ = ArrayBuilder.GetInstance(out var newDocumentStates); - foreach (var (documentId, text, mode) in g) + foreach (var (documentId, content, mode) in g) { var documentState = projectState.DocumentStates.GetRequiredState(documentId); - if (IsUnchanged(documentState, text)) + if (isUnchanged(documentState, content)) continue; - newDocumentStates.Add(documentState.UpdateText(text, mode)); + newDocumentStates.Add(updateContent(documentState, content, mode)); } return (projectId, newDocumentStates.ToImmutableAndClear()); @@ -740,11 +753,6 @@ internal SolutionCompilationState WithDocumentTexts( projectState.UpdateDocuments(newDocumentStates, contentChanged: true), newDocumentStates); }); - - static bool IsUnchanged(DocumentState oldDocument, SourceText text) - { - return oldDocument.TryGetText(out var oldText) && text == oldText; - } } public SolutionCompilationState WithDocumentState( @@ -796,31 +804,9 @@ public SolutionCompilationState WithAnalyzerConfigDocumentText( /// public SolutionCompilationState WithDocumentSyntaxRoots(ImmutableArray<(DocumentId documentId, SyntaxNode root, PreservationMode mode)> syntaxRoots) { - return UpdateDocumentsInMultipleProjects( - syntaxRoots.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, root, mode) in g) - { - var documentState = projectState.DocumentStates.GetRequiredState(documentId); - if (IsUnchanged(documentState, root)) - continue; - - newDocumentStates.Add(documentState.UpdateTree(root, mode)); - } - - return (projectId, newDocumentStates.ToImmutableAndClear()); - }), - static (projectState, newDocumentStates) => - { - return TranslationAction.TouchDocumentsAction.Create( - projectState, - projectState.UpdateDocuments(newDocumentStates, contentChanged: true), - newDocumentStates); - }); + return WithDocumentContents( + syntaxRoots, IsUnchanged, + static (documentState, root, mode) => documentState.UpdateTree(root, mode)); static bool IsUnchanged(DocumentState oldDocument, SyntaxNode root) { From 40e0a1ca787d5b7bc4d17916d1a999998fd2885e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 8 May 2024 16:58:34 -0700 Subject: [PATCH 05/18] use new helper --- .../AbstractChangeNamespaceService.cs | 70 ++++++++----------- 1 file changed, 31 insertions(+), 39 deletions(-) diff --git a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs index 7825177bc042..db4bceeeeafb 100644 --- a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs +++ b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs @@ -24,6 +24,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.RemoveUnnecessaryImports; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -487,29 +488,25 @@ private static SyntaxNode CreateImport(SyntaxGenerator syntaxGenerator, string n var refLocationGroups = refLocationsInSolution.GroupBy(loc => loc.Document.Id); - var fixedDocuments = await Task.WhenAll(refLocationGroups.Select(async refInOneDocument => - { - var result = await FixReferencingDocumentAsync( - solutionWithChangedNamespace.GetRequiredDocument(refInOneDocument.Key), - refInOneDocument, - newNamespace, - fallbackOptions, - cancellationToken).ConfigureAwait(false); - return (result.Id, await result.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false)); - })).ConfigureAwait(false); - - var solutionWithFixedReferences = MergeDocumentChanges(solutionWithChangedNamespace, fixedDocuments); + var fixedDocuments = await ProducerConsumer<(DocumentId documentId, SyntaxNode newRoot, PreservationMode preservationMode)>.RunParallelAsync( + source: refLocationGroups, + produceItems: static async (refInOneDocument, callback, args, cancellationToken) => + { + var result = await FixReferencingDocumentAsync( + args.solutionWithChangedNamespace.GetRequiredDocument(refInOneDocument.Key), + refInOneDocument, + args.newNamespace, + args.fallbackOptions, + cancellationToken).ConfigureAwait(false); + callback((result.Id, await result.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false), PreservationMode.PreserveValue)); + }, + args: (solutionWithChangedNamespace, newNamespace, fallbackOptions), + cancellationToken).ConfigureAwait(false); + var solutionWithFixedReferences = solutionWithChangedNamespace.WithDocumentSyntaxRoots(fixedDocuments); return (solutionWithFixedReferences, refLocationGroups.SelectAsArray(g => g.Key)); } - private static Solution MergeDocumentChanges( - Solution originalSolution, (DocumentId documentId, SyntaxNode newRoot)[] changedDocuments) - { - return originalSolution.WithDocumentSyntaxRoots( - changedDocuments.SelectAsArray(t => (t.documentId, t.newRoot, PreservationMode.PreserveValue))); - } - private readonly struct LocationForAffectedSymbol(ReferenceLocation location, bool isReferenceToExtensionMethod) { public ReferenceLocation ReferenceLocation { get; } = location; @@ -770,44 +767,39 @@ private static async Task RemoveUnnecessaryImportsAsync( CodeCleanupOptionsProvider fallbackOptions, CancellationToken cancellationToken) { - using var _ = PooledHashSet.GetInstance(out var linkedDocumentsToSkip); - var documentsToProcessBuilder = ArrayBuilder.GetInstance(); + using var _1 = PooledHashSet.GetInstance(out var linkedDocumentsToSkip); + using var _2 = ArrayBuilder.GetInstance(out var documentsToProcess); foreach (var id in ids) { if (linkedDocumentsToSkip.Contains(id)) - { continue; - } var document = solution.GetRequiredDocument(id); linkedDocumentsToSkip.AddRange(document.GetLinkedDocumentIds()); - documentsToProcessBuilder.Add(document); - - document = await RemoveUnnecessaryImportsWorkerAsync( - document, - CreateImports(document, names, withFormatterAnnotation: false), - cancellationToken).ConfigureAwait(false); - solution = document.Project.Solution; + documentsToProcess.Add(document); } - var documentsToProcess = documentsToProcessBuilder.ToImmutableAndFree(); - - var changeDocuments = await Task.WhenAll(documentsToProcess.Select( - async doc => + var changedDocuments = await ProducerConsumer<(DocumentId documentId, SyntaxNode newRoot, PreservationMode preservationMode)>.RunParallelAsync( + source: documentsToProcess, + produceItems: static async (doc, callback, args, cancellationToken) => { var result = await RemoveUnnecessaryImportsWorkerAsync( doc, - CreateImports(doc, names, withFormatterAnnotation: false), + CreateImports(doc, args.names, withFormatterAnnotation: false), + args.fallbackOptions, cancellationToken).ConfigureAwait(false); - return (result.Id, await result.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false)); - })).ConfigureAwait(false); + callback((result.Id, await result.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false), PreservationMode.PreserveValue)); + }, + args: (names, fallbackOptions), + cancellationToken).ConfigureAwait(false); - return MergeDocumentChanges(solution, changeDocuments); + return solution.WithDocumentSyntaxRoots(changedDocuments); - async Task RemoveUnnecessaryImportsWorkerAsync( + async static Task RemoveUnnecessaryImportsWorkerAsync( Document doc, IEnumerable importsToRemove, + CodeCleanupOptionsProvider fallbackOptions, CancellationToken token) { var removeImportService = doc.GetRequiredLanguageService(); From d83aace24563e7b7bff42ae4340519867552c5b1 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 8 May 2024 17:04:39 -0700 Subject: [PATCH 06/18] use new helper --- .../AbstractChangeSignatureService.cs | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs b/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs index 99322262f53c..d34fe3b1d562 100644 --- a/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs +++ b/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs @@ -24,6 +24,7 @@ using Microsoft.CodeAnalysis.Recommendations; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -414,17 +415,23 @@ private static async Task> FindChangeSignatureR } // Update the documents using the updated syntax trees - foreach (var docId in nodesToUpdate.Keys) - { - var updatedDoc = currentSolution.GetRequiredDocument(docId).WithSyntaxRoot(updatedRoots[docId]); - var cleanupOptions = await updatedDoc.GetCodeCleanupOptionsAsync(context.FallbackOptions, cancellationToken).ConfigureAwait(false); + var changedDocuments = await ProducerConsumer<(DocumentId documentId, SyntaxNode newRoot, PreservationMode mode)>.RunParallelAsync( + source: nodesToUpdate.Keys, + produceItems: static async (docId, callback, args, cancellationToken) => + { + var updatedDoc = args.currentSolution.GetRequiredDocument(docId).WithSyntaxRoot(args.updatedRoots[docId]); + var cleanupOptions = await updatedDoc.GetCodeCleanupOptionsAsync(args.context.FallbackOptions, cancellationToken).ConfigureAwait(false); - var docWithImports = await ImportAdder.AddImportsFromSymbolAnnotationAsync(updatedDoc, cleanupOptions.AddImportOptions, cancellationToken).ConfigureAwait(false); - var reducedDoc = await Simplifier.ReduceAsync(docWithImports, Simplifier.Annotation, cleanupOptions.SimplifierOptions, cancellationToken: cancellationToken).ConfigureAwait(false); - var formattedDoc = await Formatter.FormatAsync(reducedDoc, SyntaxAnnotation.ElasticAnnotation, cleanupOptions.FormattingOptions, cancellationToken).ConfigureAwait(false); + var docWithImports = await ImportAdder.AddImportsFromSymbolAnnotationAsync(updatedDoc, cleanupOptions.AddImportOptions, cancellationToken).ConfigureAwait(false); + var reducedDoc = await Simplifier.ReduceAsync(docWithImports, Simplifier.Annotation, cleanupOptions.SimplifierOptions, cancellationToken: cancellationToken).ConfigureAwait(false); + var formattedDoc = await Formatter.FormatAsync(reducedDoc, SyntaxAnnotation.ElasticAnnotation, cleanupOptions.FormattingOptions, cancellationToken).ConfigureAwait(false); - currentSolution = currentSolution.WithDocumentSyntaxRoot(docId, (await formattedDoc.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false))!); - } + callback((formattedDoc.Id, await formattedDoc.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false), PreservationMode.PreserveValue)); + }, + args: (currentSolution, updatedRoots, context), + cancellationToken).ConfigureAwait(false); + + currentSolution = currentSolution.WithDocumentSyntaxRoots(changedDocuments); telemetryTimer.Stop(); ChangeSignatureLogger.LogCommitInformation(telemetryNumberOfDeclarationsToUpdate, telemetryNumberOfReferencesToUpdate, telemetryTimer.Elapsed); From 293671a85f62e79081bfc9f1f08ec98807bfa8cf Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 8 May 2024 17:10:22 -0700 Subject: [PATCH 07/18] use new helper --- ...troduceParameterCodeRefactoringProvider.cs | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/Features/Core/Portable/IntroduceParameter/AbstractIntroduceParameterCodeRefactoringProvider.cs b/src/Features/Core/Portable/IntroduceParameter/AbstractIntroduceParameterCodeRefactoringProvider.cs index 9ad70f7081ce..45266894293e 100644 --- a/src/Features/Core/Portable/IntroduceParameter/AbstractIntroduceParameterCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/IntroduceParameter/AbstractIntroduceParameterCodeRefactoringProvider.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.IntroduceParameter; @@ -241,17 +242,26 @@ private async Task IntroduceParameterAsync(Document originalDocument, var rewriter = new IntroduceParameterDocumentRewriter(this, originalDocument, expression, methodSymbol, containingMethod, selectedCodeAction, fallbackOptions, allOccurrences); - foreach (var (project, projectCallSites) in methodCallSites.GroupBy(kvp => kvp.Key.Project)) - { - var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); - foreach (var (document, invocations) in projectCallSites) + var changedRoots = await ProducerConsumer<(DocumentId documentId, SyntaxNode newRoot, PreservationMode mode)>.RunParallelAsync( + source: methodCallSites.GroupBy(kvp => kvp.Key.Project), + produceItems: static async (tuple, callback, rewriter, cancellationToken) => { - var newRoot = await rewriter.RewriteDocumentAsync(compilation, document, invocations, cancellationToken).ConfigureAwait(false); - modifiedSolution = modifiedSolution.WithDocumentSyntaxRoot(document.Id, newRoot); - } - } - - return modifiedSolution; + var (project, projectCallSites) = tuple; + var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + await RoslynParallel.ForEachAsync( + projectCallSites, + cancellationToken, + async (tuple, cancellationToken) => + { + var (document, invocations) = tuple; + var newRoot = await rewriter.RewriteDocumentAsync(compilation, document, invocations, cancellationToken).ConfigureAwait(false); + callback((document.Id, newRoot, PreservationMode.PreserveValue)); + }).ConfigureAwait(false); + }, + args: rewriter, + cancellationToken).ConfigureAwait(false); + + return modifiedSolution.WithDocumentSyntaxRoots(changedRoots); } /// From f0cae2cfc62026d0f0f8ee6b057df7157086a065 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 8 May 2024 17:13:01 -0700 Subject: [PATCH 08/18] use new helper --- ...vertTupleToStructCodeRefactoringService.cs | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/Workspaces/Remote/ServiceHub/Services/ConvertTupleToStructCodeRefactoringProvider/RemoteConvertTupleToStructCodeRefactoringService.cs b/src/Workspaces/Remote/ServiceHub/Services/ConvertTupleToStructCodeRefactoringProvider/RemoteConvertTupleToStructCodeRefactoringService.cs index d7cdc206882f..a0933292be07 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/ConvertTupleToStructCodeRefactoringProvider/RemoteConvertTupleToStructCodeRefactoringService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/ConvertTupleToStructCodeRefactoringProvider/RemoteConvertTupleToStructCodeRefactoringService.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -79,18 +80,22 @@ private static async Task CleanupAsync(Solution oldSolution, Solution var changes = newSolution.GetChangedDocuments(oldSolution); var final = newSolution; - foreach (var docId in changes) - { - var document = newSolution.GetRequiredDocument(docId); + var changedDocuments = await ProducerConsumer<(DocumentId documentId, SyntaxNode newRoot, PreservationMode mode)>.RunParallelAsync( + source: changes, + produceItems: static async (docId, callback, args, cancellationToken) => + { + var document = args.newSolution.GetRequiredDocument(docId); - var options = await document.GetCodeCleanupOptionsAsync(fallbackOptions, cancellationToken).ConfigureAwait(false); - var cleaned = await CodeAction.CleanupDocumentAsync(document, options, cancellationToken).ConfigureAwait(false); + var options = await document.GetCodeCleanupOptionsAsync(args.fallbackOptions, cancellationToken).ConfigureAwait(false); + var cleaned = await CodeAction.CleanupDocumentAsync(document, options, cancellationToken).ConfigureAwait(false); - var cleanedRoot = await cleaned.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - final = final.WithDocumentSyntaxRoot(docId, cleanedRoot); - } + var cleanedRoot = await cleaned.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + callback((docId, cleanedRoot, PreservationMode.PreserveValue)); + }, + args: (newSolution, fallbackOptions), + cancellationToken).ConfigureAwait(false); - return final; + return newSolution.WithDocumentSyntaxRoots(changedDocuments); } } } From c07e1c7d1a7967f35674b0e8d8d9fa9ce1f0e4d8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 8 May 2024 17:26:19 -0700 Subject: [PATCH 09/18] simplify --- .../AbstractMakeMethodSynchronousCodeFixProvider.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Analyzers/Core/CodeFixes/MakeMethodSynchronous/AbstractMakeMethodSynchronousCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/MakeMethodSynchronous/AbstractMakeMethodSynchronousCodeFixProvider.cs index cbecadb7094d..b64d63941b49 100644 --- a/src/Analyzers/Core/CodeFixes/MakeMethodSynchronous/AbstractMakeMethodSynchronousCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/MakeMethodSynchronous/AbstractMakeMethodSynchronousCodeFixProvider.cs @@ -177,18 +177,14 @@ private static async Task RemoveAwaitFromCallersAsync( var editor = new SyntaxEditor(root, currentSolution.Services); foreach (var location in group) - { - RemoveAwaitFromCallerIfPresent(editor, syntaxFactsService, root, location, cancellationToken); - } + RemoveAwaitFromCallerIfPresent(editor, syntaxFactsService, location, cancellationToken); var newRoot = editor.GetChangedRoot(); return currentSolution.WithDocumentSyntaxRoot(document.Id, newRoot); } private static void RemoveAwaitFromCallerIfPresent( - SyntaxEditor editor, ISyntaxFactsService syntaxFacts, - SyntaxNode root, ReferenceLocation referenceLocation, - CancellationToken cancellationToken) + SyntaxEditor editor, ISyntaxFactsService syntaxFacts, ReferenceLocation referenceLocation, CancellationToken cancellationToken) { if (referenceLocation.IsImplicit) { From f8b5f1529936d7d51e3c03b32d28ab2e90accd6f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 8 May 2024 17:32:26 -0700 Subject: [PATCH 10/18] simplify --- .../EnableNullableCodeRefactoringProvider.cs | 4 ++-- .../ChangeSignature/AbstractChangeSignatureService.cs | 4 ++-- .../SyncNamespace/AbstractChangeNamespaceService.cs | 8 ++++---- .../AbstractIntroduceParameterCodeRefactoringProvider.cs | 4 ++-- .../Core/Def/CodeCleanup/AbstractCodeCleanUpFixer.cs | 2 +- .../Core/Portable/CodeActions/CodeAction_Cleanup.cs | 2 +- .../Core/Portable/Workspace/Solution/Solution.cs | 4 ++++ .../Workspace/Solution/SolutionCompilationState.cs | 2 +- .../RemoteConvertTupleToStructCodeRefactoringService.cs | 4 ++-- 9 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/Features/CSharp/Portable/CodeRefactorings/EnableNullable/EnableNullableCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/CodeRefactorings/EnableNullable/EnableNullableCodeRefactoringProvider.cs index e19a2b6c70db..03b08066988b 100644 --- a/src/Features/CSharp/Portable/CodeRefactorings/EnableNullable/EnableNullableCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/CodeRefactorings/EnableNullable/EnableNullableCodeRefactoringProvider.cs @@ -68,7 +68,7 @@ private static async Task EnableNullableReferenceTypesAsync( Project project, CodeActionPurpose purpose, CodeActionOptionsProvider fallbackOptions, IProgress _, CancellationToken cancellationToken) { var solution = project.Solution; - var updatedDocumentRoots = await ProducerConsumer<(DocumentId documentId, SyntaxNode newRoot, PreservationMode mode)>.RunParallelAsync( + var updatedDocumentRoots = await ProducerConsumer<(DocumentId documentId, SyntaxNode newRoot)>.RunParallelAsync( source: project.Documents, produceItems: static async (document, callback, fallbackOptions, cancellationToken) => { @@ -77,7 +77,7 @@ private static async Task EnableNullableReferenceTypesAsync( var updatedDocumentRoot = await EnableNullableReferenceTypesAsync( document, fallbackOptions, cancellationToken).ConfigureAwait(false); - callback((document.Id, updatedDocumentRoot, PreservationMode.PreserveValue)); + callback((document.Id, updatedDocumentRoot)); }, args: fallbackOptions, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs b/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs index d34fe3b1d562..260baa4eb404 100644 --- a/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs +++ b/src/Features/Core/Portable/ChangeSignature/AbstractChangeSignatureService.cs @@ -415,7 +415,7 @@ private static async Task> FindChangeSignatureR } // Update the documents using the updated syntax trees - var changedDocuments = await ProducerConsumer<(DocumentId documentId, SyntaxNode newRoot, PreservationMode mode)>.RunParallelAsync( + var changedDocuments = await ProducerConsumer<(DocumentId documentId, SyntaxNode newRoot)>.RunParallelAsync( source: nodesToUpdate.Keys, produceItems: static async (docId, callback, args, cancellationToken) => { @@ -426,7 +426,7 @@ private static async Task> FindChangeSignatureR var reducedDoc = await Simplifier.ReduceAsync(docWithImports, Simplifier.Annotation, cleanupOptions.SimplifierOptions, cancellationToken: cancellationToken).ConfigureAwait(false); var formattedDoc = await Formatter.FormatAsync(reducedDoc, SyntaxAnnotation.ElasticAnnotation, cleanupOptions.FormattingOptions, cancellationToken).ConfigureAwait(false); - callback((formattedDoc.Id, await formattedDoc.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false), PreservationMode.PreserveValue)); + callback((formattedDoc.Id, await formattedDoc.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false))); }, args: (currentSolution, updatedRoots, context), cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs index db4bceeeeafb..9630a575205f 100644 --- a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs +++ b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs @@ -488,7 +488,7 @@ private static SyntaxNode CreateImport(SyntaxGenerator syntaxGenerator, string n var refLocationGroups = refLocationsInSolution.GroupBy(loc => loc.Document.Id); - var fixedDocuments = await ProducerConsumer<(DocumentId documentId, SyntaxNode newRoot, PreservationMode preservationMode)>.RunParallelAsync( + var fixedDocuments = await ProducerConsumer<(DocumentId documentId, SyntaxNode newRoot)>.RunParallelAsync( source: refLocationGroups, produceItems: static async (refInOneDocument, callback, args, cancellationToken) => { @@ -498,7 +498,7 @@ private static SyntaxNode CreateImport(SyntaxGenerator syntaxGenerator, string n args.newNamespace, args.fallbackOptions, cancellationToken).ConfigureAwait(false); - callback((result.Id, await result.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false), PreservationMode.PreserveValue)); + callback((result.Id, await result.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false))); }, args: (solutionWithChangedNamespace, newNamespace, fallbackOptions), cancellationToken).ConfigureAwait(false); @@ -780,7 +780,7 @@ private static async Task RemoveUnnecessaryImportsAsync( documentsToProcess.Add(document); } - var changedDocuments = await ProducerConsumer<(DocumentId documentId, SyntaxNode newRoot, PreservationMode preservationMode)>.RunParallelAsync( + var changedDocuments = await ProducerConsumer<(DocumentId documentId, SyntaxNode newRoot)>.RunParallelAsync( source: documentsToProcess, produceItems: static async (doc, callback, args, cancellationToken) => { @@ -789,7 +789,7 @@ private static async Task RemoveUnnecessaryImportsAsync( CreateImports(doc, args.names, withFormatterAnnotation: false), args.fallbackOptions, cancellationToken).ConfigureAwait(false); - callback((result.Id, await result.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false), PreservationMode.PreserveValue)); + callback((result.Id, await result.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false))); }, args: (names, fallbackOptions), cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/IntroduceParameter/AbstractIntroduceParameterCodeRefactoringProvider.cs b/src/Features/Core/Portable/IntroduceParameter/AbstractIntroduceParameterCodeRefactoringProvider.cs index 45266894293e..03f44f9dc54b 100644 --- a/src/Features/Core/Portable/IntroduceParameter/AbstractIntroduceParameterCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/IntroduceParameter/AbstractIntroduceParameterCodeRefactoringProvider.cs @@ -242,7 +242,7 @@ private async Task IntroduceParameterAsync(Document originalDocument, var rewriter = new IntroduceParameterDocumentRewriter(this, originalDocument, expression, methodSymbol, containingMethod, selectedCodeAction, fallbackOptions, allOccurrences); - var changedRoots = await ProducerConsumer<(DocumentId documentId, SyntaxNode newRoot, PreservationMode mode)>.RunParallelAsync( + var changedRoots = await ProducerConsumer<(DocumentId documentId, SyntaxNode newRoot)>.RunParallelAsync( source: methodCallSites.GroupBy(kvp => kvp.Key.Project), produceItems: static async (tuple, callback, rewriter, cancellationToken) => { @@ -255,7 +255,7 @@ await RoslynParallel.ForEachAsync( { var (document, invocations) = tuple; var newRoot = await rewriter.RewriteDocumentAsync(compilation, document, invocations, cancellationToken).ConfigureAwait(false); - callback((document.Id, newRoot, PreservationMode.PreserveValue)); + callback((document.Id, newRoot)); }).ConfigureAwait(false); }, args: rewriter, diff --git a/src/VisualStudio/Core/Def/CodeCleanup/AbstractCodeCleanUpFixer.cs b/src/VisualStudio/Core/Def/CodeCleanup/AbstractCodeCleanUpFixer.cs index 7ea407ef0afc..3f422f9f713b 100644 --- a/src/VisualStudio/Core/Def/CodeCleanup/AbstractCodeCleanUpFixer.cs +++ b/src/VisualStudio/Core/Def/CodeCleanup/AbstractCodeCleanUpFixer.cs @@ -249,7 +249,7 @@ await RoslynParallel.ForEachAsync( args: (globalOptions, solution, enabledFixIds, progressTracker), cancellationToken).ConfigureAwait(false); - return solution.WithDocumentSyntaxRoots(changedRoots.SelectAsArray(t => (t.documentId, t.newRoot, PreservationMode.PreserveValue))); + return solution.WithDocumentSyntaxRoots(changedRoots); } private static async Task FixDocumentAsync( diff --git a/src/Workspaces/Core/Portable/CodeActions/CodeAction_Cleanup.cs b/src/Workspaces/Core/Portable/CodeActions/CodeAction_Cleanup.cs index 33e3ebc44bbc..0e1a0cebd1f9 100644 --- a/src/Workspaces/Core/Portable/CodeActions/CodeAction_Cleanup.cs +++ b/src/Workspaces/Core/Portable/CodeActions/CodeAction_Cleanup.cs @@ -168,7 +168,7 @@ async Task RunParallelCleanupPassAsync( cancellationToken).ConfigureAwait(false); // Grab all the cleaned roots and produce the new solution snapshot from that. - return solution.WithDocumentSyntaxRoots(changedRoots.SelectAsArray(t => (t.documentId, t.newRoot, PreservationMode.PreserveValue))); + return solution.WithDocumentSyntaxRoots(changedRoots); } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs index 5d07e9810301..9eb60f0a85db 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs @@ -1311,6 +1311,10 @@ public Solution WithAnalyzerConfigDocumentText(DocumentId documentId, TextAndVer public Solution WithDocumentSyntaxRoot(DocumentId documentId, SyntaxNode root, PreservationMode mode = PreservationMode.PreserveValue) => WithDocumentSyntaxRoots([(documentId, root, mode)]); + /// . + internal Solution WithDocumentSyntaxRoots(ImmutableArray<(DocumentId documentId, SyntaxNode root)> syntaxRoots) + => WithDocumentSyntaxRoots(syntaxRoots.SelectAsArray(t => (t.documentId, t.root, PreservationMode.PreserveValue))); + /// . internal Solution WithDocumentSyntaxRoots(ImmutableArray<(DocumentId documentId, SyntaxNode root, PreservationMode mode)> syntaxRoots) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs index 064de2969735..fa242b07820b 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionCompilationState.cs @@ -801,7 +801,7 @@ public SolutionCompilationState WithAnalyzerConfigDocumentText( this.SolutionState.WithAnalyzerConfigDocumentText(documentId, textAndVersion, mode)); } - /// + /// public SolutionCompilationState WithDocumentSyntaxRoots(ImmutableArray<(DocumentId documentId, SyntaxNode root, PreservationMode mode)> syntaxRoots) { return WithDocumentContents( diff --git a/src/Workspaces/Remote/ServiceHub/Services/ConvertTupleToStructCodeRefactoringProvider/RemoteConvertTupleToStructCodeRefactoringService.cs b/src/Workspaces/Remote/ServiceHub/Services/ConvertTupleToStructCodeRefactoringProvider/RemoteConvertTupleToStructCodeRefactoringService.cs index a0933292be07..c1d32bb09e5f 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/ConvertTupleToStructCodeRefactoringProvider/RemoteConvertTupleToStructCodeRefactoringService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/ConvertTupleToStructCodeRefactoringProvider/RemoteConvertTupleToStructCodeRefactoringService.cs @@ -80,7 +80,7 @@ private static async Task CleanupAsync(Solution oldSolution, Solution var changes = newSolution.GetChangedDocuments(oldSolution); var final = newSolution; - var changedDocuments = await ProducerConsumer<(DocumentId documentId, SyntaxNode newRoot, PreservationMode mode)>.RunParallelAsync( + var changedDocuments = await ProducerConsumer<(DocumentId documentId, SyntaxNode newRoot)>.RunParallelAsync( source: changes, produceItems: static async (docId, callback, args, cancellationToken) => { @@ -90,7 +90,7 @@ private static async Task CleanupAsync(Solution oldSolution, Solution var cleaned = await CodeAction.CleanupDocumentAsync(document, options, cancellationToken).ConfigureAwait(false); var cleanedRoot = await cleaned.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - callback((docId, cleanedRoot, PreservationMode.PreserveValue)); + callback((docId, cleanedRoot)); }, args: (newSolution, fallbackOptions), cancellationToken).ConfigureAwait(false); From 1ef30bd0055674a7975a068d3f05b5caa8a1936c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 8 May 2024 17:35:15 -0700 Subject: [PATCH 11/18] use helper --- .../MoveToNamespace/AbstractMoveToNamespaceService.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceService.cs b/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceService.cs index 0c45abfa34f0..b92b83f04038 100644 --- a/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceService.cs +++ b/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceService.cs @@ -288,12 +288,8 @@ private static async Task PropagateChangeToLinkedDocumentsAsync(Docume var formattedText = await formattedDocument.GetValueTextAsync(cancellationToken).ConfigureAwait(false); var solution = formattedDocument.Project.Solution; - foreach (var documentId in formattedDocument.GetLinkedDocumentIds()) - { - solution = solution.WithDocumentText(documentId, formattedText); - } - - return solution; + solution = solution.WithDocumentTexts( + formattedDocument.GetLinkedDocumentIds().SelectAsArray(id => (id, formattedText, PreservationMode.PreserveValue))); } private static string GetNewSymbolName(ISymbol symbol, string targetNamespace) From 26231b92e36c3f7fbc693b548f3bc6d1c64c96c2 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 8 May 2024 17:35:32 -0700 Subject: [PATCH 12/18] use helper --- .../Portable/MoveToNamespace/AbstractMoveToNamespaceService.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceService.cs b/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceService.cs index b92b83f04038..f277e94b2293 100644 --- a/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceService.cs +++ b/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceService.cs @@ -288,8 +288,9 @@ private static async Task PropagateChangeToLinkedDocumentsAsync(Docume var formattedText = await formattedDocument.GetValueTextAsync(cancellationToken).ConfigureAwait(false); var solution = formattedDocument.Project.Solution; - solution = solution.WithDocumentTexts( + var finalSolution = solution.WithDocumentTexts( formattedDocument.GetLinkedDocumentIds().SelectAsArray(id => (id, formattedText, PreservationMode.PreserveValue))); + return finalSolution; } private static string GetNewSymbolName(ISymbol symbol, string targetNamespace) From 3f861040b572cd48f28fc6104760a35c98dee34f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 8 May 2024 17:37:41 -0700 Subject: [PATCH 13/18] helper --- ...enameTrackingTaggerProvider.RenameTrackingCommitter.cs | 8 +++----- .../MoveToNamespace/AbstractMoveToNamespaceService.cs | 2 +- .../Core/Portable/Workspace/Solution/Solution.cs | 3 +++ 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.RenameTrackingCommitter.cs b/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.RenameTrackingCommitter.cs index dcc3fe3db63e..1168c4eced62 100644 --- a/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.RenameTrackingCommitter.cs +++ b/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.RenameTrackingCommitter.cs @@ -196,12 +196,10 @@ private async Task CreateSolutionWithOriginalNameAsync( // Apply the original name to all linked documents to construct a consistent solution var solution = document.Project.Solution; - foreach (var documentId in document.GetLinkedDocumentIds().Add(document.Id)) - { - solution = solution.WithDocumentText(documentId, newFullText); - } + var finalSolution = solution.WithDocumentTexts( + document.GetLinkedDocumentIds().Add(document.Id).SelectAsArray(id => (id, newFullText))); - return solution; + return finalSolution; } private async Task TryGetSymbolAsync(Solution solutionWithOriginalName, DocumentId documentId, CancellationToken cancellationToken) diff --git a/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceService.cs b/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceService.cs index f277e94b2293..a967f30ae4c5 100644 --- a/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceService.cs +++ b/src/Features/Core/Portable/MoveToNamespace/AbstractMoveToNamespaceService.cs @@ -289,7 +289,7 @@ private static async Task PropagateChangeToLinkedDocumentsAsync(Docume var solution = formattedDocument.Project.Solution; var finalSolution = solution.WithDocumentTexts( - formattedDocument.GetLinkedDocumentIds().SelectAsArray(id => (id, formattedText, PreservationMode.PreserveValue))); + formattedDocument.GetLinkedDocumentIds().SelectAsArray(id => (id, formattedText))); return finalSolution; } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs index 9eb60f0a85db..978f704c4154 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs @@ -1183,6 +1183,9 @@ public Solution WithDocumentFilePath(DocumentId documentId, string filePath) public Solution WithDocumentText(DocumentId documentId, SourceText text, PreservationMode mode = PreservationMode.PreserveValue) => WithDocumentTexts([(documentId, text, mode)]); + internal Solution WithDocumentTexts(ImmutableArray<(DocumentId documentId, SourceText text)> texts) + => WithDocumentTexts(texts.SelectAsArray(t => (t.documentId, t.text, PreservationMode.PreserveValue))); + internal Solution WithDocumentTexts(ImmutableArray<(DocumentId documentId, SourceText text, PreservationMode mode)> texts) { foreach (var (documentId, text, mode) in texts) From d1260da369a46a043b4094d070cb54e7a9936f4c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 8 May 2024 17:40:06 -0700 Subject: [PATCH 14/18] use helper --- .../AbstractSuppressionBatchFixAllProvider.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionBatchFixAllProvider.cs b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionBatchFixAllProvider.cs index 7fb1b6b5b54a..78d630386483 100644 --- a/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionBatchFixAllProvider.cs +++ b/src/Features/Core/Portable/CodeFixes/Suppression/AbstractSuppressionBatchFixAllProvider.cs @@ -240,11 +240,8 @@ private static async Task TryMergeFixesAsync( // Finally, apply the changes to each document to the solution, producing the // new solution. - var currentSolution = oldSolution; - foreach (var (documentId, finalText) in documentIdToFinalText) - currentSolution = currentSolution.WithDocumentText(documentId, finalText); - - return currentSolution; + var finalSolution = oldSolution.WithDocumentTexts(documentIdToFinalText); + return finalSolution; } private static async Task>> GetDocumentIdToChangedDocumentsAsync( @@ -269,7 +266,7 @@ private static async Task TryMergeFixesAsync( return documentIdToChangedDocuments; } - private static async Task> GetDocumentIdToFinalTextAsync( + private static async Task> GetDocumentIdToFinalTextAsync( Solution oldSolution, IReadOnlyDictionary> documentIdToChangedDocuments, ImmutableArray<(Diagnostic diagnostic, CodeAction action)> diagnosticsAndCodeActions, @@ -291,7 +288,7 @@ private static async Task> GetDocume } await Task.WhenAll(getFinalDocumentTasks).ConfigureAwait(false); - return documentIdToFinalText; + return documentIdToFinalText.SelectAsArray(kvp => (kvp.Key, kvp.Value)); } private static async Task GetFinalDocumentTextAsync( From efc2a1506a966afc1b08929d56bb3edc347b04ec Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 8 May 2024 17:41:50 -0700 Subject: [PATCH 15/18] use helper --- .../FixAllOccurrences/BatchFixAllProvider.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/BatchFixAllProvider.cs b/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/BatchFixAllProvider.cs index a7e5192a9bdf..c5fccb6afbcf 100644 --- a/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/BatchFixAllProvider.cs +++ b/src/Workspaces/Core/Portable/CodeFixes/FixAllOccurrences/BatchFixAllProvider.cs @@ -253,15 +253,10 @@ private static Action> GetRegisterCodeFix private static async Task ApplyChangesAsync( Solution currentSolution, - ImmutableArray<(DocumentId, TextChangeMerger)> docIdsAndMerger, + ImmutableArray<(DocumentId documentId, TextChangeMerger merger)> docIdsAndMerger, CancellationToken cancellationToken) { - foreach (var (documentId, textMerger) in docIdsAndMerger) - { - var newText = await textMerger.GetFinalMergedTextAsync(cancellationToken).ConfigureAwait(false); - currentSolution = currentSolution.WithDocumentText(documentId, newText); - } - - return currentSolution; + var docIdsAndTexts = await docIdsAndMerger.SelectAsArrayAsync(async t => (t.documentId, await t.merger.GetFinalMergedTextAsync(cancellationToken).ConfigureAwait(false))).ConfigureAwait(false); + return currentSolution.WithDocumentTexts(docIdsAndTexts); } } From 47f4a96622ccb7ed3d65d59b22b4fb0e0ba65b79 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 8 May 2024 17:43:48 -0700 Subject: [PATCH 16/18] use helper --- .../DocumentBasedFixAllProviderHelpers.cs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/Workspaces/Core/Portable/CodeFixesAndRefactorings/DocumentBasedFixAllProviderHelpers.cs b/src/Workspaces/Core/Portable/CodeFixesAndRefactorings/DocumentBasedFixAllProviderHelpers.cs index 24589e388454..a58b5618efa8 100644 --- a/src/Workspaces/Core/Portable/CodeFixesAndRefactorings/DocumentBasedFixAllProviderHelpers.cs +++ b/src/Workspaces/Core/Portable/CodeFixesAndRefactorings/DocumentBasedFixAllProviderHelpers.cs @@ -52,17 +52,14 @@ internal static class DocumentBasedFixAllProviderHelpers progressTracker, cancellationToken).ConfigureAwait(false); - // Once we clean the document, we get the text of it and insert that back into the final solution. This way - // we can release both the original fixed tree, and the cleaned tree (both of which can be much more - // expensive than just text). - var finalSolution = cleanedSolution; - foreach (var documentId in CodeAction.GetAllChangedOrAddedDocumentIds(originalSolution, finalSolution)) - { - var cleanedDocument = finalSolution.GetRequiredDocument(documentId); - var cleanedText = await cleanedDocument.GetValueTextAsync(cancellationToken).ConfigureAwait(false); - finalSolution = finalSolution.WithDocumentText(documentId, cleanedText); - } - + // Once we clean the document, we get the text of it and insert that back into the final solution. This way we + // can release both the original fixed tree, and the cleaned tree (both of which can be much more expensive than + // just text). + var cleanedTexts = await CodeAction.GetAllChangedOrAddedDocumentIds(originalSolution, cleanedSolution) + .SelectAsArrayAsync(async documentId => (documentId, await cleanedSolution.GetRequiredDocument(documentId).GetTextAsync(cancellationToken).ConfigureAwait(false))) + .ConfigureAwait(false); + + var finalSolution = cleanedSolution.WithDocumentTexts(cleanedTexts); return finalSolution; async Task GetInitialUncleanedSolutionAsync(Solution originalSolution) From cbc0df086f484cd0441ccee63767948551e88dcf Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 8 May 2024 17:44:28 -0700 Subject: [PATCH 17/18] use helper --- .../LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs b/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs index 1f7fb23a853f..59464d6c0f4f 100644 --- a/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs +++ b/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs @@ -54,10 +54,8 @@ internal async Task MergeDiffsAsync(IMergeConflict mergedText = await newSolution.GetDocument(linkedDocumentsWithChanges.Single()).GetValueTextAsync(cancellationToken).ConfigureAwait(false); } - foreach (var documentId in allLinkedDocuments) - { - updatedSolution = updatedSolution.WithDocumentText(documentId, mergedText); - } + updatedSolution = updatedSolution.WithDocumentTexts( + allLinkedDocuments.SelectAsArray(documentId => (documentId, mergedText))); } return new LinkedFileMergeSessionResult(updatedSolution, linkedFileMergeResults); From a870326caa00591f365db11ca767bbf4a5c3976d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 8 May 2024 17:48:27 -0700 Subject: [PATCH 18/18] use helper --- .../Core/Portable/Remote/RemoteUtilities.cs | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/Workspaces/Core/Portable/Remote/RemoteUtilities.cs b/src/Workspaces/Core/Portable/Remote/RemoteUtilities.cs index 31746fb54306..ee221fb20114 100644 --- a/src/Workspaces/Core/Portable/Remote/RemoteUtilities.cs +++ b/src/Workspaces/Core/Portable/Remote/RemoteUtilities.cs @@ -47,15 +47,21 @@ internal static class RemoteUtilities /// a solution textually equivalent to the newSolution passed to . /// public static async Task UpdateSolutionAsync( - Solution oldSolution, ImmutableArray<(DocumentId, ImmutableArray)> documentTextChanges, CancellationToken cancellationToken) + Solution oldSolution, + ImmutableArray<(DocumentId documentId, ImmutableArray textChanges)> documentTextChanges, + CancellationToken cancellationToken) { var currentSolution = oldSolution; - foreach (var (docId, textChanges) in documentTextChanges) - { - var text = await oldSolution.GetDocument(docId).GetValueTextAsync(cancellationToken).ConfigureAwait(false); - currentSolution = currentSolution.WithDocumentText(docId, text.WithChanges(textChanges)); - } - return currentSolution; + var documentIdsAndTexts = await documentTextChanges + .SelectAsArrayAsync(async (tuple, cancellationToken) => + { + var oldText = await oldSolution.GetDocument(tuple.documentId).GetValueTextAsync(cancellationToken).ConfigureAwait(false); + var newText = oldText.WithChanges(tuple.textChanges); + return (tuple.documentId, newText); + }, cancellationToken) + .ConfigureAwait(false); + + return oldSolution.WithDocumentTexts(documentIdsAndTexts); } }