File: Debugging\CSharpProximityExpressionsService_ExpressionTermCollector.cs
Web Access
Project: src\src\Features\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Features.csproj (Microsoft.CodeAnalysis.CSharp.Features)
// 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.
 
#nullable disable
 
using System.Collections.Generic;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.Debugging;
 
internal sealed partial class CSharpProximityExpressionsService
{
    private static string ConvertToString(ExpressionSyntax expression)
    {
        var converted = expression.ConvertToSingleLine();
        return converted.ToString();
    }
 
    private static void AddExpressionTerms(ExpressionSyntax expression, IList<string> terms)
    {
        // Check here rather than at all the call sites...
        if (expression == null)
        {
            return;
        }
 
        // Collect terms from this expression, which returns flags indicating the validity
        // of this expression as a whole.
        var expressionType = ExpressionType.Invalid;
        AddSubExpressionTerms(expression, terms, ref expressionType);
 
        AddIfValidTerm(expression, expressionType, terms);
    }
 
    private static void AddIfValidTerm(ExpressionSyntax expression, ExpressionType type, IList<string> terms)
    {
        if (IsValidTerm(type))
        {
            // If this expression identified itself as a valid term, add it to the
            // term table
            terms.Add(ConvertToString(expression));
        }
    }
 
    private static bool IsValidTerm(ExpressionType type)
        => (type & ExpressionType.ValidTerm) == ExpressionType.ValidTerm;
 
    private static bool IsValidExpression(ExpressionType type)
        => (type & ExpressionType.ValidExpression) == ExpressionType.ValidExpression;
 
    private static void AddSubExpressionTerms(ExpressionSyntax expression, IList<string> terms, ref ExpressionType expressionType)
    {
        // Check here rather than at all the call sites...
        if (expression == null)
        {
            return;
        }
 
        switch (expression.Kind())
        {
            case SyntaxKind.ThisExpression:
            case SyntaxKind.BaseExpression:
                // an op term is ok if it's a "this" or "base" op it allows us to see
                // "this.goo" in the autos window note: it's not a VALIDTERM since we don't
                // want "this" showing up in the auto's window twice.
                expressionType = ExpressionType.ValidExpression;
                return;
 
            case SyntaxKind.IdentifierName:
                // Name nodes are always valid terms
                expressionType = ExpressionType.ValidTerm;
                return;
 
            case SyntaxKind.CharacterLiteralExpression:
            case SyntaxKind.FalseLiteralExpression:
            case SyntaxKind.NullLiteralExpression:
            case SyntaxKind.NumericLiteralExpression:
            case SyntaxKind.StringLiteralExpression:
            case SyntaxKind.TrueLiteralExpression:
                // Constants can make up a valid term, but we don't consider them valid
                // terms themselves (since we don't want them to show up in the autos window
                // on their own).
                expressionType = ExpressionType.ValidExpression;
                return;
 
            case SyntaxKind.CastExpression:
                AddCastExpressionTerms((CastExpressionSyntax)expression, terms, ref expressionType);
                return;
 
            case SyntaxKind.SimpleMemberAccessExpression:
            case SyntaxKind.PointerMemberAccessExpression:
                AddMemberAccessExpressionTerms((MemberAccessExpressionSyntax)expression, terms, ref expressionType);
                return;
 
            case SyntaxKind.ObjectCreationExpression:
                AddObjectCreationExpressionTerms((ObjectCreationExpressionSyntax)expression, terms, ref expressionType);
                return;
 
            case SyntaxKind.ArrayCreationExpression:
                AddArrayCreationExpressionTerms((ArrayCreationExpressionSyntax)expression, terms, ref expressionType);
                return;
 
            case SyntaxKind.InvocationExpression:
                AddInvocationExpressionTerms((InvocationExpressionSyntax)expression, terms, ref expressionType);
                return;
        }
 
        // +, -, ++, --, !, etc.
        //
        // This is a valid expression if it doesn't have obvious side effects (i.e. ++, --)
        if (expression is PrefixUnaryExpressionSyntax prefixUnary)
        {
            AddPrefixUnaryExpressionTerms(prefixUnary, terms, ref expressionType);
            return;
        }
 
        if (expression is AwaitExpressionSyntax awaitExpression)
        {
            AddAwaitExpressionTerms(awaitExpression, terms, ref expressionType);
            return;
        }
 
        if (expression is PostfixUnaryExpressionSyntax postfixExpression)
        {
            AddPostfixUnaryExpressionTerms(postfixExpression, terms, ref expressionType);
            return;
        }
 
        if (expression is BinaryExpressionSyntax binaryExpression)
        {
            AddBinaryExpressionTerms(expression, binaryExpression.Left, binaryExpression.Right, terms, ref expressionType);
            return;
        }
 
        if (expression is AssignmentExpressionSyntax assignmentExpression)
        {
            AddBinaryExpressionTerms(expression, assignmentExpression.Left, assignmentExpression.Right, terms, ref expressionType);
            return;
        }
 
        if (expression is ConditionalExpressionSyntax conditional)
        {
            AddConditionalExpressionTerms(conditional, terms, ref expressionType);
            return;
        }
 
        if (expression is ParenthesizedExpressionSyntax parenthesizedExpression)
        {
            AddSubExpressionTerms(parenthesizedExpression.Expression, terms, ref expressionType);
        }
 
        expressionType = ExpressionType.Invalid;
    }
 
    private static void AddCastExpressionTerms(CastExpressionSyntax castExpression, IList<string> terms, ref ExpressionType expressionType)
    {
        // For a cast, just add the nested expression.  Note: this is technically
        // unsafe as the cast *may* have side effects.  However, in practice this is
        // extremely rare, so we allow for this since it's ok in the common case.
 
        var flags = ExpressionType.Invalid;
 
        // Ask our subexpression for terms
        AddSubExpressionTerms(castExpression.Expression, terms, ref flags);
 
        // Is our expression a valid term?
        AddIfValidTerm(castExpression.Expression, flags, terms);
 
        // If the subexpression is a valid term, so is the cast expression
        expressionType = flags;
    }
 
    private static void AddMemberAccessExpressionTerms(MemberAccessExpressionSyntax memberAccessExpression, IList<string> terms, ref ExpressionType expressionType)
    {
        var flags = ExpressionType.Invalid;
 
        // These operators always have a RHS of a name node, which we know would
        // "claim" to be a valid term, but is not valid without the LHS present.
        // So, we don't bother collecting anything from the RHS...
        AddSubExpressionTerms(memberAccessExpression.Expression, terms, ref flags);
 
        // If the LHS says it's a valid term, then we add it ONLY if our PARENT
        // is NOT another dot/arrow.  This allows the expression 'a.b.c.d' to
        // add both 'a.b.c.d' and 'a.b.c', but not 'a.b' and 'a'.
        if (IsValidTerm(flags) &&
            memberAccessExpression.Parent?.Kind() is not SyntaxKind.SimpleMemberAccessExpression and not SyntaxKind.PointerMemberAccessExpression)
        {
            terms.Add(ConvertToString(memberAccessExpression.Expression));
        }
 
        // And this expression itself is a valid term if the LHS is a valid
        // expression, and its PARENT is not an invocation.
        if (IsValidExpression(flags) &&
            !memberAccessExpression.IsParentKind(SyntaxKind.InvocationExpression))
        {
            expressionType = ExpressionType.ValidTerm;
        }
        else
        {
            expressionType = ExpressionType.ValidExpression;
        }
    }
 
    private static void AddObjectCreationExpressionTerms(ObjectCreationExpressionSyntax objectionCreationExpression, IList<string> terms, ref ExpressionType expressionType)
    {
        // Object creation can *definitely* cause side effects.  So we initially
        // mark this as something invalid.  We allow it as a valid expr if all
        // the sub arguments are valid terms.
        expressionType = ExpressionType.Invalid;
 
        if (objectionCreationExpression.ArgumentList != null)
        {
            var flags = ExpressionType.Invalid;
            AddArgumentTerms(objectionCreationExpression.ArgumentList, terms, ref flags);
 
            // If all arguments are terms, then this is possibly a valid expr that can be used
            // somewhere higher in the stack.
            if (IsValidTerm(flags))
            {
                expressionType = ExpressionType.ValidExpression;
            }
        }
    }
 
    private static void AddArrayCreationExpressionTerms(
        ArrayCreationExpressionSyntax arrayCreationExpression,
        IList<string> terms,
        ref ExpressionType expressionType)
    {
        var validTerm = true;
 
        if (arrayCreationExpression.Initializer != null)
        {
            var flags = ExpressionType.Invalid;
            arrayCreationExpression.Initializer.Expressions.Do(e => AddSubExpressionTerms(e, terms, ref flags));
 
            validTerm &= IsValidTerm(flags);
        }
 
        if (validTerm)
        {
            expressionType = ExpressionType.ValidExpression;
        }
        else
        {
            expressionType = ExpressionType.Invalid;
        }
    }
 
    private static void AddInvocationExpressionTerms(InvocationExpressionSyntax invocationExpression, IList<string> terms, ref ExpressionType expressionType)
    {
#pragma warning disable IDE0059 // Unnecessary assignment of a value
        // Invocations definitely have side effects.  So we assume this
        // is invalid initially;
        expressionType = ExpressionType.Invalid;
#pragma warning restore IDE0059 // Unnecessary assignment of a value
        ExpressionType leftFlags = ExpressionType.Invalid, rightFlags = ExpressionType.Invalid;
 
        AddSubExpressionTerms(invocationExpression.Expression, terms, ref leftFlags);
        AddArgumentTerms(invocationExpression.ArgumentList, terms, ref rightFlags);
 
        AddIfValidTerm(invocationExpression.Expression, leftFlags, terms);
 
        // We're valid if both children are...
        expressionType = (leftFlags & rightFlags) & ExpressionType.ValidExpression;
    }
 
    private static void AddPrefixUnaryExpressionTerms(PrefixUnaryExpressionSyntax prefixUnaryExpression, IList<string> terms, ref ExpressionType expressionType)
    {
        expressionType = ExpressionType.Invalid;
        var flags = ExpressionType.Invalid;
 
        // Ask our subexpression for terms
        AddSubExpressionTerms(prefixUnaryExpression.Operand, terms, ref flags);
 
        // Is our expression a valid term?
        AddIfValidTerm(prefixUnaryExpression.Operand, flags, terms);
 
        if (prefixUnaryExpression.Kind() is SyntaxKind.LogicalNotExpression or SyntaxKind.BitwiseNotExpression or SyntaxKind.UnaryMinusExpression or SyntaxKind.UnaryPlusExpression)
        {
            // We're a valid expression if our subexpression is...
            expressionType = flags & ExpressionType.ValidExpression;
        }
    }
 
    private static void AddAwaitExpressionTerms(AwaitExpressionSyntax awaitExpression, IList<string> terms, ref ExpressionType expressionType)
    {
        expressionType = ExpressionType.Invalid;
        var flags = ExpressionType.Invalid;
 
        // Ask our subexpression for terms
        AddSubExpressionTerms(awaitExpression.Expression, terms, ref flags);
 
        // Is our expression a valid term?
        AddIfValidTerm(awaitExpression.Expression, flags, terms);
    }
 
    private static void AddPostfixUnaryExpressionTerms(PostfixUnaryExpressionSyntax postfixUnaryExpression, IList<string> terms, ref ExpressionType expressionType)
    {
        // ++ and -- are the only postfix operators.  Since they always have side
        // effects, we never consider this an expression.
        expressionType = ExpressionType.Invalid;
 
        var flags = ExpressionType.Invalid;
 
        // Ask our subexpression for terms
        AddSubExpressionTerms(postfixUnaryExpression.Operand, terms, ref flags);
 
        // Is our expression a valid term?
        AddIfValidTerm(postfixUnaryExpression.Operand, flags, terms);
    }
 
    private static void AddConditionalExpressionTerms(ConditionalExpressionSyntax conditionalExpression, IList<string> terms, ref ExpressionType expressionType)
    {
        ExpressionType conditionFlags = ExpressionType.Invalid, trueFlags = ExpressionType.Invalid, falseFlags = ExpressionType.Invalid;
 
        AddSubExpressionTerms(conditionalExpression.Condition, terms, ref conditionFlags);
        AddSubExpressionTerms(conditionalExpression.WhenTrue, terms, ref trueFlags);
        AddSubExpressionTerms(conditionalExpression.WhenFalse, terms, ref falseFlags);
 
        AddIfValidTerm(conditionalExpression.Condition, conditionFlags, terms);
        AddIfValidTerm(conditionalExpression.WhenTrue, trueFlags, terms);
        AddIfValidTerm(conditionalExpression.WhenFalse, falseFlags, terms);
 
        // We're valid if all children are...
        expressionType = (conditionFlags & trueFlags & falseFlags) & ExpressionType.ValidExpression;
    }
 
    private static void AddBinaryExpressionTerms(ExpressionSyntax binaryExpression, ExpressionSyntax left, ExpressionSyntax right, IList<string> terms, ref ExpressionType expressionType)
    {
        ExpressionType leftFlags = ExpressionType.Invalid, rightFlags = ExpressionType.Invalid;
 
        AddSubExpressionTerms(left, terms, ref leftFlags);
        AddSubExpressionTerms(right, terms, ref rightFlags);
 
        if (IsValidTerm(leftFlags))
        {
            terms.Add(ConvertToString(left));
        }
 
        if (IsValidTerm(rightFlags))
        {
            terms.Add(ConvertToString(right));
        }
 
        // Many sorts of binops (like +=) will definitely have side effects.  We only
        // consider this valid if it's a simple expression like +, -, etc.
 
        switch (binaryExpression.Kind())
        {
            case SyntaxKind.AddExpression:
            case SyntaxKind.SubtractExpression:
            case SyntaxKind.MultiplyExpression:
            case SyntaxKind.DivideExpression:
            case SyntaxKind.ModuloExpression:
            case SyntaxKind.LeftShiftExpression:
            case SyntaxKind.RightShiftExpression:
            case SyntaxKind.LogicalOrExpression:
            case SyntaxKind.LogicalAndExpression:
            case SyntaxKind.BitwiseOrExpression:
            case SyntaxKind.BitwiseAndExpression:
            case SyntaxKind.ExclusiveOrExpression:
            case SyntaxKind.EqualsExpression:
            case SyntaxKind.NotEqualsExpression:
            case SyntaxKind.LessThanExpression:
            case SyntaxKind.LessThanOrEqualExpression:
            case SyntaxKind.GreaterThanExpression:
            case SyntaxKind.GreaterThanOrEqualExpression:
            case SyntaxKind.IsExpression:
            case SyntaxKind.AsExpression:
            case SyntaxKind.CoalesceExpression:
                // We're valid if both children are...
                expressionType = (leftFlags & rightFlags) & ExpressionType.ValidExpression;
                return;
 
            default:
                expressionType = ExpressionType.Invalid;
                return;
        }
    }
 
    private static void AddArgumentTerms(ArgumentListSyntax argumentList, IList<string> terms, ref ExpressionType expressionType)
    {
        var validExpr = true;
        var validTerm = true;
 
        // Process the list of expressions.  This is probably a list of
        // arguments to a function call(or a list of array index expressions)
        foreach (var arg in argumentList.Arguments)
        {
            var flags = ExpressionType.Invalid;
 
            AddSubExpressionTerms(arg.Expression, terms, ref flags);
            if (IsValidTerm(flags))
            {
                terms.Add(ConvertToString(arg.Expression));
            }
 
            validExpr &= IsValidExpression(flags);
            validTerm &= IsValidTerm(flags);
        }
 
        // We're never a valid term if all arguments were valid terms.  If not, we're a valid
        // expression if all arguments where.  Otherwise, we're just invalid.
        expressionType = validTerm
            ? ExpressionType.ValidTerm
            : validExpr
                ? ExpressionType.ValidExpression : ExpressionType.Invalid;
    }
}