From 753f1d2481a26ad12c2b621632f9bcce4fbf4c04 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Mon, 20 Jul 2026 21:40:27 +0200 Subject: [PATCH] Fix #1924: don't undo the compiler's &&-to-& optimization ExpressionBuilder printed bitwise & / | on booleans as && / || whenever the right-hand side was pure, but Roslyn lowers && / || to & / | only when the right operand is a bare local or parameter read (LocalRewriter.MakeBinaryOperator, unchanged since 2014). Shapes like (c == 'a') | (c == 'b') can therefore only originate from a bitwise source operator, yet were shown as short-circuiting. Per the discussion in #1545, show the operator the IL actually uses instead of guessing the source form: the reversal is dropped entirely, so Roslyn-compiled "a && b" now decompiles to "a & b", which recompiles to the same IL. Assisted-by: Claude:claude-fable-5:Claude Code --- .../TestCases/Pretty/ShortCircuit.cs | 18 +++++++++++++----- .../TestCases/Pretty/Switch.cs | 8 ++++---- .../CSharp/ExpressionBuilder.cs | 16 ---------------- 3 files changed, 17 insertions(+), 25 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ShortCircuit.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ShortCircuit.cs index 9516ad3cf..d2193890b 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ShortCircuit.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ShortCircuit.cs @@ -326,13 +326,21 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } #endif - public void PreferLogicalToBitwise(bool a, bool b, int i, float f) + public void BitwiseBooleanOperators(bool a, bool b, int i, float f) { - B(a && b); + B(a & b); + B(a & (i == 1)); + B((i == 1) & a); + B((i > i - 3) & a); + B((f < 0.1f) & a); + B(a | b); B(a && i == 1); - B(i == 1 && a); - B(i > i - 3 && a); - B(f < 0.1f && a); + B(a || i == 1); + } + + public bool BitwiseOrWithComparisons(char c) + { + return (c == 'a') | (c == 'b'); } } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.cs index 2fac42565..a5e5a3bbb 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.cs @@ -1282,7 +1282,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty #if ROSLYN || OPT public static void SingleIf1(int i, bool a) { - if (i == 1 || (i == 2 && a)) + if (i == 1 || ((i == 2) & a)) { Console.WriteLine(1); } @@ -1292,7 +1292,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public static void SingleIf2(int i, bool a, bool b) { - if (i == 1 || (i == 2 && a) || (i == 3 && b)) + if (i == 1 || ((i == 2) & a) || ((i == 3) & b)) { Console.WriteLine(1); } @@ -1301,7 +1301,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public static void SingleIf3(int i, bool a, bool b) { - if (a || i == 1 || (i == 2 && b)) + if (a || i == 1 || ((i == 2) & b)) { Console.WriteLine(1); } @@ -1310,7 +1310,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public static void SingleIf4(int i, bool a) { - if (i == 1 || i == 2 || (i != 3 && a) || i != 4) + if (i == 1 || i == 2 || ((i != 3) & a) || i != 4) { Console.WriteLine(1); } diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index f2504ac9c..ddd5da25b 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -1650,22 +1650,6 @@ namespace ICSharpCode.Decompiler.CSharp right.ResolveResult)); } } - if (op.IsBitwise() - && left.Type.IsKnownType(KnownTypeCode.Boolean) - && right.Type.IsKnownType(KnownTypeCode.Boolean) - && SemanticHelper.IsPure(inst.Right.Flags)) - { - // Undo the C# compiler's optimization of "a && b" to "a & b". - if (op == BinaryOperatorType.BitwiseAnd) - { - op = BinaryOperatorType.ConditionalAnd; - } - else if (op == BinaryOperatorType.BitwiseOr) - { - op = BinaryOperatorType.ConditionalOr; - } - } - if (op.IsBitwise() && (left.Type.Kind == TypeKind.Enum || right.Type.Kind == TypeKind.Enum)) { left = AdjustConstantExpressionToType(left, right.Type);