BoundaryML/baml · error
use_last_expr_as_return is true but last statement is not Ex
Error message
use_last_expr_as_return is true but last statement is not Expression
What it means
This is an internal invariant violation (an `unreachable!()` panic) in evaluate_block_with_control_flow. The flag use_last_expr_as_return is only supposed to be set when the block's last statement is an Expression statement; if the last statement is anything else (assignment, loop, return, etc.) while the flag is still set, the interpreter panics with this message. It indicates a compiler/interpreter bug, not user error.
Source
Thrown at engine/baml-compiler/src/thir/interpret.rs:1382
.await?,
)?
} else if use_last_expr_as_return {
// No explicit trailing expression, but last statement is an expression statement,
// so use that as the implicit return value (handles cases like if-else at the end of a block)
if let Some(Statement::Expression { expr, .. }) = block.statements.last() {
expect_value(
evaluate_expr(
expr,
scopes,
thir,
run_llm_function,
watch_handler,
function_name,
)
.await?,
)?
} else {
unreachable!("use_last_expr_as_return is true but last statement is not Expression")
}
} else {
// No trailing expression and last statement is not an expression, return null
BamlValueWithMeta::Null((internal_baml_diagnostics::Span::fake(), None))
};
scopes.pop();
Ok(ControlFlow::Normal(ret))
})
}
async fn evaluate_block<F, Fut>(
block: &Block<ExprMetadata>,
scopes: &mut Vec<Scope>,
thir: &THir<ExprMetadata>,
run_llm_function: &mut F,
watch_handler: &SharedWatchHandler,
function_name: &str,
) -> Result<BamlValueWithMeta<ExprMetadata>>View on GitHub (pinned to bd85ce9dee)
Solutions
- Add an explicit final expression (or explicit `return`) as the last statement of the block so the implicit-return path is entered only with an Expression last statement.
- Check for a trailing non-expression statement (assignment, loop) at the end of the block and move or restructure it.
- Pin/downgrade to the previous BAML version if this appeared after an upgrade, and file a bug with the minimal reproducing BAML code.
- Upgrade to the latest BAML — this unreachable path may already be fixed.
Example fix
// before (BAML)
let r = if (x) { total += 1 } else { 0 };
// after
let r = if (x) { total += 1; total } else { 0 }; Defensive patterns
Strategy: fallback
Try / catch
// This is an internal panic; catch broadly and degrade:
try {
return await bamlFn(ctx, args);
} catch (e) {
if (String(e).includes("use_last_expr_as_return")) {
logBug("baml interpreter invariant violation — file a bug with this input");
return legacyPath(args);
}
throw e;
} Prevention
- End expression blocks with an explicit expression, never an assignment or loop.
- Add explicit `return`/final expression in blocks relying on implicit returns.
- Pin your BAML version and test expression blocks after upgrades.
- Report this panic upstream with a minimal reproducer — it is a compiler bug.
When it happens
Trigger: A BAML expression block evaluated with implicit-return semantics (use_last_expr_as_return=true) whose final statement is not an Expression — e.g. a block ending in an assignment, a for loop, or a watch statement where the compiler incorrectly enabled implicit return. Also occurs when an early `return` inside the block drains the statement list unexpectedly.
Common situations: Hitting this while using newer BAML expression-language features (blocks with implicit returns, if-else as final statement) on code shapes the interpreter doesn't yet handle; usually reported as a bug after upgrading the BAML version.
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
- baml.panics.IntegerOverflow
- baml.panics.IndexOutOfBounds
- baml.panics.InvalidFieldAccess
- baml.panics.MapKeyNotFound
- baml.panics.StackOverflow
AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12).
Data as JSON: /api/errors/8be0ac07e582fa41.
Report an issue: GitHub.