我正在编写一个Roslyn分析器,以使用&&||运算符替换三元运算符中布尔文字的用法。这是代码修复提供程序应该执行的操作:

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 : expr2更改为expr1 || expr2并不总是产生等效的表达式。例如,b1 ? true : b2 && b3等于b1 || (b2 && b3),但不等于b1 || b2 && b3。具体来说,只要expr2包含等于或低于||的任何运算符,就会发生这种情况。

如何检测expr2是否包含此类运算符,因此我知道何时需要加上括号,何时不需要?在Roslyn中,是否有一种特殊的API /优雅方式来实现这一目标?

最佳答案

如果您要解决的问题是确定代码修复提供程序中的生成表达式是否应包含括号,则标准解决方案是让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);
    }
}


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

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

当然,这种方法的主要优点是操作简便,并且可以大大降低代码复杂性。由于您正在重用现有的,经过测试的逻辑,因此很有可能无需编写大量测试就可以证明它是正确的。而且,如果将来的C#版本添加了其他运算符或其他语法,则很可能保持正确。

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

10-11 02:54
查看更多