File: Simplification\AbstractSimplificationService.cs
Web Access
Project: ..\..\..\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// 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.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Collections;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Simplification
{
    internal abstract class AbstractSimplificationService<TExpressionSyntax, TStatementSyntax, TCrefSyntax> : ISimplificationService
        where TExpressionSyntax : SyntaxNode
        where TStatementSyntax : SyntaxNode
        where TCrefSyntax : SyntaxNode
    {
        protected static readonly Func<SyntaxNode, bool> s_containsAnnotations = n => n.ContainsAnnotations;
        protected static readonly Func<SyntaxNodeOrToken, bool> s_hasSimplifierAnnotation = n => n.HasAnnotation(Simplifier.Annotation);
 
        private readonly ImmutableArray<AbstractReducer> _reducers;
 
        protected AbstractSimplificationService(ImmutableArray<AbstractReducer> reducers)
            => _reducers = reducers;
 
        protected abstract ImmutableArray<NodeOrTokenToReduce> GetNodesAndTokensToReduce(SyntaxNode root, Func<SyntaxNodeOrToken, bool> isNodeOrTokenOutsideSimplifySpans);
        protected abstract SemanticModel GetSpeculativeSemanticModel(ref SyntaxNode nodeToSpeculate, SemanticModel originalSemanticModel, SyntaxNode originalNode);
        protected abstract bool NodeRequiresNonSpeculativeSemanticModel(SyntaxNode node);
 
        public abstract SimplifierOptions DefaultOptions { get; }
        public abstract SimplifierOptions GetSimplifierOptions(IOptionsReader options, SimplifierOptions? fallbackOptions);
 
        protected virtual SyntaxNode TransformReducedNode(SyntaxNode reducedNode, SyntaxNode originalNode)
            => reducedNode;
 
        public abstract SyntaxNode Expand(SyntaxNode node, SemanticModel semanticModel, SyntaxAnnotation? annotationForReplacedAliasIdentifier, Func<SyntaxNode, bool>? expandInsideNode, bool expandParameter, CancellationToken cancellationToken);
        public abstract SyntaxToken Expand(SyntaxToken token, SemanticModel semanticModel, Func<SyntaxNode, bool>? expandInsideNode, CancellationToken cancellationToken);
 
        public async Task<Document> ReduceAsync(
            Document document,
            ImmutableArray<TextSpan> spans,
            SimplifierOptions options,
            ImmutableArray<AbstractReducer> reducers = default,
            CancellationToken cancellationToken = default)
        {
            using (Logger.LogBlock(FunctionId.Simplifier_ReduceAsync, cancellationToken))
            {
                var spanList = spans.NullToEmpty();
 
                // we have no span
                if (!spanList.Any())
                {
                    return document;
                }
 
                var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
 
                // Chaining of the Speculative SemanticModel (i.e. Generating a speculative SemanticModel from an existing Speculative SemanticModel) is not supported
                // Hence make sure we always start working off of the actual SemanticModel instead of a speculative SemanticModel.
                Debug.Assert(!semanticModel.IsSpeculativeSemanticModel);
 
                var root = await semanticModel.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
 
#if DEBUG
                var originalDocHasErrors = await document.HasAnyErrorsAsync(cancellationToken).ConfigureAwait(false);
#endif
 
                var reduced = await this.ReduceCoreAsync(document, spanList, options, reducers, cancellationToken).ConfigureAwait(false);
 
                if (reduced != document)
                {
#if DEBUG
                    if (!originalDocHasErrors)
                    {
                        await reduced.VerifyNoErrorsAsync("Error introduced by Simplification Service", cancellationToken).ConfigureAwait(false);
                    }
#endif
                }
 
                return reduced;
            }
        }
 
        private async Task<Document> ReduceCoreAsync(
            Document document,
            ImmutableArray<TextSpan> spans,
            SimplifierOptions options,
            ImmutableArray<AbstractReducer> reducers,
            CancellationToken cancellationToken)
        {
            // Create a simple interval tree for simplification spans.
            var spansTree = new SimpleIntervalTree<TextSpan, TextSpanIntervalIntrospector>(new TextSpanIntervalIntrospector(), spans);
 
            bool isNodeOrTokenOutsideSimplifySpans(SyntaxNodeOrToken nodeOrToken)
                => !spansTree.HasIntervalThatOverlapsWith(nodeOrToken.FullSpan.Start, nodeOrToken.FullSpan.Length);
 
            var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
            var root = await semanticModel.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
 
            // prep namespace imports marked for simplification 
            var removeIfUnusedAnnotation = new SyntaxAnnotation();
            var originalRoot = root;
            root = PrepareNamespaceImportsForRemovalIfUnused(document, root, removeIfUnusedAnnotation, isNodeOrTokenOutsideSimplifySpans);
            var hasImportsToSimplify = root != originalRoot;
 
            if (hasImportsToSimplify)
            {
                document = document.WithSyntaxRoot(root);
                semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
                root = await semanticModel.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
            }
 
            // Get the list of syntax nodes and tokens that need to be reduced.
            var nodesAndTokensToReduce = this.GetNodesAndTokensToReduce(root, isNodeOrTokenOutsideSimplifySpans);
 
            if (nodesAndTokensToReduce.Any())
            {
                if (reducers.IsDefault)
                {
                    reducers = _reducers;
                }
 
                // Take out any reducers that don't even apply with the current
                // set of users options. i.e. no point running 'reduce to var'
                // if the user doesn't have the 'var' preference set.
                reducers = reducers.WhereAsArray(r => r.IsApplicable(options));
 
                var reducedNodesMap = new ConcurrentDictionary<SyntaxNode, SyntaxNode>();
                var reducedTokensMap = new ConcurrentDictionary<SyntaxToken, SyntaxToken>();
 
                // Reduce all the nodesAndTokensToReduce using the given reducers/rewriters and
                // store the reduced nodes and/or tokens in the reduced nodes/tokens maps.
                // Note that this method doesn't update the original syntax tree.
                await this.ReduceAsync(document, root, nodesAndTokensToReduce, reducers, options, semanticModel, reducedNodesMap, reducedTokensMap, cancellationToken).ConfigureAwait(false);
 
                if (reducedNodesMap.Any() || reducedTokensMap.Any())
                {
                    // Update the syntax tree with reduced nodes/tokens.
                    root = root.ReplaceSyntax(
                        nodes: reducedNodesMap.Keys,
                        computeReplacementNode: (o, n) => TransformReducedNode(reducedNodesMap[o], n),
                        tokens: reducedTokensMap.Keys,
                        computeReplacementToken: (o, n) => reducedTokensMap[o],
                        trivia: SpecializedCollections.EmptyEnumerable<SyntaxTrivia>(),
                        computeReplacementTrivia: null);
 
                    document = document.WithSyntaxRoot(root);
                }
            }
 
            if (hasImportsToSimplify)
            {
                // remove any unused namespace imports that were marked for simplification
                document = await this.RemoveUnusedNamespaceImportsAsync(document, removeIfUnusedAnnotation, cancellationToken).ConfigureAwait(false);
            }
 
            return document;
        }
 
        private async Task ReduceAsync(
            Document document,
            SyntaxNode root,
            ImmutableArray<NodeOrTokenToReduce> nodesAndTokensToReduce,
            ImmutableArray<AbstractReducer> reducers,
            SimplifierOptions options,
            SemanticModel semanticModel,
            ConcurrentDictionary<SyntaxNode, SyntaxNode> reducedNodesMap,
            ConcurrentDictionary<SyntaxToken, SyntaxToken> reducedTokensMap,
            CancellationToken cancellationToken)
        {
            // Debug flag to help processing things serially instead of parallel.
            var executeSerially = Debugger.IsAttached;
 
            Contract.ThrowIfFalse(nodesAndTokensToReduce.Any());
 
            // Reduce each node or token in the given list by running it through each reducer.
            var simplifyTasks = new Task[nodesAndTokensToReduce.Length];
            for (var i = 0; i < nodesAndTokensToReduce.Length; i++)
            {
                var nodeOrTokenToReduce = nodesAndTokensToReduce[i];
                simplifyTasks[i] = Task.Run(async () =>
                {
                    var nodeOrToken = nodeOrTokenToReduce.OriginalNodeOrToken;
                    var simplifyAllDescendants = nodeOrTokenToReduce.SimplifyAllDescendants;
                    var semanticModelForReduce = semanticModel;
                    var currentNodeOrToken = nodeOrTokenToReduce.NodeOrToken;
                    var isNode = nodeOrToken.IsNode;
 
                    foreach (var reducer in reducers)
                    {
                        cancellationToken.ThrowIfCancellationRequested();
 
                        using var rewriter = reducer.GetOrCreateRewriter();
                        rewriter.Initialize(document.Project.ParseOptions, options, cancellationToken);
 
                        do
                        {
                            if (currentNodeOrToken.SyntaxTree != semanticModelForReduce.SyntaxTree)
                            {
                                // currentNodeOrToken was simplified either by a previous reducer or
                                // a previous iteration of the current reducer.
                                // Create a speculative semantic model for the simplified node for semantic queries.
 
                                // Certain node kinds (expressions/statements) require non-null parent nodes during simplification.
                                // However, the reduced nodes haven't been parented yet, so do the required parenting using the original node's parent.
                                if (currentNodeOrToken.Parent == null &&
                                    nodeOrToken.Parent != null &&
                                    (currentNodeOrToken.IsToken ||
                                    currentNodeOrToken.AsNode() is TExpressionSyntax ||
                                    currentNodeOrToken.AsNode() is TStatementSyntax ||
                                    currentNodeOrToken.AsNode() is TCrefSyntax))
                                {
                                    var annotation = new SyntaxAnnotation();
                                    currentNodeOrToken = currentNodeOrToken.WithAdditionalAnnotations(annotation);
 
                                    var replacedParent = isNode
                                        ? nodeOrToken.Parent.ReplaceNode(nodeOrToken.AsNode()!, currentNodeOrToken.AsNode()!)
                                        : nodeOrToken.Parent.ReplaceToken(nodeOrToken.AsToken(), currentNodeOrToken.AsToken());
 
                                    currentNodeOrToken = replacedParent
                                        .ChildNodesAndTokens()
                                        .Single(c => c.HasAnnotation(annotation));
                                }
 
                                if (isNode)
                                {
                                    var currentNode = currentNodeOrToken.AsNode()!;
                                    if (this.NodeRequiresNonSpeculativeSemanticModel(nodeOrToken.AsNode()!))
                                    {
                                        // Since this node cannot be speculated, we are replacing the Document with the changes and get a new SemanticModel
                                        var marker = new SyntaxAnnotation();
                                        var newRoot = root.ReplaceNode(nodeOrToken.AsNode()!, currentNode.WithAdditionalAnnotations(marker));
                                        var newDocument = document.WithSyntaxRoot(newRoot);
                                        semanticModelForReduce = await newDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
                                        newRoot = await semanticModelForReduce.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
                                        currentNodeOrToken = newRoot.DescendantNodes().Single(c => c.HasAnnotation(marker));
                                    }
                                    else
                                    {
                                        // Create speculative semantic model for simplified node.
                                        semanticModelForReduce = GetSpeculativeSemanticModel(ref currentNode, semanticModel, nodeOrToken.AsNode()!);
                                        currentNodeOrToken = currentNode;
                                    }
                                }
                            }
 
                            // Reduce the current node or token.
                            currentNodeOrToken = rewriter.VisitNodeOrToken(currentNodeOrToken, semanticModelForReduce, simplifyAllDescendants);
                        }
                        while (rewriter.HasMoreWork);
                    }
 
                    // If nodeOrToken was simplified, add it to the appropriate dictionary of replaced nodes/tokens.
                    if (currentNodeOrToken != nodeOrToken)
                    {
                        if (isNode)
                        {
                            reducedNodesMap[nodeOrToken.AsNode()!] = currentNodeOrToken.AsNode()!;
                        }
                        else
                        {
                            reducedTokensMap[nodeOrToken.AsToken()] = currentNodeOrToken.AsToken();
                        }
                    }
                }, cancellationToken);
 
                if (executeSerially)
                    await simplifyTasks[i].ConfigureAwait(false);
            }
 
            await Task.WhenAll(simplifyTasks).ConfigureAwait(false);
        }
 
        // find any namespace imports / using directives marked for simplification in the specified spans
        // and add removeIfUnused annotation
        private static SyntaxNode PrepareNamespaceImportsForRemovalIfUnused(
            Document document,
            SyntaxNode root,
            SyntaxAnnotation removeIfUnusedAnnotation,
            Func<SyntaxNodeOrToken, bool> isNodeOrTokenOutsideSimplifySpan)
        {
            var gen = SyntaxGenerator.GetGenerator(document);
 
            var importsToSimplify = root.DescendantNodes().Where(n =>
                !isNodeOrTokenOutsideSimplifySpan(n)
                && gen.GetDeclarationKind(n) == DeclarationKind.NamespaceImport
                && n.HasAnnotation(Simplifier.Annotation));
 
            return root.ReplaceNodes(importsToSimplify, (o, r) => r.WithAdditionalAnnotations(removeIfUnusedAnnotation));
        }
 
        private async Task<Document> RemoveUnusedNamespaceImportsAsync(
            Document document,
            SyntaxAnnotation removeIfUnusedAnnotation,
            CancellationToken cancellationToken)
        {
            var model = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
            var root = await model.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
            var addedImports = root.GetAnnotatedNodes(removeIfUnusedAnnotation);
            var unusedImports = new HashSet<SyntaxNode>();
            this.GetUnusedNamespaceImports(model, unusedImports, cancellationToken);
 
            // only remove the unused imports that we added
            unusedImports.IntersectWith(addedImports);
 
            if (unusedImports.Count > 0)
            {
                var gen = SyntaxGenerator.GetGenerator(document);
                var newRoot = gen.RemoveNodes(root, unusedImports);
                return document.WithSyntaxRoot(newRoot);
            }
            else
            {
                return document;
            }
        }
 
        protected abstract void GetUnusedNamespaceImports(SemanticModel model, HashSet<SyntaxNode> namespaceImports, CancellationToken cancellationToken);
    }
}