From 5fc78a96c0ec5b8facbc83d1268b064e15f7af43 Mon Sep 17 00:00:00 2001 From: Rich Lander Date: Thu, 18 Jun 2026 07:08:09 -0700 Subject: [PATCH] Collapse dual-constant guard-returns to the bare condition, not `c && true` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `FoldGuardReturn` rewrites `if (c) return A; return B;` into a short-circuit boolean when either arm is a bool constant. When BOTH arms are constants it still ran the generic `tail`-constant formula, appending the `then` literal as a dead term: `if (a > 0) { if (b > 0) return true; } return false;` folded to `return a > 0 && b > 0 && true;` (the `BothPositive` fixture), and the dual `if (c) return false; return true;` to `return !c || false;`. When the two arms are opposite bool constants the condition itself is the result — `if (c) return true; return false;` is `return c;` and the dual is `return !c;` — so special-case that ahead of the generic fold and reuse the detached condition directly (no extra term, condition still evaluated, so sound even if it has side effects). Equal-constant arms (dead `if (c) return true; return true;`) are left to the generic path; they do not arise in compiled code. This is an output-fidelity fix: `BothPositive` already diverged from its fixture's nested-if-to-literal IL (an intentional, idiomatic boolean-fold over-render, like the IsNullOrEmpty corpus form), and stays on the compile-back docket as before — but now renders the idiomatic `return a > 0 && b > 0;` instead of the noisy `&& true`. Compile-back fixture counts unchanged (exact 157, docket 8, recompile-fail 537; System.Linq unchanged). Decompiler tests 440 -> 441; main suite 1140 green. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../IrImporterTests.cs | 14 ++++++++++++++ .../Pipeline/Passes/BooleanFoldingPass.cs | 10 +++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/ILInspector.Decompiler.Tests/IrImporterTests.cs b/src/ILInspector.Decompiler.Tests/IrImporterTests.cs index 22b8f7af..b3071d9d 100644 --- a/src/ILInspector.Decompiler.Tests/IrImporterTests.cs +++ b/src/ILInspector.Decompiler.Tests/IrImporterTests.cs @@ -1581,6 +1581,20 @@ public void BooleanFolding_TernarySource_PerConfigShape() #endif } + [Fact] + public void BooleanFolding_GuardReturn_BothConstantArms_CollapseToCondition() + { + // if (a > 0) { if (b > 0) return true; } return false; folds the nested + // guards to one condition, then the dual-constant return to the bare + // condition — not a dead `a > 0 && b > 0 && true` (the true arm is the + // result, not an extra && term). + using var source = MetadataSource.Open(typeof(CfgSampleClass).Assembly.Location); + string output = PrintWithPasses( + typeof(CfgSampleClass).FullName!, nameof(CfgSampleClass.BothPositive), source); + + Assert.Equal("return a > 0 && b > 0;", output); + } + [Fact] public void Structuring_NestedGuards_NestAndDropGotos() { diff --git a/src/ILInspector.Decompiler/Pipeline/Passes/BooleanFoldingPass.cs b/src/ILInspector.Decompiler/Pipeline/Passes/BooleanFoldingPass.cs index cb7deeb1..4375f4d4 100644 --- a/src/ILInspector.Decompiler/Pipeline/Passes/BooleanFoldingPass.cs +++ b/src/ILInspector.Decompiler/Pipeline/Passes/BooleanFoldingPass.cs @@ -228,7 +228,15 @@ static bool FoldGuardReturn(IfStatement guard) var condition = guard.Condition; condition.Detach(); IrExpression folded; - if (tailConstant is { } tailBool) + if (thenConstant is { } thenBool && tailConstant is { } tailBool2 && thenBool != tailBool2) + { + // Both arms are opposite bool constants, so the condition itself is + // the result: if (c) return true; return false; ≡ return c; and the + // dual ≡ return !c;. The generic fold below would append the literal + // arm as a dead `c && true` / `!c || false`. + folded = thenBool ? condition : Conditions.Negate(condition); + } + else if (tailConstant is { } tailBool) { thenValue.Detach(); // if (c) return A; return true; ≡ return !c || A;