What
A pattern match on a Boolean compiles to a three-way if with a synthesized, unreachable default:
if a then
return "true"
elseif false == a then
return "false"
else
return error("No patterns matched")
end
a is a Boolean, so true/false already cover it. The elseif false == a guard is always taken once reached, and the else default is dead. The whole thing should reduce to:
if a then return "true" else return "false" end
The canonical source is the Show Boolean instance, so this is the shape every inlined show or logShow of a boolean produces. Since #180 inlines the boolean show dictionary at call sites, it now appears more often.
Where
The idiom (signature elseif false ==) occurs 16 times across 6 goldens:
Golden.RecursiveBindings.Test: 4
Golden.GenericEqTwoTypes.Test: 4
Golden.Primops.Test: 3
Golden.BugListGenericEq.Test: 3
Golden.StringCodePoints.Test: 1
Golden.CharLiterals.Test: 1
Why it is not folded today
The IR shape is:
IfThenElse c "true" (IfThenElse (Eq (LitBool False) c) "false" (Exception "No patterns matched"))
None of the existing if/boolean rules apply:
So the optimizer has no handle on the shape and prints it verbatim.
Proposed transformations
Two pieces, both reusing the existing folds.
- Complete the boolean-equality fold in
constantFolding (cheap and independently useful):
Eq _ (LiteralBool _ False) b → Just (primNot b) -- b : Bool, see Note [IR is assumed well-typed]
Eq _ b (LiteralBool _ False) → Just (primNot b)
Eq _ b (LiteralBool _ True) → Just b -- mirror the existing left-literal case
- Propagate a branch condition's known value into its branches, the Boolean sibling of the existing
propagateKnownCtorThroughLet: inside if c then t else e, c is True throughout t and False throughout e. Substituting c := False into the else branch lets the existing folds finish the job:
if c then "true" else (if (False == c) then "false" else Exc)
-- propagation: in the else branch, c is known False
if c then "true" else (if (False == False) then "false" else Exc)
-- constantFolding (existing): Eq (LitBool False) (LitBool False) → True
if c then "true" else (if True then "false" else Exc)
-- removeUnreachableElseBranch (existing)
if c then "true" else "false"
A smaller, more local alternative to (2) is a dominated-condition rule, if c then t else (if c then _dead else e) → if c then t else e, which composes with the existing flipNegatedIf (after fold (1) turns false == c into not c) to the same result. Either works. Propagation is the more general choice and matches the constructor propagation the optimizer already does; the dominated-condition rule is the lower-risk one. Both need c to be a variable so re-testing it is free and sound.
Test
Work test-first. A focused Optimizer unit test on the IR shape above proves it reduces to the two-way if, including a guard that a genuinely partial match keeps its default. The 16 goldens listed also shrink, since the elseif false == guard and the error default disappear. The eval goldens do not move, which is the semantic safety net: runtime output is identical, only the shape changes.
Out of scope
Constructor-tag matches on closed sums produce a similar dead error("No patterns matched") default (the other ~48 of the 64 such defaults in the corpus, for example genericEqPrime in Golden.BugListGenericEq.Test). Eliminating those is a separate, larger change: proving a tag chain exhaustive needs the complete constructor set per type, which the IR and UberModule do not carry today (a Ctor node knows its own TyName and CtorName, not its siblings). That is a plumbing feature rather than a local rewrite, tracked on its own.
Payoff
Modest per site but recurring. This is the shape of every show or logShow of a boolean, so it turns up in real code as well as across the eval-heavy goldens, and it removes a dead error call from otherwise-total code.
What
A pattern match on a
Booleancompiles to a three-wayifwith a synthesized, unreachable default:ais aBoolean, sotrue/falsealready cover it. Theelseif false == aguard is always taken once reached, and theelsedefault is dead. The whole thing should reduce to:The canonical source is the
Show Booleaninstance, so this is the shape every inlinedshoworlogShowof a boolean produces. Since #180 inlines the booleanshowdictionary at call sites, it now appears more often.Where
The idiom (signature
elseif false ==) occurs 16 times across 6 goldens:Golden.RecursiveBindings.Test: 4Golden.GenericEqTwoTypes.Test: 4Golden.Primops.Test: 3Golden.BugListGenericEq.Test: 3Golden.StringCodePoints.Test: 1Golden.CharLiterals.Test: 1Why it is not folded today
The IR shape is:
None of the existing if/boolean rules apply:
reduceBooleanIf(Lift the pure subset of foreign values into the IR: primop nodes and an allowlist-driven lifter #178) collapsesif p then True else False, but here the branches are the strings"true"/"false", not boolean literals.constantFoldingfoldsEq True b → bbut has noEq False b → not bcounterpart, sofalse == cstays opaque.removeUnreachableThenBranch/removeUnreachableElseBranchneed a literalif True/if False.So the optimizer has no handle on the shape and prints it verbatim.
Proposed transformations
Two pieces, both reusing the existing folds.
constantFolding(cheap and independently useful):propagateKnownCtorThroughLet: insideif c then t else e,cisTruethroughouttandFalsethroughoute. Substitutingc := Falseinto the else branch lets the existing folds finish the job:A smaller, more local alternative to (2) is a dominated-condition rule,
if c then t else (if c then _dead else e) → if c then t else e, which composes with the existingflipNegatedIf(after fold (1) turnsfalse == cintonot c) to the same result. Either works. Propagation is the more general choice and matches the constructor propagation the optimizer already does; the dominated-condition rule is the lower-risk one. Both needcto be a variable so re-testing it is free and sound.Test
Work test-first. A focused
Optimizerunit test on the IR shape above proves it reduces to the two-way if, including a guard that a genuinely partial match keeps its default. The 16 goldens listed also shrink, since theelseif false ==guard and theerrordefault disappear. The eval goldens do not move, which is the semantic safety net: runtime output is identical, only the shape changes.Out of scope
Constructor-tag matches on closed sums produce a similar dead
error("No patterns matched")default (the other ~48 of the 64 such defaults in the corpus, for examplegenericEqPrimeinGolden.BugListGenericEq.Test). Eliminating those is a separate, larger change: proving a tag chain exhaustive needs the complete constructor set per type, which the IR andUberModuledo not carry today (aCtornode knows its ownTyNameandCtorName, not its siblings). That is a plumbing feature rather than a local rewrite, tracked on its own.Payoff
Modest per site but recurring. This is the shape of every
showorlogShowof a boolean, so it turns up in real code as well as across the eval-heavy goldens, and it removes a deaderrorcall from otherwise-total code.