c# - Roslyn:如何确定表达式是否具有优先级等于/低于特定运算符的运算符?

标签 c# .net roslyn

我正在编写一个 Roslyn 分析器,用 &&|| 运算符替换三元运算符中 bool 文字的使用。以下是代码修复提供者应该做的事情:

expr1 ? true : expr2 -> expr1 || expr2
expr1 ? false : expr2 -> !expr1 && expr2
expr1 ? expr2 : true -> !expr1 || expr2
expr1 ? expr2 : false -> expr1 && expr2

这是我的代码的相关部分:

// In MA0002Analyzer.cs
internal static bool? ValueOfBoolLiteral(ExpressionSyntax expr)
{
    switch (expr.Kind())
    {
        case SyntaxKind.TrueLiteralExpression:
            return true;
        case SyntaxKind.FalseLiteralExpression:
            return false;
    }

    if (expr is ParenthesizedExpressionSyntax parenExpr)
    {
        return ValueOfBoolLiteral(parenExpr.Expression);
    }

    return null;
}

// In MA0002CodeFixProvider.cs
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

private static Task<Document> UseLogicalOperatorAsync(Document document, ConditionalExpressionSyntax ternary, CancellationToken ct)
{
    // expr1 ? true : expr2 -> expr1 || expr2
    // expr1 ? false : expr2 -> !expr1 && expr2
    // expr1 ? expr2 : true -> !expr1 || expr2
    // expr1 ? expr2 : false -> expr1 && expr2

    async Task<Document> UseBinaryOperatorAsync(SyntaxKind operatorKind, ExpressionSyntax left, ExpressionSyntax right)
    {
        var logicalExpr = BinaryExpression(operatorKind, left, right);
        var syntaxRoot = await document.GetSyntaxRootAsync(ct);
        return document.WithSyntaxRoot(syntaxRoot.ReplaceNode(ternary, logicalExpr));
    }

    bool? literalValue = MA0002Analyzer.ValueOfBoolLiteral(ternary.WhenTrue);
    if (literalValue != null)
    {
        // ? has lower precedence than ||, so it's possible we could run into a situation where stuff in expr1 binds more
        // tightly to stuff in expr2 than before. For example, b1 ? true : b2 && b3 equals b1 || (b2 && b3), but not b1 || b2 && b3.
        // We want to prevent this by wrapping expr2 in parentheses when necessary, but only when expr2 contains operators that
        // have equal/less precendence than ||.
        ExpressionSyntax right = MaybeParenthesize(ternary.WhenFalse);

        return literalValue == true ?
            // It's never necessary to parenthesize expr1 here because boolean operators are left-associative.
            UseBinaryOperatorAsync(SyntaxKind.LogicalOrExpression, ternary.Condition, right) :
            // However, it might be necessary to parenthesize expr1 here because it is being negated.
            UseBinaryOperatorAsync(SyntaxKind.LogicalAndExpression, Negate(ternary.Condition), right);
    }

    literalValue = MA0002Analyzer.ValueOfBoolLiteral(ternary.WhenFalse);
    if (literalValue != null)
    {
        // In "b1 ? b2 : true;", calling ToFullString() on b2's node will give "b2 ". This is bc the space to the right of b2 is counted as part
        // of its trivia. Therefore, we must remove trailing trivia before moving b2 to the end of a new expression, or we'll get "!b1 || b2 ;".
        // This is not an issue for the node on the false branch of the conditional.
        ExpressionSyntax right = MaybeParenthesize(ternary.WhenTrue.WithoutTrailingTrivia());

        return literalValue == true ?
            UseBinaryOperatorAsync(SyntaxKind.LogicalOrExpression, Negate(ternary.Condition), right) :
            UseBinaryOperatorAsync(SyntaxKind.LogicalAndExpression, ternary.Condition, right);
    }

    return Task.FromResult(document);
}

private static ExpressionSyntax MaybeParenthesize(ExpressionSyntax expr)
{
    // What goes here?
}

private static ExpressionSyntax Negate(ExpressionSyntax expr)
{
    if (expr.IsKind(SyntaxKind.LogicalNotExpression))
    {
        var pue = (PrefixUnaryExpressionSyntax)expr;
        return pue.Operand;
    }

    if (expr is ParenthesizedExpressionSyntax parenExpr)
    {
        return parenExpr.WithExpression(Negate(parenExpr.Expression));
    }

    return PrefixUnaryExpression(SyntaxKind.LogicalNotExpression, expr);
}

如您所见,我一直在研究如何实现 MaybeParenthesize。问题在于,由于三元运算符的优先级低于 ||,因此从 expr1 更改? true : expr2expr1 || expr2 并不总是产生等价的表达式。例如,b1 ?真:b2 && b3 等于 b1 || (b2 && b3) 但不是 b1 || b2 && b3。具体来说,只要 expr2 包含任何优先级等于/低于 || 的运算符,就会发生这种情况。

我如何检测 expr2 是否包含此类运算符,以便我知道何时需要将其括起来,何时不需要?在 Roslyn 中是否有特殊的 API/优雅的方式来实现这一点?

最佳答案

如果您要解决的问题是决定代码修复提供程序中生成的表达式是否应该有括号,那么标准解决方案是让 Roslyn 为您决定。

这意味着您总是生成括号,但如果这样做是安全的,请让 Roslyn 再次删除它们。

我过去曾使用此扩展方法完成此操作:

public static ExpressionSyntax AddParentheses(this ExpressionSyntax expression)
{
    switch (expression.RawKind)
    {
        case (int)SyntaxKind.ParenthesizedExpression:
        case (int)SyntaxKind.IdentifierName:
        case (int)SyntaxKind.QualifiedName:
        case (int)SyntaxKind.SimpleMemberAccessExpression:
        case (int)SyntaxKind.InterpolatedStringExpression:
        case (int)SyntaxKind.NumericLiteralExpression:
        case (int)SyntaxKind.StringLiteralExpression:
        case (int)SyntaxKind.CharacterLiteralExpression:
        case (int)SyntaxKind.TrueLiteralExpression:
        case (int)SyntaxKind.FalseLiteralExpression:
        case (int)SyntaxKind.NullLiteralExpression:
            return expression;

        default:
            return SyntaxFactory
                .ParenthesizedExpression(expression)
                .WithAdditionalAnnotations(Simplifier.Annotation);
    }
}

开关和第一组情况不是必需的。它们有一些成本,但在某些场景下它们可以消除一些不必要的分配。

真正的魔力在于默认子句。 .WithAdditionalAnnotations(Simplifier.Annotation) 告诉代码修复基础结构括号可以简化,即删除,如果这样做不会改变代码的含义(在上下文)。

这种方法的主要优点当然是简单,并且可以显着降低代码复杂性。由于您正在重用现有的、经过测试的逻辑,因此它也很有可能是正确的,而无需为其编写大量测试。此外,如果 future 的 C# 版本添加额外的运算符或其他语法,它可能会保持正确。

关于c# - Roslyn:如何确定表达式是否具有优先级等于/低于特定运算符的运算符?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43314098/

相关文章:

c# - 将 id 值传递给 gridview 控件中的 LinkBut​​ton 服务器端事件

C#:Windows 7 机器的登录历史记录

.net - WPF 中的 StackPanel 与 DataGrid 与 DockPanel

c# - 窗体最大化时自动缩放子控件

c# - 将 TypeOf "List<int>"与 "System.Collections.Generic.List<int>"进行比较

c# - 如何使用 Roslyn 代码修复提供程序 API 从文档中删除 SyntaxNode 列表?

c# - .NET 如何有效监听引发的事件?

.net - 隐藏 Infragistics Winform UltraCombo 上的标题

c# - WPF/XAML : How to reference class that is not defined within any namespace

c# - 我怎样才能改进这个设计?