gleam-lang/gleam · error

Tried to reduce without 2 guards

Error message

Tried to reduce without 2 guards

What it means

Same invariant as the expression reducer but for clause guards: `do_reduce_clause_guard` requires exactly two guard operands to combine with an operator. Fewer than two indicates the guard parser's stack bookkeeping broke, so it panics.

Solutions

  1. Extract the failing `case` clause guard into a minimal repro and file a Gleam compiler bug.
  2. When patching the parser, ensure guard operators always follow two pushed guards before reduction.
  3. Work around by rewriting the guard (e.g. splitting a compound guard into simpler ones or using a `let assert` / body-level check).

Example fix

// before (suspect guard)
case x { y if > 3 -> ... }
// after
case x { y if y > 3 -> ... }
Defensive patterns

Strategy: try-catch

Try / catch

let parsed = std::panic::catch_unwind(|| parse_case(tokens));
match parsed {
    Ok(case) => case,
    _ => report_compiler_bug("Tried to reduce without 2 guards"),
}

Prevention

When it happens

Trigger: A clause-guard operator (e.g. `&&`, `||`, comparisons inside a `case` guard) is reduced while the guard stack contains fewer than two `UntypedClauseGuard` values, due to a guard-parsing bug or internals misuse.

Common situations: Gleam parser development/regressions around `case` expression guards; not normally triggerable by well-formed or even straightforwardly malformed user code.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of gleam-lang/gleam@15b07c7830 (2026-09-14). Data as JSON: /api/errors/c82aec21cf2e8d00. Report an issue: GitHub.

Appendix: source

Thrown at compiler-core/src/parse.rs:5145

/// Simple-Precedence-Parser, perform reduction for expression
fn do_reduce_expression(operator: Spanned, estack: &mut Vec<UntypedExpr>) {
    match (estack.pop(), estack.pop()) {
        (Some(right), Some(left)) => {
            let new_expression = expression_operator_reduction(operator, left, right);
            estack.push(new_expression);
        }
        _ => panic!("Tried to reduce without 2 expressions"),
    }
}

/// Simple-Precedence-Parser, perform reduction for clause guard
fn do_reduce_clause_guard(operator: Spanned, estack: &mut Vec<UntypedClauseGuard>) {
    match (estack.pop(), estack.pop()) {
        (Some(right), Some(left)) => {
            let expression = clause_guard_reduction(operator, left, right);
            estack.push(expression);
        }
        _ => panic!("Tried to reduce without 2 guards"),
    }
}

/// Simple-Precedence-Parser, perform reduction for clause guard
fn do_reduce_constant(operator: Spanned, estack: &mut Vec<UntypedConstant>) {
    match (estack.pop(), estack.pop()) {
        (Some(left), Some(right)) => {
            let new_expression = constant_binop_reduction(operator, right, left);
            estack.push(new_expression);
        }
        _ => panic!("Tried to reduce without 2 guards"),
    }
}

/// Simple-Precedence-Parser, perform reduction for bit array size expressions
fn reduce_bit_array_size((_, token, _): Spanned, experession_stack: &mut Vec<BitArraySize<()>>) {
    let operator = token_to_bit_array_size_operator(&token)
        .expect("only operator tokens are pushed onto the bit array size opstack");

View on GitHub (pinned to 15b07c7830)