|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.ErrorReporting;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis
{
internal partial class SolutionState
{
private abstract partial class CompilationAndGeneratorDriverTranslationAction
{
internal sealed class TouchDocumentAction : CompilationAndGeneratorDriverTranslationAction
{
private readonly DocumentState _oldState;
private readonly DocumentState _newState;
public TouchDocumentAction(DocumentState oldState, DocumentState newState)
{
_oldState = oldState;
_newState = newState;
}
public override Task<Compilation> TransformCompilationAsync(Compilation oldCompilation, CancellationToken cancellationToken)
{
return UpdateDocumentInCompilationAsync(oldCompilation, _oldState, _newState, cancellationToken);
}
public DocumentId DocumentId => _newState.Attributes.Id;
// Replacing a single tree doesn't impact the generated trees in a compilation, so we can use this against
// compilations that have generated trees.
public override bool CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput => true;
public override CompilationAndGeneratorDriverTranslationAction? TryMergeWithPrior(CompilationAndGeneratorDriverTranslationAction priorAction)
{
if (priorAction is TouchDocumentAction priorTouchAction &&
priorTouchAction._newState == _oldState)
{
return new TouchDocumentAction(priorTouchAction._oldState, _newState);
}
return null;
}
}
internal sealed class TouchAdditionalDocumentAction : CompilationAndGeneratorDriverTranslationAction
{
private readonly AdditionalDocumentState _oldState;
private readonly AdditionalDocumentState _newState;
public TouchAdditionalDocumentAction(AdditionalDocumentState oldState, AdditionalDocumentState newState)
{
_oldState = oldState;
_newState = newState;
}
// Changing an additional document doesn't change the compilation directly, so we can "apply" the
// translation (which is a no-op). Since we use a 'false' here to mean that it's not worth keeping
// the compilation with stale trees around, answering true is still important.
public override bool CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput => true;
public override CompilationAndGeneratorDriverTranslationAction? TryMergeWithPrior(CompilationAndGeneratorDriverTranslationAction priorAction)
{
if (priorAction is TouchAdditionalDocumentAction priorTouchAction &&
priorTouchAction._newState == _oldState)
{
return new TouchAdditionalDocumentAction(priorTouchAction._oldState, _newState);
}
return null;
}
public override GeneratorDriver? TransformGeneratorDriver(GeneratorDriver generatorDriver)
{
var oldText = _oldState.AdditionalText;
var newText = _newState.AdditionalText;
return generatorDriver.ReplaceAdditionalText(oldText, newText);
}
}
internal sealed class RemoveDocumentsAction : CompilationAndGeneratorDriverTranslationAction
{
private readonly ImmutableArray<DocumentState> _documents;
public RemoveDocumentsAction(ImmutableArray<DocumentState> documents)
{
_documents = documents;
}
public override async Task<Compilation> TransformCompilationAsync(Compilation oldCompilation, CancellationToken cancellationToken)
{
var syntaxTrees = new List<SyntaxTree>(_documents.Length);
foreach (var document in _documents)
{
cancellationToken.ThrowIfCancellationRequested();
syntaxTrees.Add(await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false));
}
return oldCompilation.RemoveSyntaxTrees(syntaxTrees);
}
// This action removes the specified trees, but leaves the generated trees untouched.
public override bool CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput => true;
}
internal sealed class AddDocumentsAction : CompilationAndGeneratorDriverTranslationAction
{
private readonly ImmutableArray<DocumentState> _documents;
public AddDocumentsAction(ImmutableArray<DocumentState> documents)
{
_documents = documents;
}
public override async Task<Compilation> TransformCompilationAsync(Compilation oldCompilation, CancellationToken cancellationToken)
{
var syntaxTrees = new List<SyntaxTree>(capacity: _documents.Length);
foreach (var document in _documents)
{
cancellationToken.ThrowIfCancellationRequested();
syntaxTrees.Add(await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false));
}
return oldCompilation.AddSyntaxTrees(syntaxTrees);
}
// This action adds the specified trees, but leaves the generated trees untouched.
public override bool CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput => true;
}
internal sealed class ReplaceAllSyntaxTreesAction : CompilationAndGeneratorDriverTranslationAction
{
private readonly ProjectState _state;
private readonly bool _isParseOptionChange;
public ReplaceAllSyntaxTreesAction(ProjectState state, bool isParseOptionChange)
{
_state = state;
_isParseOptionChange = isParseOptionChange;
}
public override async Task<Compilation> TransformCompilationAsync(Compilation oldCompilation, CancellationToken cancellationToken)
{
var syntaxTrees = new List<SyntaxTree>(capacity: _state.DocumentStates.Count);
foreach (var documentState in _state.DocumentStates.GetStatesInCompilationOrder())
{
cancellationToken.ThrowIfCancellationRequested();
syntaxTrees.Add(await documentState.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false));
}
return oldCompilation.RemoveAllSyntaxTrees().AddSyntaxTrees(syntaxTrees);
}
// Because this removes all trees, it'd also remove the generated trees.
public override bool CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput => false;
public override GeneratorDriver? TransformGeneratorDriver(GeneratorDriver generatorDriver)
{
if (_isParseOptionChange)
{
RoslynDebug.AssertNotNull(_state.ParseOptions);
return generatorDriver.WithUpdatedParseOptions(_state.ParseOptions);
}
else
{
// We are using this as a way to reorder syntax trees -- we don't need to do anything as the driver
// will get the new compilation once we pass it to it.
return generatorDriver;
}
}
}
internal sealed class ProjectCompilationOptionsAction : CompilationAndGeneratorDriverTranslationAction
{
private readonly ProjectState _state;
private readonly bool _isAnalyzerConfigChange;
public ProjectCompilationOptionsAction(ProjectState state, bool isAnalyzerConfigChange)
{
_state = state;
_isAnalyzerConfigChange = isAnalyzerConfigChange;
}
public override Task<Compilation> TransformCompilationAsync(Compilation oldCompilation, CancellationToken cancellationToken)
{
RoslynDebug.AssertNotNull(_state.CompilationOptions);
return Task.FromResult(oldCompilation.WithOptions(_state.CompilationOptions));
}
// Updating the options of a compilation doesn't require us to reparse trees, so we can use this to update
// compilations with stale generated trees.
public override bool CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput => true;
public override GeneratorDriver? TransformGeneratorDriver(GeneratorDriver generatorDriver)
{
if (_isAnalyzerConfigChange)
{
return generatorDriver.WithUpdatedAnalyzerConfigOptions(_state.AnalyzerOptions.AnalyzerConfigOptionsProvider);
}
else
{
// Changing any other option is fine and the driver can be reused. The driver
// will get the new compilation once we pass it to it.
return generatorDriver;
}
}
}
internal sealed class ProjectAssemblyNameAction : CompilationAndGeneratorDriverTranslationAction
{
private readonly string _assemblyName;
public ProjectAssemblyNameAction(string assemblyName)
{
_assemblyName = assemblyName;
}
public override Task<Compilation> TransformCompilationAsync(Compilation oldCompilation, CancellationToken cancellationToken)
{
return Task.FromResult(oldCompilation.WithAssemblyName(_assemblyName));
}
// Updating the options of a compilation doesn't require us to reparse trees, so we can use this to update
// compilations with stale generated trees.
public override bool CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput => true;
}
internal sealed class AddOrRemoveAnalyzerReferencesAction : CompilationAndGeneratorDriverTranslationAction
{
private readonly string _language;
private readonly ImmutableArray<AnalyzerReference> _referencesToAdd;
private readonly ImmutableArray<AnalyzerReference> _referencesToRemove;
public AddOrRemoveAnalyzerReferencesAction(string language, ImmutableArray<AnalyzerReference> referencesToAdd = default, ImmutableArray<AnalyzerReference> referencesToRemove = default)
{
_language = language;
_referencesToAdd = referencesToAdd;
_referencesToRemove = referencesToRemove;
}
// Changing analyzer references doesn't change the compilation directly, so we can "apply" the
// translation (which is a no-op). Since we use a 'false' here to mean that it's not worth keeping
// the compilation with stale trees around, answering true is still important.
public override bool CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput => true;
public override GeneratorDriver? TransformGeneratorDriver(GeneratorDriver generatorDriver)
{
if (!_referencesToRemove.IsDefaultOrEmpty)
{
generatorDriver = generatorDriver.RemoveGenerators(_referencesToRemove.SelectMany(r => r.GetGenerators(_language)).ToImmutableArray());
}
if (!_referencesToAdd.IsDefaultOrEmpty)
{
generatorDriver = generatorDriver.AddGenerators(_referencesToAdd.SelectMany(r => r.GetGenerators(_language)).ToImmutableArray());
}
return generatorDriver;
}
}
internal sealed class AddAdditionalDocumentsAction : CompilationAndGeneratorDriverTranslationAction
{
private readonly ImmutableArray<AdditionalDocumentState> _additionalDocuments;
public AddAdditionalDocumentsAction(ImmutableArray<AdditionalDocumentState> additionalDocuments)
{
_additionalDocuments = additionalDocuments;
}
// Changing an additional document doesn't change the compilation directly, so we can "apply" the
// translation (which is a no-op). Since we use a 'false' here to mean that it's not worth keeping
// the compilation with stale trees around, answering true is still important.
public override bool CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput => true;
public override GeneratorDriver? TransformGeneratorDriver(GeneratorDriver generatorDriver)
{
return generatorDriver.AddAdditionalTexts(_additionalDocuments.SelectAsArray(static documentState => documentState.AdditionalText));
}
}
internal sealed class RemoveAdditionalDocumentsAction : CompilationAndGeneratorDriverTranslationAction
{
private readonly ImmutableArray<AdditionalDocumentState> _additionalDocuments;
public RemoveAdditionalDocumentsAction(ImmutableArray<AdditionalDocumentState> additionalDocuments)
{
_additionalDocuments = additionalDocuments;
}
// Changing an additional document doesn't change the compilation directly, so we can "apply" the
// translation (which is a no-op). Since we use a 'false' here to mean that it's not worth keeping
// the compilation with stale trees around, answering true is still important.
public override bool CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput => true;
public override GeneratorDriver? TransformGeneratorDriver(GeneratorDriver generatorDriver)
{
return generatorDriver.RemoveAdditionalTexts(_additionalDocuments.SelectAsArray(static documentState => documentState.AdditionalText));
}
}
internal sealed class ReplaceGeneratorDriverAction : CompilationAndGeneratorDriverTranslationAction
{
private readonly GeneratorDriver _oldGeneratorDriver;
private readonly ProjectState _newProjectState;
public ReplaceGeneratorDriverAction(GeneratorDriver oldGeneratorDriver, ProjectState newProjectState)
{
_oldGeneratorDriver = oldGeneratorDriver;
_newProjectState = newProjectState;
}
public override bool CanUpdateCompilationWithStaleGeneratedTreesIfGeneratorsGiveSameOutput => true;
public override GeneratorDriver? TransformGeneratorDriver(GeneratorDriver _)
{
// The GeneratorDriver that we have here is from a prior version of the Project, it may be missing state changes due
// to changes to the project. We'll update everything here.
var generatorDriver = _oldGeneratorDriver.ReplaceAdditionalTexts(_newProjectState.AdditionalDocumentStates.SelectAsArray(static documentState => documentState.AdditionalText))
.WithUpdatedParseOptions(_newProjectState.ParseOptions!)
.WithUpdatedAnalyzerConfigOptions(_newProjectState.AnalyzerOptions.AnalyzerConfigOptionsProvider)
.ReplaceGenerators(_newProjectState.SourceGenerators.ToImmutableArray());
return generatorDriver;
}
public override CompilationAndGeneratorDriverTranslationAction? TryMergeWithPrior(CompilationAndGeneratorDriverTranslationAction priorAction)
{
// If the prior action is also a ReplaceGeneratorDriverAction, we'd entirely overwrite it's changes, so we can drop the prior one entirely.
return priorAction is ReplaceGeneratorDriverAction ? this : null;
}
}
}
}
}
|