Skip to content

Collapse Boolean pattern matches (including inlined show) to a two-way if #223

Description

@Unisay

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.

  1. 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
  1. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    OptimisationA Compiler Optimisationarea: irIR / optimizer / DCE / inliner

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions