gleam-lang/gleam · error

Expression not fully reduced.

Error message

Expression not fully reduced.

What it means

At the end of the expression precedence-climbing loop, the parser expects exactly one fully reduced expression on the stack. If more than one expression remains after popping the final one, the reduction invariants were violated, so it panics. The `_ => return None` arm handles genuine parse failure (returns `None` so a proper parse error is reported).

Solutions

  1. Reduce the input to a minimal snippet that triggers the panic and file a Gleam compiler bug with it.
  2. If you are modifying the parser, audit operator handlers so every `push_operand` is balanced by `do_reduce_expression` calls.
  3. As a workaround, reformulate the expression (add explicit parentheses) — though a panic here usually indicates a compiler bug, not user error.
Defensive patterns

Strategy: try-catch

Try / catch

let parsed = std::panic::catch_unwind(|| parse_expression(tokens));
match parsed {
    Ok(Some(expr)) => expr,
    _ => report_compiler_bug("Expression not fully reduced"),
}

Prevention

When it happens

Trigger: The infix expression parser's operator/operand stack bookkeeping goes out of sync — e.g. an operator handler pushes an expression without consuming one, or a bug in a new operator's precedence handling leaves two unreduced expressions stacked when the input ends.

Common situations: Gleam parser development/regressions; extremely rare for end users, since malformed input normally returns None and produces a normal parse error instead.

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/d6ef1091a5234c97. Report an issue: GitHub.

Appendix: source

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

// Higher number means higher precedence.
// All operators are left associative.

/// Simple-Precedence-Parser, handle seeing an operator or end
fn handle_operator<A>(
    next_operator: Option<(Spanned, u8)>,
    operator_stack: &mut Vec<(Spanned, u8)>,
    expression_stack: &mut Vec<A>,
    do_reduce: &impl Fn(Spanned, &mut Vec<A>),
) -> Option<A> {
    let mut next_operator = next_operator;
    loop {
        match (operator_stack.pop(), next_operator.take()) {
            (None, None) => match expression_stack.pop() {
                Some(fin) => {
                    if expression_stack.is_empty() {
                        return Some(fin);
                    } else {
                        panic!("Expression not fully reduced.")
                    }
                }
                _ => {
                    return None;
                }
            },

            (None, Some(operator)) => {
                operator_stack.push(operator);
                break;
            }

            (Some((operator, _)), None) => do_reduce(operator, expression_stack),

            (Some((left_operator, left_precedence)), Some((right_operator, right_precedence))) => {
                match left_precedence.cmp(&right_precedence) {
                    // all operators are left associative
                    Ordering::Greater | Ordering::Equal => {

View on GitHub (pinned to 15b07c7830)