Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update features to fork solution once instead of forking once per changed document. #73396

Merged
merged 19 commits into from
May 9, 2024
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -177,18 +177,14 @@ private static async Task<Solution> 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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,12 +196,10 @@ private async Task<Solution> 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<ISymbol> TryGetSymbolAsync(Solution solutionWithOriginalName, DocumentId documentId, CancellationToken cancellationToken)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -67,14 +68,21 @@ private static async Task<Solution> EnableNullableReferenceTypesAsync(
Project project, CodeActionPurpose purpose, CodeActionOptionsProvider fallbackOptions, IProgress<CodeAnalysisProgress> _, 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)>.RunParallelAsync(
source: project.Documents,
produceItems: static async (document, callback, fallbackOptions, cancellationToken) =>
{
if (await document.IsGeneratedCodeAsync(cancellationToken).ConfigureAwait(false))
return;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

view with whitespace off.


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));
},
args: fallbackOptions,
cancellationToken).ConfigureAwait(false);

solution = solution.WithDocumentSyntaxRoots(updatedDocumentRoots);

if (purpose is CodeActionPurpose.Apply)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -414,17 +415,23 @@ private static async Task<ImmutableArray<ReferencedSymbol>> 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)>.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)));
},
args: (currentSolution, updatedRoots, context),
cancellationToken).ConfigureAwait(false);

currentSolution = currentSolution.WithDocumentSyntaxRoots(changedDocuments);

telemetryTimer.Stop();
ChangeSignatureLogger.LogCommitInformation(telemetryNumberOfDeclarationsToUpdate, telemetryNumberOfReferencesToUpdate, telemetryTimer.Elapsed);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,11 +240,8 @@ private static async Task<Solution> 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<IReadOnlyDictionary<DocumentId, ConcurrentBag<(CodeAction, Document)>>> GetDocumentIdToChangedDocumentsAsync(
Expand All @@ -269,7 +266,7 @@ private static async Task<Solution> TryMergeFixesAsync(
return documentIdToChangedDocuments;
}

private static async Task<IReadOnlyDictionary<DocumentId, SourceText>> GetDocumentIdToFinalTextAsync(
private static async Task<ImmutableArray<(DocumentId documentId, SourceText newText)>> GetDocumentIdToFinalTextAsync(
Solution oldSolution,
IReadOnlyDictionary<DocumentId, ConcurrentBag<(CodeAction, Document)>> documentIdToChangedDocuments,
ImmutableArray<(Diagnostic diagnostic, CodeAction action)> diagnosticsAndCodeActions,
Expand All @@ -291,7 +288,7 @@ private static async Task<IReadOnlyDictionary<DocumentId, SourceText>> GetDocume
}

await Task.WhenAll(getFinalDocumentTasks).ConfigureAwait(false);
return documentIdToFinalText;
return documentIdToFinalText.SelectAsArray(kvp => (kvp.Key, kvp.Value));
}

private static async Task GetFinalDocumentTextAsync(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -487,32 +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(refInOneDocument =>
FixReferencingDocumentAsync(
solutionWithChangedNamespace.GetRequiredDocument(refInOneDocument.Key),
var fixedDocuments = await ProducerConsumer<(DocumentId documentId, SyntaxNode newRoot)>.RunParallelAsync(
source: refLocationGroups,
produceItems: static async (refInOneDocument, callback, args, cancellationToken) =>
{
var result = await FixReferencingDocumentAsync(
args.solutionWithChangedNamespace.GetRequiredDocument(refInOneDocument.Key),
refInOneDocument,
newNamespace,
fallbackOptions,
cancellationToken))).ConfigureAwait(false);

var solutionWithFixedReferences = await MergeDocumentChangesAsync(solutionWithChangedNamespace, fixedDocuments, cancellationToken).ConfigureAwait(false);
args.newNamespace,
args.fallbackOptions,
cancellationToken).ConfigureAwait(false);
callback((result.Id, await result.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false)));
},
args: (solutionWithChangedNamespace, newNamespace, fallbackOptions),
cancellationToken).ConfigureAwait(false);

var solutionWithFixedReferences = solutionWithChangedNamespace.WithDocumentSyntaxRoots(fixedDocuments);
return (solutionWithFixedReferences, refLocationGroups.SelectAsArray(g => g.Key));
}

private static async Task<Solution> MergeDocumentChangesAsync(Solution originalSolution, Document[] changedDocuments, CancellationToken cancellationToken)
{
foreach (var document in changedDocuments)
{
originalSolution = originalSolution.WithDocumentSyntaxRoot(
document.Id,
await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false));
}

return originalSolution;
}

private readonly struct LocationForAffectedSymbol(ReferenceLocation location, bool isReferenceToExtensionMethod)
{
public ReferenceLocation ReferenceLocation { get; } = location;
Expand Down Expand Up @@ -773,40 +767,39 @@ private static async Task<Solution> RemoveUnnecessaryImportsAsync(
CodeCleanupOptionsProvider fallbackOptions,
CancellationToken cancellationToken)
{
using var _ = PooledHashSet<DocumentId>.GetInstance(out var linkedDocumentsToSkip);
var documentsToProcessBuilder = ArrayBuilder<Document>.GetInstance();
using var _1 = PooledHashSet<DocumentId>.GetInstance(out var linkedDocumentsToSkip);
using var _2 = ArrayBuilder<Document>.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);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was wacky. we were updating the documents serially, and then doing a parallel processing of it below.

solution = document.Project.Solution;
documentsToProcess.Add(document);
}

var documentsToProcess = documentsToProcessBuilder.ToImmutableAndFree();

var changeDocuments = await Task.WhenAll(documentsToProcess.Select(
doc => RemoveUnnecessaryImportsWorkerAsync(
var changedDocuments = await ProducerConsumer<(DocumentId documentId, SyntaxNode newRoot)>.RunParallelAsync(
source: documentsToProcess,
produceItems: static async (doc, callback, args, cancellationToken) =>
{
var result = await RemoveUnnecessaryImportsWorkerAsync(
doc,
CreateImports(doc, names, withFormatterAnnotation: false),
cancellationToken))).ConfigureAwait(false);
CreateImports(doc, args.names, withFormatterAnnotation: false),
args.fallbackOptions,
cancellationToken).ConfigureAwait(false);
callback((result.Id, await result.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false)));
},
args: (names, fallbackOptions),
cancellationToken).ConfigureAwait(false);

return await MergeDocumentChangesAsync(solution, changeDocuments, cancellationToken).ConfigureAwait(false);
return solution.WithDocumentSyntaxRoots(changedDocuments);

async Task<Document> RemoveUnnecessaryImportsWorkerAsync(
async static Task<Document> RemoveUnnecessaryImportsWorkerAsync(
Document doc,
IEnumerable<SyntaxNode> importsToRemove,
CodeCleanupOptionsProvider fallbackOptions,
CancellationToken token)
{
var removeImportService = doc.GetRequiredLanguageService<IRemoveUnnecessaryImportsService>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -241,17 +242,26 @@ private async Task<Solution> IntroduceParameterAsync(Document originalDocument,
var rewriter = new IntroduceParameterDocumentRewriter(this, originalDocument,
expression, methodSymbol, containingMethod, selectedCodeAction, fallbackOptions, allOccurrences);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i checked, and rewriter should be safe to use concurrently from within produceItems below.


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)>.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));
}).ConfigureAwait(false);
},
args: rewriter,
cancellationToken).ConfigureAwait(false);

return modifiedSolution.WithDocumentSyntaxRoots(changedRoots);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,12 +288,9 @@ private static async Task<Solution> 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;
var finalSolution = solution.WithDocumentTexts(
formattedDocument.GetLinkedDocumentIds().SelectAsArray(id => (id, formattedText)));
return finalSolution;
}

private static string GetNewSymbolName(ISymbol symbol, string targetNamespace)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Document> FixDocumentAsync(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ async Task<Solution> 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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -253,15 +253,10 @@ private static Action<CodeAction, ImmutableArray<Diagnostic>> GetRegisterCodeFix

private static async Task<Solution> 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);
}
}
Loading
Loading