File: UseCompoundAssignmentUtilities.cs
Web Access
Project: ..\..\..\src\CodeStyle\Core\Analyzers\Microsoft.CodeAnalysis.CodeStyle.csproj (Microsoft.CodeAnalysis.CodeStyle)
// 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.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.LanguageService;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.UseCompoundAssignment
{
    internal static class UseCompoundAssignmentUtilities
    {
        internal const string Increment = nameof(Increment);
        internal const string Decrement = nameof(Decrement);
 
        public static void GenerateMaps<TSyntaxKind>(
            ImmutableArray<(TSyntaxKind exprKind, TSyntaxKind assignmentKind, TSyntaxKind tokenKind)> kinds,
            out ImmutableDictionary<TSyntaxKind, TSyntaxKind> binaryToAssignmentMap,
            out ImmutableDictionary<TSyntaxKind, TSyntaxKind> assignmentToTokenMap) where TSyntaxKind : struct
        {
            var binaryToAssignmentBuilder = ImmutableDictionary.CreateBuilder<TSyntaxKind, TSyntaxKind>();
            var assignmentToTokenBuilder = ImmutableDictionary.CreateBuilder<TSyntaxKind, TSyntaxKind>();
 
            foreach (var (exprKind, assignmentKind, tokenKind) in kinds)
            {
                binaryToAssignmentBuilder[exprKind] = assignmentKind;
                assignmentToTokenBuilder[assignmentKind] = tokenKind;
            }
 
            binaryToAssignmentMap = binaryToAssignmentBuilder.ToImmutable();
            assignmentToTokenMap = assignmentToTokenBuilder.ToImmutable();
 
            Debug.Assert(binaryToAssignmentMap.Count == assignmentToTokenMap.Count);
            Debug.Assert(binaryToAssignmentMap.Values.All(assignmentToTokenMap.ContainsKey));
        }
 
        public static bool IsSideEffectFree(
            ISyntaxFacts syntaxFacts, SyntaxNode expr, SemanticModel semanticModel, CancellationToken cancellationToken)
        {
            return IsSideEffectFreeRecurse(syntaxFacts, expr, semanticModel, isTopLevel: true, cancellationToken);
        }
 
        private static bool IsSideEffectFreeRecurse(
            ISyntaxFacts syntaxFacts, SyntaxNode expr, SemanticModel semanticModel,
            bool isTopLevel, CancellationToken cancellationToken)
        {
            if (expr == null)
            {
                return false;
            }
 
            // it basically has to be of the form "a.b.c", where all components are locals,
            // parameters or fields.  Basically, nothing that can cause arbitrary user code
            // execution when being evaluated by the compiler.
 
            if (syntaxFacts.IsThisExpression(expr) ||
                syntaxFacts.IsBaseExpression(expr))
            {
                // Referencing this/base like  this.a.b.c causes no side effects itself.
                return true;
            }
 
            if (syntaxFacts.IsIdentifierName(expr))
            {
                return IsSideEffectFreeSymbol(expr, semanticModel, isTopLevel, cancellationToken);
            }
 
            if (syntaxFacts.IsParenthesizedExpression(expr))
            {
                syntaxFacts.GetPartsOfParenthesizedExpression(expr,
                    out _, out var expression, out _);
 
                return IsSideEffectFreeRecurse(syntaxFacts, expression, semanticModel, isTopLevel, cancellationToken);
            }
 
            if (syntaxFacts.IsSimpleMemberAccessExpression(expr))
            {
                syntaxFacts.GetPartsOfMemberAccessExpression(expr,
                    out var subExpr, out _);
                return IsSideEffectFreeRecurse(syntaxFacts, subExpr, semanticModel, isTopLevel: false, cancellationToken) &&
                       IsSideEffectFreeSymbol(expr, semanticModel, isTopLevel, cancellationToken);
            }
 
            if (syntaxFacts.IsConditionalAccessExpression(expr))
            {
                syntaxFacts.GetPartsOfConditionalAccessExpression(expr,
                    out var expression, out var whenNotNull);
                return IsSideEffectFreeRecurse(syntaxFacts, expression, semanticModel, isTopLevel: false, cancellationToken) &&
                       IsSideEffectFreeRecurse(syntaxFacts, whenNotNull, semanticModel, isTopLevel: false, cancellationToken);
            }
 
            // Something we don't explicitly handle.  Assume this may have side effects.
            return false;
        }
 
        private static bool IsSideEffectFreeSymbol(
            SyntaxNode expr, SemanticModel semanticModel, bool isTopLevel, CancellationToken cancellationToken)
        {
            var symbolInfo = semanticModel.GetSymbolInfo(expr, cancellationToken);
            if (symbolInfo.CandidateSymbols.Length > 0 ||
                symbolInfo.Symbol == null)
            {
                // couldn't bind successfully, assume that this might have side-effects.
                return false;
            }
 
            var symbol = symbolInfo.Symbol;
            switch (symbol.Kind)
            {
                case SymbolKind.Namespace:
                case SymbolKind.NamedType:
                case SymbolKind.Field:
                case SymbolKind.Parameter:
                case SymbolKind.Local:
                    return true;
            }
 
            if (symbol.Kind == SymbolKind.Property && isTopLevel)
            {
                // If we have `this.Prop = this.Prop * 2`, then that's just a single read/write of
                // the prop and we can safely make that `this.Prop *= 2` (since it will still be a
                // single read/write).  However, if we had `this.prop.x = this.prop.x * 2`, then
                // that's multiple reads of `this.prop`, and it's not safe to convert that to
                // `this.prop.x *= 2` in the case where calling 'prop' may have side effects.
                //
                // Note, this doesn't apply if the property is a ref-property.  In that case, we'd
                // go from a read and a write to to just a read (and a write to it's returned ref
                // value).
                var property = (IPropertySymbol)symbol;
                if (property.RefKind == RefKind.None)
                {
                    return true;
                }
            }
 
            return false;
        }
    }
}