BoundaryML/baml · error

c_for_after_stmt cannot accept empty input

Error message

c_for_after_stmt cannot accept empty input

What it means

Same invariant as the init statement, applied to the after-statement slot of a C-style for loop: a Rule::c_for_after_stmt pair with no inner tokens panics with 'c_for_after_stmt cannot accept empty input' inside parse_c_for_loop.

Source

Thrown at engine/baml-lib/ast/src/parser/parse_expr.rs:205

    token: Pair<'_>,
    body: ExpressionBlock,
    span: Span,
    diagnostics: &mut Diagnostics,
) -> Option<CForLoopStmt> {
    assert_correct_parser(&token, &[Rule::c_for_loop], diagnostics);

    let mut header = token.into_inner();

    let init_stmt = consume_if_rule(&mut header, Rule::c_for_init_stmt).map(|rule| {
        rule.into_inner()
            .next()
            .expect("c_for_init_stmt cannot accept empty input")
    });
    let condition = consume_if_rule(&mut header, Rule::expression);
    let after_stmt = consume_if_rule(&mut header, Rule::c_for_after_stmt).map(|rule| {
        rule.into_inner()
            .next()
            .expect("c_for_after_stmt cannot accept empty input")
    });

    let init_stmt = parse_optional_rule(init_stmt, |rule| {
        let span = diagnostics.span(rule.as_span());
        parse_statement_inner_rule(rule, span, diagnostics)
    })?
    .map(Box::new);

    let condition = parse_optional_rule(condition, |rule| parse_expression(rule, diagnostics))?;

    let after_stmt = parse_optional_rule(after_stmt, |rule| {
        let span = diagnostics.span(rule.as_span());

        match rule.as_rule() {
            Rule::block_aware_assign_stmt => {
                let mut tokens = rule.into_inner();

                let left = parse_expression(tokens.next()?, diagnostics)?;

View on GitHub (pinned to bd85ce9dee)

Solutions

  1. Rebuild the ast crate so grammar and parser come from one version.
  2. Swap .expect for ok_or_else(anyhow!) error propagation if maintaining the code.
  3. Isolate and report the triggering expression snippet upstream.

Example fix

// before
rule.into_inner().next().expect("c_for_after_stmt cannot accept empty input")
// after
rule.into_inner().next().ok_or_else(|| anyhow!("c_for_after_stmt cannot accept empty input"))?
Defensive patterns

Strategy: type-guard

Validate before calling

let has_child = header
    .clone()
    .find(|p| p.as_rule() == Rule::c_for_after_stmt)
    .map(|p| p.into_inner().next().is_some())
    .unwrap_or(true);
if !has_child {
    eprintln!("empty c_for_after_stmt; aborting parse of this loop");
}

Type guard

fn after_stmt_present(header: &Pairs<Rule>) -> bool {
    header.clone()
        .find(|p| p.as_rule() == Rule::c_for_after_stmt)
        .map(|p| p.into_inner().next().is_some())
        .unwrap_or(true)
}

Try / catch

let r = std::panic::catch_unwind(|| parse_c_for_loop(token, &mut diagnostics));
if r.is_err() {
    return Err(anyhow!("for-loop header is malformed"));
}

Prevention

When it happens

Trigger: Parsing an expression whose c_for_after_stmt grammar token contains no children — only expected when grammar and hand-written parser code are out of sync, or on malformed token streams.

Common situations: Modified/custom grammar builds of the baml ast crate; fuzz inputs producing degenerate parse trees.

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 BoundaryML/baml@bd85ce9dee (2026-09-12). Data as JSON: /api/errors/488ee14c94618560. Report an issue: GitHub.